@tscircuit/fake-snippets 0.0.5 → 0.0.7

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-tests/fake-snippets-api/routes/package_files/create.test.ts +375 -0
  2. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +248 -0
  3. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +220 -0
  4. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +204 -0
  5. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +0 -1
  6. package/bun.lock +97 -73
  7. package/dist/bundle.js +602 -213
  8. package/fake-snippets-api/lib/db/db-client.ts +13 -0
  9. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +168 -0
  10. package/fake-snippets-api/lib/package_release/find-package-release-id.ts +122 -0
  11. package/fake-snippets-api/routes/api/package_files/create.ts +132 -0
  12. package/fake-snippets-api/routes/api/package_files/download.ts +70 -153
  13. package/fake-snippets-api/routes/api/package_files/get.ts +24 -5
  14. package/fake-snippets-api/routes/api/package_files/list.ts +16 -28
  15. package/fake-snippets-api/routes/api/package_releases/get.ts +21 -1
  16. package/index.html +113 -20
  17. package/package.json +9 -9
  18. package/playwright-tests/profile-page.spec.ts +59 -0
  19. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-before-delete.png +0 -0
  20. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-delete-dialog.png +0 -0
  21. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-dropdown-open.png +0 -0
  22. package/scripts/generate-image-sizes.ts +0 -1
  23. package/scripts/generate_bundle_stats.js +22 -6
  24. package/src/App.tsx +12 -1
  25. package/src/components/AiChatInterface.tsx +8 -0
  26. package/src/components/Analytics.tsx +1 -1
  27. package/src/components/CodeAndPreview.tsx +9 -3
  28. package/src/components/CreateNewSnippetWithAiHero.tsx +6 -2
  29. package/src/components/EditorNav.tsx +4 -0
  30. package/src/components/Footer.tsx +1 -1
  31. package/src/components/Header.tsx +7 -10
  32. package/src/components/HeaderLogin.tsx +1 -1
  33. package/src/components/PreviewContent.tsx +4 -1
  34. package/src/components/SnippetList.tsx +71 -0
  35. package/src/lib/templates/blinking-led-board-template.ts +2 -1
  36. package/src/pages/dashboard.tsx +19 -44
  37. package/src/pages/editor.tsx +1 -1
  38. package/src/pages/landing.tsx +8 -16
  39. package/src/pages/quickstart.tsx +9 -9
  40. package/src/pages/user-profile.tsx +50 -3
@@ -1,24 +1,24 @@
1
1
  import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
2
2
  import { z } from "zod"
3
3
  import * as ZT from "fake-snippets-api/lib/db/schema"
4
- import { NotFoundError } from "winterspec/middleware"
4
+ import { getPackageFileIdFromFileDescriptor } from "fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor"
5
5
 
6
6
  const routeSpec = {
7
7
  methods: ["POST"],
8
8
  auth: "none",
9
9
  jsonBody: z
10
10
  .object({
11
- package_file_id: z.string().uuid(),
11
+ package_file_id: z.string(),
12
12
  })
13
13
  .or(
14
14
  z.object({
15
- package_release_id: z.string().uuid(),
15
+ package_release_id: z.string(),
16
16
  file_path: z.string(),
17
17
  }),
18
18
  )
19
19
  .or(
20
20
  z.object({
21
- package_id: z.string().uuid(),
21
+ package_id: z.string(),
22
22
  version: z.string().optional(),
23
23
  file_path: z.string(),
24
24
  }),
@@ -45,8 +45,27 @@ const routeSpec = {
45
45
  } as const
46
46
 
47
47
  export default withRouteSpec(routeSpec)(async (req, ctx) => {
48
+ const packageFileId = await getPackageFileIdFromFileDescriptor(
49
+ req.jsonBody,
50
+ ctx,
51
+ )
52
+
53
+ const packageFile = ctx.db.packageFiles.find(
54
+ (pf: ZT.PackageFile) => pf.package_file_id === packageFileId,
55
+ )
56
+
57
+ if (!packageFile) {
58
+ return ctx.error(404, {
59
+ error_code: "package_file_not_found",
60
+ message: "Package file not found",
61
+ })
62
+ }
63
+
48
64
  return ctx.json({
49
65
  ok: true,
50
- package_file: undefined,
66
+ package_file: {
67
+ ...packageFile,
68
+ created_at: packageFile.created_at.toString(),
69
+ },
51
70
  })
52
71
  })
@@ -1,13 +1,14 @@
1
1
  import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
2
2
  import { z } from "zod"
3
3
  import * as ZT from "fake-snippets-api/lib/db/schema"
4
+ import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
4
5
 
5
6
  const routeSpec = {
6
7
  methods: ["POST"],
7
8
  auth: "none",
8
9
  jsonBody: z
9
10
  .object({
10
- package_release_id: z.string().uuid(),
11
+ package_release_id: z.string(),
11
12
  })
12
13
  .or(
13
14
  z.object({
@@ -27,34 +28,21 @@ const routeSpec = {
27
28
  } as const
28
29
 
29
30
  export default withRouteSpec(routeSpec)(async (req, ctx) => {
30
- // const package_release_id = await findPackageReleaseId(req.jsonBody, ctx)
31
- // if (!package_release_id) {
32
- // return ctx.error(404, {
33
- // error_code: "package_release_not_found",
34
- // message: "Package release not found",
35
- // })
36
- // }
37
- // const package_files = await ctx.db
38
- // .selectFrom("main.package_file")
39
- // .select([
40
- // "package_file_id",
41
- // "package_release_id",
42
- // "content_mimetype",
43
- // "file_path",
44
- // "created_at",
45
- // ])
46
- // .where("package_release_id", "=", package_release_id)
47
- // .where("file_path", "not like", ".tscircuit-internal/%")
48
- // .execute()
49
- // return ctx.json({
50
- // ok: true,
51
- // package_files: package_files.map((pf) => ({
52
- // ...pf,
53
- // created_at: pf.created_at.toISOString(),
54
- // })),
55
- // })
31
+ const packageReleaseId = await findPackageReleaseId(req.jsonBody, ctx)
32
+
33
+ if (!packageReleaseId) {
34
+ return ctx.error(404, {
35
+ error_code: "package_release_not_found",
36
+ message: "Package release not found",
37
+ })
38
+ }
39
+
40
+ const packageFiles = ctx.db.packageFiles.filter(
41
+ (file) => file.package_release_id === packageReleaseId,
42
+ )
43
+
56
44
  return ctx.json({
57
45
  ok: true,
58
- package_files: [],
46
+ package_files: packageFiles,
59
47
  })
60
48
  })
@@ -15,7 +15,27 @@ export default withRouteSpec({
15
15
  package_release: zt.packageReleaseSchema,
16
16
  }),
17
17
  })(async (req, ctx) => {
18
- const { package_release_id } = req.jsonBody
18
+ const { package_release_id, package_name_with_version } = req.jsonBody
19
+
20
+ if (package_name_with_version && !package_release_id) {
21
+ const [packageName, parsedVersion] = package_name_with_version.split("@")
22
+ const pkg = ctx.db.packages.find((x) => x.name === packageName)
23
+ const pkgRelease = ctx.db.packageReleases.find((x) => {
24
+ return x.version == parsedVersion && x.package_id == pkg?.package_id
25
+ })
26
+
27
+ if (!pkgRelease) {
28
+ return ctx.error(404, {
29
+ error_code: "package_release_not_found",
30
+ message: "Package release not found",
31
+ })
32
+ }
33
+
34
+ return ctx.json({
35
+ ok: true,
36
+ package_release: publicMapPackageRelease(pkgRelease),
37
+ })
38
+ }
19
39
 
20
40
  const foundRelease =
21
41
  package_release_id && ctx.db.getPackageReleaseById(package_release_id)
package/index.html CHANGED
@@ -1,23 +1,116 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="robots" content="index, follow, NOODP" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16">
8
- <title>tscircuit - Code Electronics with React</title>
9
- <meta name="description" content="tscircuit is an open-source electronics design tool that lets you create circuits using React components. Design schematics, generate PCB layouts, export and manufacture PCBs online!" />
10
- <meta name="keywords" content="electronic design, PCB design, schematic capture, React components, circuit design, electronics CAD, open source EDA" />
11
- <meta property="og:title" content="tscircuit - Design Electronics with React Components" />
12
- <meta property="og:description" content="Create electronic circuits using React components. Design schematics, generate PCB layouts, and manufacture custom PCBs with this free open-source tool." />
13
- <meta property="og:type" content="website" />
14
- <meta name="twitter:card" content="summary_large_image" />
15
- <meta name="twitter:title" content="tscircuit - Design Electronics with React Components" />
16
- <meta name="twitter:description" content="Create electronic circuits using React components. Free open-source electronics design tool." />
17
- <link rel="canonical" href="https://tscircuit.com" />
18
- </head>
19
- <body>
20
- <div id="root"></div>
21
- <script type="module" src="/src/main.tsx"></script>
22
- </body>
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="robots" content="index, follow, NOODP" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16" />
9
+ <title>tscircuit - Code Electronics with React</title>
10
+ <meta name="description"
11
+ content="tscircuit is an open-source electronics design tool that lets you create circuits using React components. Design schematics, generate PCB layouts, export and manufacture PCBs online!" />
12
+ <meta name="keywords"
13
+ content="electronic design, PCB design, schematic capture, React components, circuit design, electronics CAD, open source EDA" />
14
+ <meta property="og:title" content="tscircuit - Design Electronics with React Components" />
15
+ <meta property="og:description"
16
+ content="Create electronic circuits using React components. Design schematics, generate PCB layouts, and manufacture custom PCBs with this free open-source tool." />
17
+ <meta property="og:type" content="website" />
18
+ <meta name="twitter:card" content="summary_large_image" />
19
+ <meta name="twitter:title" content="tscircuit - Design Electronics with React Components" />
20
+ <meta name="twitter:description"
21
+ content="Create electronic circuits using React components. Free open-source electronics design tool." />
22
+ <link rel="canonical" href="https://tscircuit.com" />
23
+
24
+ <style>
25
+ /* Loader Styles */
26
+ .loading-overlay {
27
+ position: fixed;
28
+ top: 0;
29
+ left: 0;
30
+ width: 100%;
31
+ height: 100%;
32
+ background: rgba(255, 255, 255, 0.7);
33
+ display: flex;
34
+ justify-content: center;
35
+ align-items: center;
36
+ z-index: 9999;
37
+ }
38
+
39
+ .loading-container {
40
+ display: flex;
41
+ flex-direction: column;
42
+ align-items: center;
43
+ gap: 1em;
44
+ }
45
+
46
+ .loading {
47
+ background-color: lightgrey;
48
+ height: 2px;
49
+ overflow: hidden;
50
+ position: relative;
51
+ width: 12em;
52
+ border-radius: 2px;
53
+ }
54
+
55
+ .loading-bar {
56
+ animation: side2side 2s ease-in-out infinite;
57
+ background-color: dodgerblue;
58
+ height: 100%;
59
+ position: absolute;
60
+ width: 45%;
61
+ }
62
+
63
+ @keyframes side2side {
64
+ 0%, 100% {
65
+ transform: translateX(-50%);
66
+ }
67
+ 50% {
68
+ transform: translateX(150%);
69
+ }
70
+ }
71
+ </style>
72
+
73
+ <script>
74
+ window.onload = function() {
75
+ const loader = document.getElementById("loader");
76
+ const root = document.getElementById("root");
77
+
78
+ // Hide the loader and show the root when the page has fully loaded
79
+ loader.style.transition = "opacity 0.3s ease";
80
+ loader.style.opacity = "0"; // Fade out the loader
81
+ setTimeout(function() {
82
+ loader.style.display = "none"; // Hide loader completely
83
+ root.style.visibility = "visible"; // Show the root content
84
+ }, 300); // Match the opacity transition duration
85
+ };
86
+ </script>
87
+
88
+ <!-- Google tag (gtag.js) -->
89
+ <script async src="https://www.googletagmanager.com/gtag/js?id=AW-16607843883">
90
+ </script>
91
+ <script>
92
+ window.dataLayer = window.dataLayer || [];
93
+ function gtag(){dataLayer.push(arguments);}
94
+ gtag('js', new Date());
95
+
96
+ gtag('config', 'AW-16607843883');
97
+ </script>
98
+ </head>
99
+
100
+ <body>
101
+ <div class="loaderanimation">
102
+ <div id="loader" class="loading-overlay">
103
+ <div class="loading-container">
104
+ <div class="loading">
105
+ <div class="loading-bar"></div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div id="root" class="loaderanimation" style="visibility: hidden;"></div> <!-- Initially hidden -->
112
+
113
+ <script type="module" src="./src/main.tsx"></script>
114
+ </body>
115
+
23
116
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,13 +61,13 @@
61
61
  "@radix-ui/react-toggle": "^1.1.0",
62
62
  "@radix-ui/react-toggle-group": "^1.1.0",
63
63
  "@radix-ui/react-tooltip": "^1.1.2",
64
- "@tscircuit/3d-viewer": "^0.0.113",
65
- "@tscircuit/footprinter": "^0.0.102",
64
+ "@tscircuit/3d-viewer": "^0.0.142",
65
+ "@tscircuit/footprinter": "^0.0.124",
66
66
  "@tscircuit/layout": "^0.0.29",
67
67
  "@tscircuit/math-utils": "^0.0.10",
68
68
  "@tscircuit/mm": "^0.0.8",
69
69
  "@tscircuit/pcb-viewer": "^1.11.12",
70
- "@tscircuit/props": "^0.0.138",
70
+ "@tscircuit/props": "^0.0.143",
71
71
  "@tscircuit/schematic-viewer": "^1.4.3",
72
72
  "@types/file-saver": "^2.0.7",
73
73
  "@types/ms": "^0.7.34",
@@ -76,12 +76,12 @@
76
76
  "@valtown/codemirror-ts": "^2.2.0",
77
77
  "@vercel/analytics": "^1.4.1",
78
78
  "change-case": "^5.4.4",
79
- "circuit-json": "^0.0.135",
79
+ "circuit-json": "^0.0.136",
80
80
  "circuit-json-to-bom-csv": "^0.0.6",
81
81
  "circuit-json-to-gerber": "^0.0.16",
82
82
  "circuit-json-to-pnp-csv": "^0.0.6",
83
+ "circuit-json-to-readable-netlist": "^0.0.8",
83
84
  "circuit-json-to-tscircuit": "^0.0.4",
84
- "circuit-json-to-readable-netlist": "^0.0.7",
85
85
  "class-variance-authority": "^0.7.0",
86
86
  "clsx": "^2.1.1",
87
87
  "cmdk": "^1.0.4",
@@ -97,7 +97,7 @@
97
97
  "immer": "^10.1.1",
98
98
  "input-otp": "^1.2.4",
99
99
  "jose": "^5.9.3",
100
- "jscad-electronics": "^0.0.24",
100
+ "jscad-electronics": "^0.0.25",
101
101
  "jszip": "^3.10.1",
102
102
  "kicad-converter": "^0.0.16",
103
103
  "lucide-react": "^0.445.0",
@@ -130,9 +130,9 @@
130
130
  "@babel/standalone": "^7.26.2",
131
131
  "@biomejs/biome": "^1.9.2",
132
132
  "@playwright/test": "^1.48.0",
133
- "@tscircuit/core": "^0.0.296",
133
+ "@tscircuit/core": "^0.0.315",
134
134
  "@tscircuit/prompt-benchmarks": "^0.0.28",
135
- "@tscircuit/runframe": "^0.0.139",
135
+ "@tscircuit/runframe": "^0.0.193",
136
136
  "@types/babel__standalone": "^7.1.7",
137
137
  "@types/bun": "^1.1.10",
138
138
  "@types/country-list": "^2.1.4",
@@ -0,0 +1,59 @@
1
+ import { test, expect } from "@playwright/test"
2
+
3
+ test("test delete functionality in profile", async ({ page }) => {
4
+ // Go to profile page
5
+ await page.goto("http://localhost:5177/testuser")
6
+
7
+ // Login
8
+ await page.getByRole("button", { name: "Log in" }).click()
9
+
10
+ // Wait for snippets grid to load
11
+ await page.waitForSelector(".grid")
12
+ await page.waitForLoadState("networkidle")
13
+
14
+ // Verify initial snippet exists
15
+ const snippetTitle = page.locator(".text-md.font-semibold").first()
16
+ const snippetName = await snippetTitle.textContent()
17
+ expect(await snippetTitle.isVisible()).toBe(true)
18
+
19
+ // Take screenshot of initial state
20
+ await expect(page).toHaveScreenshot("profile-page-before-delete.png")
21
+
22
+ // Open dropdown menu
23
+ await page.locator(".lucide-ellipsis-vertical").first().click()
24
+ await page.waitForTimeout(1000)
25
+
26
+ // Take screenshot with dropdown open
27
+ await expect(page).toHaveScreenshot("profile-page-dropdown-open.png")
28
+
29
+ // Click delete option
30
+ await page.getByRole("menuitem", { name: "Delete Snippet" }).click()
31
+
32
+ // Wait for and verify confirmation dialog
33
+ const dialog = page.locator('[role="dialog"]')
34
+ await expect(dialog).toBeVisible()
35
+ const dialogText = await dialog.textContent()
36
+ expect(dialogText).toContain(
37
+ `Are you sure you want to delete the snippet "${snippetName}"?`,
38
+ )
39
+
40
+ // Take screenshot of delete dialog
41
+ await expect(page).toHaveScreenshot("profile-page-delete-dialog.png")
42
+
43
+ // Confirm delete
44
+ await page.getByRole("button", { name: "Delete" }).click()
45
+
46
+ // Verify success toast appears
47
+ await page.waitForSelector('div:has-text("Successfully deleted")', {
48
+ state: "visible",
49
+ })
50
+
51
+ // Wait for page to update
52
+ await page.waitForLoadState("networkidle")
53
+
54
+ // Verify snippet is removed
55
+ const remainingSnippets = await page
56
+ .locator(`.text-md.font-semibold:has-text("${snippetName}")`)
57
+ .count()
58
+ expect(remainingSnippets).toBe(0)
59
+ })
@@ -7,7 +7,6 @@ const INPUT_DIR = "src/assets/originals"
7
7
  const OUTPUT_DIR = "public/assets"
8
8
 
9
9
  async function generateImageSizes() {
10
- console.log("one")
11
10
  if (!fs.existsSync(INPUT_DIR)) {
12
11
  fs.mkdirSync(INPUT_DIR, { recursive: true })
13
12
  }
@@ -82,11 +82,22 @@ function compareSizes(prData, mainData, dependencies) {
82
82
  const mainSize = mainStats.depStats[dep]?.size || 0
83
83
  const diff = prSize - mainSize
84
84
 
85
+ let percentChange = "N/A"
86
+ if (prSize === 0 && mainSize > 0) {
87
+ percentChange = "Removed"
88
+ } else if (mainSize !== 0) {
89
+ percentChange = (diff / mainSize) * 100
90
+ } else if (prSize > 0 && mainSize === 0) {
91
+ percentChange = "Added"
92
+ } else {
93
+ percentChange = 0
94
+ }
95
+
85
96
  diffStats[dep] = {
86
97
  before: mainSize,
87
98
  after: prSize,
88
99
  diff,
89
- percentChange: (diff / mainSize) * 100 || 0,
100
+ percentChange,
90
101
  }
91
102
  })
92
103
 
@@ -95,7 +106,10 @@ function compareSizes(prData, mainData, dependencies) {
95
106
  totalAfter: prStats.totalSize,
96
107
  totalDiff: prStats.totalSize - mainStats.totalSize,
97
108
  totalPercentChange:
98
- ((prStats.totalSize - mainStats.totalSize) / mainStats.totalSize) * 100,
109
+ mainStats.totalSize !== 0
110
+ ? ((prStats.totalSize - mainStats.totalSize) / mainStats.totalSize) *
111
+ 100
112
+ : 0,
99
113
  diffStats,
100
114
  }
101
115
  }
@@ -107,7 +121,7 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
107
121
  markdown += `## Total Bundle Size\n\n`
108
122
  markdown += `- Before: **${formatBytes(comparison.totalBefore)}**\n`
109
123
  markdown += `- After: **${formatBytes(comparison.totalAfter)}**\n`
110
- markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${comparison.totalPercentChange.toFixed(2)}%)\n\n`
124
+ markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${isNaN(comparison.totalPercentChange) ? "N/A" : comparison.totalPercentChange.toFixed(2)}%)\n\n`
111
125
 
112
126
  markdown += `## Diff\n\n`
113
127
 
@@ -117,7 +131,10 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
117
131
 
118
132
  const significantChanges = sortedDiffs.filter(
119
133
  ([, stats]) =>
120
- Math.abs(stats.percentChange) > 1 || Math.abs(stats.diff) > 1024,
134
+ (typeof stats.percentChange === "number" &&
135
+ (Math.abs(stats.percentChange) > 1 || Math.abs(stats.diff) > 1024)) ||
136
+ stats.percentChange === "Added" ||
137
+ stats.percentChange === "Removed",
121
138
  )
122
139
 
123
140
  if (significantChanges.length > 0) {
@@ -127,8 +144,7 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
127
144
  for (const [name, stats] of significantChanges) {
128
145
  const version = dependencies[name]
129
146
  const symbol = stats.diff > 0 ? "📈" : "📉"
130
- markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} |
131
- ${stats.percentChange.toFixed(2)}% |\n`
147
+ markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} | ${typeof stats.percentChange === "number" ? stats.percentChange.toFixed(2) + "%" : stats.percentChange} |\n`
132
148
  }
133
149
  } else {
134
150
  markdown += "No significant changes in bundle size.\n"
package/src/App.tsx CHANGED
@@ -5,9 +5,20 @@ import "./components/CmdKMenu"
5
5
  import { ContextProviders } from "./ContextProviders"
6
6
  import React from "react"
7
7
 
8
+ const FullPageLoader = () => (
9
+ <div className="fixed inset-0 flex items-center justify-center bg-white z-50">
10
+ <div className="w-48">
11
+ <div className="loading">
12
+ <div className="loading-bar"></div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ )
17
+
8
18
  const lazyImport = (importFn: () => Promise<any>) =>
9
19
  lazy<ComponentType<any>>(async () => {
10
20
  try {
21
+ await new Promise((resolve) => setTimeout(resolve, 500))
11
22
  const module = await importFn()
12
23
 
13
24
  if (module.default) {
@@ -85,7 +96,7 @@ function App() {
85
96
  return (
86
97
  <ContextProviders>
87
98
  <ErrorBoundary>
88
- <Suspense fallback={<div>Loading...</div>}>
99
+ <Suspense fallback={<FullPageLoader />}>
89
100
  <Switch>
90
101
  <Route path="/" component={LandingPage} />
91
102
  <Route path="/editor" component={EditorPage} />
@@ -165,6 +165,14 @@ export default function AIChatInterface({
165
165
  {messages.length === 0 && isLoggedIn && (
166
166
  <div className="text-gray-500 text-xl text-center pt-[30vh] flex flex-col items-center">
167
167
  <div>Submit a prompt to {snippet ? "edit!" : "get started!"}</div>
168
+ <div className="mt-2">
169
+ This is our legacy AI chat interface. For a better experience,
170
+ please use{" "}
171
+ <PrefetchPageLink href="https://chat.tscircuit.com">
172
+ chat.tscircuit.com
173
+ </PrefetchPageLink>
174
+ .
175
+ </div>
168
176
  <div className="text-6xl mt-4">↓</div>
169
177
  </div>
170
178
  )}
@@ -3,7 +3,7 @@ import posthog from "posthog-js"
3
3
  import CookieConsent from "react-cookie-consent"
4
4
 
5
5
  posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
6
- api_host: "https://us.i.posthog.com",
6
+ api_host: "https://postpig.tscircuit.com",
7
7
  person_profiles: "always",
8
8
  })
9
9
 
@@ -166,6 +166,15 @@ export function CodeAndPreview({ snippet }: Props) {
166
166
  const [lastSavedAt, setLastSavedAt] = useState(Date.now())
167
167
 
168
168
  const handleSave = async () => {
169
+ if (hasUnrunChanges) {
170
+ toast({
171
+ title: "Warning",
172
+ description: "You must run the snippet before saving your changes.",
173
+ variant: "destructive",
174
+ })
175
+ return
176
+ }
177
+
169
178
  setLastSavedAt(Date.now())
170
179
  if (snippet) {
171
180
  updateSnippetMutation.mutate()
@@ -197,8 +206,6 @@ export function CodeAndPreview({ snippet }: Props) {
197
206
  ...(code.match(/export const (\w+) ?=/)?.slice(1) ?? []),
198
207
  ]
199
208
 
200
- console.log(possibleExportNames)
201
-
202
209
  const exportName = possibleExportNames[0]
203
210
 
204
211
  let entrypointContent: string
@@ -224,7 +231,6 @@ export function CodeAndPreview({ snippet }: Props) {
224
231
  "main.tsx": entrypointContent,
225
232
  }
226
233
  }, [code, manualEditsFileContent])
227
- console.log(fsMap)
228
234
 
229
235
  if (!snippet && (urlParams.snippet_id || urlParams.should_create_snippet)) {
230
236
  return (
@@ -12,12 +12,16 @@ export function CreateNewSnippetWithAiHero() {
12
12
  const handleSubmit = (e: React.FormEvent) => {
13
13
  e.preventDefault()
14
14
  if (inputValue.trim()) {
15
- navigate(`/ai?initial_prompt=${encodeURIComponent(inputValue)}`)
15
+ navigate(
16
+ `https://chat.tscircuit.com?initial_prompt=${encodeURIComponent(inputValue)}`,
17
+ )
16
18
  }
17
19
  }
18
20
 
19
21
  const handleQuickPrompt = (prompt: string) => {
20
- navigate(`/ai?initial_prompt=${encodeURIComponent(prompt)}`)
22
+ navigate(
23
+ `https://chat.tscircuit.com?initial_prompt=${encodeURIComponent(prompt)}`,
24
+ )
21
25
  }
22
26
 
23
27
  return (
@@ -48,6 +48,7 @@ import { SnippetLink } from "./SnippetLink"
48
48
  import { TypeBadge } from "./TypeBadge"
49
49
  import { useUpdateDescriptionDialog } from "./dialogs/edit-description-dialog"
50
50
  import { useForkSnippetMutation } from "@/hooks/useForkSnippetMutation"
51
+ import tscircuitCorePkg from "@tscircuit/core/package.json"
51
52
 
52
53
  export default function EditorNav({
53
54
  circuitJson,
@@ -367,6 +368,9 @@ export default function EditorNav({
367
368
  <Trash2 className="mr-2 h-3 w-3" />
368
369
  Delete Snippet
369
370
  </DropdownMenuItem>
371
+ <DropdownMenuItem className="text-xs text-gray-500" disabled>
372
+ @tscircuit/core@{tscircuitCorePkg.version}
373
+ </DropdownMenuItem>
370
374
  </DropdownMenuContent>
371
375
  </DropdownMenu>
372
376
  <Button
@@ -26,7 +26,7 @@ export default function Footer() {
26
26
  { name: "Home", href: "/" },
27
27
  { name: "Dashboard", href: "/dashboard" },
28
28
  { name: "Editor", href: "/editor" },
29
- { name: "Create with AI", href: "/ai" },
29
+ { name: "Create with AI", href: "https://chat.tscircuit.com" },
30
30
  {
31
31
  name: "My Profile",
32
32
  href: `/${session?.github_username}`,