create-next-imagicma 0.0.4 → 0.0.6

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 (106) hide show
  1. package/README.md +30 -13
  2. package/bin/create-next-imagicma.mjs +138 -22
  3. package/package.json +2 -1
  4. package/template/app/globals.css +331 -0
  5. package/template/app/layout.tsx +4 -6
  6. package/template/app/page.tsx +18 -40
  7. package/template/package.json +1 -1
  8. package/template/public/imagicma-picker-bridge.js +374 -0
  9. package/template-hono/.env.example +8 -0
  10. package/template-hono/.imagicma/AGENTS.md +7 -0
  11. package/template-hono/.imagicma/port.json +5 -0
  12. package/template-hono/AGENTS.md +39 -0
  13. package/template-hono/README.md +48 -0
  14. package/template-hono/client/src/App.tsx +13 -0
  15. package/template-hono/client/src/components/ErrorBoundary.tsx +74 -0
  16. package/template-hono/client/src/components/HelloClient.tsx +69 -0
  17. package/template-hono/client/src/globals.css +767 -0
  18. package/template-hono/client/src/main.tsx +24 -0
  19. package/template-hono/client/src/pages/HelloPage.tsx +22 -0
  20. package/template-hono/client/src/pages/HomePage.tsx +30 -0
  21. package/template-hono/client/src/providers.tsx +21 -0
  22. package/template-hono/components/ui/accordion.tsx +58 -0
  23. package/template-hono/components/ui/alert-dialog.tsx +141 -0
  24. package/template-hono/components/ui/alert.tsx +61 -0
  25. package/template-hono/components/ui/aspect-ratio.tsx +7 -0
  26. package/template-hono/components/ui/avatar.tsx +51 -0
  27. package/template-hono/components/ui/badge.tsx +40 -0
  28. package/template-hono/components/ui/breadcrumb.tsx +117 -0
  29. package/template-hono/components/ui/button.tsx +64 -0
  30. package/template-hono/components/ui/calendar.tsx +72 -0
  31. package/template-hono/components/ui/card.tsx +87 -0
  32. package/template-hono/components/ui/carousel.tsx +262 -0
  33. package/template-hono/components/ui/chart.tsx +365 -0
  34. package/template-hono/components/ui/checkbox.tsx +30 -0
  35. package/template-hono/components/ui/collapsible.tsx +11 -0
  36. package/template-hono/components/ui/command.tsx +153 -0
  37. package/template-hono/components/ui/context-menu.tsx +200 -0
  38. package/template-hono/components/ui/dialog.tsx +122 -0
  39. package/template-hono/components/ui/drawer.tsx +118 -0
  40. package/template-hono/components/ui/dropdown-menu.tsx +200 -0
  41. package/template-hono/components/ui/form.tsx +178 -0
  42. package/template-hono/components/ui/hover-card.tsx +29 -0
  43. package/template-hono/components/ui/input-otp.tsx +71 -0
  44. package/template-hono/components/ui/input.tsx +25 -0
  45. package/template-hono/components/ui/label.tsx +26 -0
  46. package/template-hono/components/ui/menubar.tsx +256 -0
  47. package/template-hono/components/ui/navigation-menu.tsx +130 -0
  48. package/template-hono/components/ui/pagination.tsx +119 -0
  49. package/template-hono/components/ui/popover.tsx +31 -0
  50. package/template-hono/components/ui/progress.tsx +28 -0
  51. package/template-hono/components/ui/radio-group.tsx +44 -0
  52. package/template-hono/components/ui/resizable.tsx +45 -0
  53. package/template-hono/components/ui/scroll-area.tsx +48 -0
  54. package/template-hono/components/ui/select.tsx +160 -0
  55. package/template-hono/components/ui/separator.tsx +31 -0
  56. package/template-hono/components/ui/sheet.tsx +140 -0
  57. package/template-hono/components/ui/sidebar.tsx +732 -0
  58. package/template-hono/components/ui/skeleton.tsx +17 -0
  59. package/template-hono/components/ui/slider.tsx +28 -0
  60. package/template-hono/components/ui/switch.tsx +29 -0
  61. package/template-hono/components/ui/table.tsx +119 -0
  62. package/template-hono/components/ui/tabs.tsx +55 -0
  63. package/template-hono/components/ui/textarea.tsx +24 -0
  64. package/template-hono/components/ui/toast.tsx +129 -0
  65. package/template-hono/components/ui/toaster.tsx +35 -0
  66. package/template-hono/components/ui/toggle-group.tsx +61 -0
  67. package/template-hono/components/ui/toggle.tsx +45 -0
  68. package/template-hono/components/ui/tooltip.tsx +30 -0
  69. package/template-hono/drizzle.config.ts +50 -0
  70. package/template-hono/eslint.config.mjs +13 -0
  71. package/template-hono/gitignore +40 -0
  72. package/template-hono/hooks/use-greeting.ts +15 -0
  73. package/template-hono/hooks/use-mobile.ts +21 -0
  74. package/template-hono/hooks/use-toast.ts +194 -0
  75. package/template-hono/index.html +13 -0
  76. package/template-hono/lib/queryClient.ts +59 -0
  77. package/template-hono/lib/theme/default-theme.ts +11 -0
  78. package/template-hono/lib/utils.ts +6 -0
  79. package/template-hono/package.json +82 -0
  80. package/template-hono/pnpm-lock.yaml +5162 -0
  81. package/template-hono/postcss.config.mjs +7 -0
  82. package/template-hono/process-compose.yaml +13 -0
  83. package/template-hono/public/favicon.ico +0 -0
  84. package/template-hono/public/file.svg +1 -0
  85. package/template-hono/public/globe.svg +1 -0
  86. package/template-hono/public/imagicma-picker-bridge.js +374 -0
  87. package/template-hono/public/next.svg +1 -0
  88. package/template-hono/public/vercel.svg +1 -0
  89. package/template-hono/public/window.svg +1 -0
  90. package/template-hono/scripts/imagicma-common.mjs +118 -0
  91. package/template-hono/scripts/imagicma-dev.mjs +29 -0
  92. package/template-hono/scripts/imagicma-guard.mjs +17 -0
  93. package/template-hono/scripts/imagicma-start.mjs +24 -0
  94. package/template-hono/server/app.ts +40 -0
  95. package/template-hono/server/db.ts +22 -0
  96. package/template-hono/server/dev-app.ts +5 -0
  97. package/template-hono/server/index.ts +94 -0
  98. package/template-hono/server/routes/greeting.ts +25 -0
  99. package/template-hono/server/storage.ts +39 -0
  100. package/template-hono/shared/routes.ts +13 -0
  101. package/template-hono/shared/schema.ts +17 -0
  102. package/template-hono/tailwind.config.mjs +97 -0
  103. package/template-hono/tsconfig.json +39 -0
  104. package/template-hono/tsconfig.server.json +15 -0
  105. package/template-hono/types/pg.d.ts +19 -0
  106. package/template-hono/vite.config.ts +125 -0
@@ -1,6 +1,7 @@
1
1
  import type { Metadata } from "next";
2
+ import Script from "next/script";
2
3
  import "./globals.css";
3
- import { DevPreviewShield } from "./_components/DevPreviewShield";
4
+ // import { DevPreviewShield } from "./_components/DevPreviewShield";
4
5
  import { Providers } from "./providers";
5
6
  import { DEFAULT_THEME_STYLE } from "@/lib/theme/default-theme";
6
7
 
@@ -21,12 +22,9 @@ export default function RootLayout({
21
22
  data-theme-style={DEFAULT_THEME_STYLE}
22
23
  >
23
24
  <body className="antialiased">
25
+ <Script src="/imagicma-picker-bridge.js" strategy="beforeInteractive" />
24
26
  <Providers>
25
- {process.env.NODE_ENV === "development" ? (
26
- <DevPreviewShield>{children}</DevPreviewShield>
27
- ) : (
28
- children
29
- )}
27
+ {children}
30
28
  </Providers>
31
29
  </body>
32
30
  </html>
@@ -1,47 +1,25 @@
1
1
  export default function Home() {
2
2
  return (
3
- <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-indigo-600 via-purple-600 to-sky-600 px-6 py-16 text-white">
4
- <div className="absolute inset-0 bg-[radial-gradient(60%_60%_at_50%_40%,rgba(255,255,255,0.20),transparent_60%)]" />
5
- <main className="relative w-full max-w-xl rounded-3xl border border-white/15 bg-white/10 p-10 shadow-2xl backdrop-blur-xl">
6
- <div className="flex flex-col items-center text-center">
7
- <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-white/10">
8
- <svg
9
- viewBox="0 0 24 24"
10
- className="h-9 w-9 text-white/90"
11
- fill="none"
12
- aria-hidden="true"
13
- >
14
- <path
15
- d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z"
16
- stroke="currentColor"
17
- strokeWidth="1.8"
18
- />
19
- <path
20
- d="M19.4 13.1a7.8 7.8 0 0 0 0-2.2l2-1.6a.7.7 0 0 0 .2-.9l-1.9-3.2a.7.7 0 0 0-.8-.3l-2.4 1a8.3 8.3 0 0 0-1.9-1.1l-.4-2.5a.7.7 0 0 0-.7-.6H8.5a.7.7 0 0 0-.7.6l-.4 2.5a8.3 8.3 0 0 0-1.9 1.1l-2.4-1a.7.7 0 0 0-.8.3L.4 8.4a.7.7 0 0 0 .2.9l2 1.6a7.8 7.8 0 0 0 0 2.2l-2 1.6a.7.7 0 0 0-.2.9l1.9 3.2c.2.3.5.4.8.3l2.4-1a8.3 8.3 0 0 0 1.9 1.1l.4 2.5c.1.3.3.6.7.6h3.7c.3 0 .6-.2.7-.6l.4-2.5a8.3 8.3 0 0 0 1.9-1.1l2.4 1c.3.1.6 0 .8-.3l1.9-3.2a.7.7 0 0 0-.2-.9l-2-1.6Z"
21
- stroke="currentColor"
22
- strokeWidth="1.2"
23
- strokeLinejoin="round"
24
- opacity="0.85"
25
- />
26
- </svg>
27
- </div>
3
+ <div className="relative min-h-screen overflow-hidden bg-gradient-to-br from-indigo-600 via-purple-600 to-sky-600 text-white">
4
+ <div aria-hidden className="pointer-events-none absolute inset-0 overflow-hidden">
5
+ <div className="absolute inset-0 bg-[radial-gradient(60%_60%_at_50%_40%,rgba(255,255,255,0.20),transparent_60%)]" />
6
+ </div>
28
7
 
29
- <h1 className="mt-6 text-4xl font-semibold tracking-tight">
30
- 项目构建中
31
- </h1>
32
- <p className="mt-4 max-w-md text-base leading-7 text-white/80">
33
- 我们正在努力为你构建预览服务,请稍后再试。
34
- </p>
8
+ <div aria-hidden className="aurora-frame">
9
+ <span className="aurora-edge top" />
10
+ <span className="aurora-edge right" />
11
+ <span className="aurora-edge bottom" />
12
+ <span className="aurora-edge left" />
13
+ <span className="aurora-edge top is-glow" />
14
+ <span className="aurora-edge right is-glow" />
15
+ <span className="aurora-edge bottom is-glow" />
16
+ <span className="aurora-edge left is-glow" />
17
+ </div>
35
18
 
36
- <div
37
- className="mt-10 flex items-center gap-3 text-sm text-white/70"
38
- role="status"
39
- aria-live="polite"
40
- >
41
- <span className="sr-only">加载中</span>
42
- <div className="h-5 w-5 rounded-full border-2 border-white/25 border-t-white/90 motion-reduce:animate-none animate-spin" />
43
- <span>准备中…</span>
44
- </div>
19
+ <main className="relative z-[1] flex min-h-screen items-center justify-center">
20
+ <div role="status" aria-live="polite">
21
+ <span className="sr-only">加载中</span>
22
+ <div className="h-10 w-10 rounded-full border-[3px] border-white/25 border-t-white/90 motion-reduce:animate-none animate-spin" />
45
23
  </div>
46
24
  </main>
47
25
  </div>
@@ -3,7 +3,7 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "next dev",
6
+ "dev": "next dev -p 5005",
7
7
  "build": "next build",
8
8
  "start": "next start",
9
9
  "check": "tsc -p tsconfig.json --noEmit",
@@ -0,0 +1,374 @@
1
+ (function () {
2
+ var CHANNEL = 'imagicma.preview-picker';
3
+ var VERSION = 1;
4
+ var MAX_TEXT_LENGTH = 240;
5
+ var MAX_SELECTOR_LENGTH = 512;
6
+ var PROD_PARENT_ORIGIN = 'https://www.imagicma.cn';
7
+ var LOCAL_PARENT_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i;
8
+ var LOCAL_IMAGICMA_PARENT_RE = /^https?:\/\/([a-z0-9-]+\.)?local\.imagicma\.cn(:\d+)?$/i;
9
+
10
+ var state = {
11
+ active: false,
12
+ sessionId: '',
13
+ nonce: '',
14
+ parentOrigin: '',
15
+ hoverEl: null,
16
+ overlayEl: null,
17
+ rafId: 0,
18
+ };
19
+
20
+ var listenersBound = false;
21
+
22
+ function isAllowedParentOrigin(origin) {
23
+ if (!origin) return false;
24
+ return origin === PROD_PARENT_ORIGIN || LOCAL_PARENT_RE.test(origin) || LOCAL_IMAGICMA_PARENT_RE.test(origin);
25
+ }
26
+
27
+ function isRecord(value) {
28
+ return typeof value === 'object' && value !== null;
29
+ }
30
+
31
+ function trimText(value) {
32
+ return typeof value === 'string' ? value.trim() : '';
33
+ }
34
+
35
+ function truncateText(value, max) {
36
+ if (!value) return '';
37
+ return value.length > max ? value.slice(0, max) : value;
38
+ }
39
+
40
+ function safePostMessage(type, payload) {
41
+ if (!state.parentOrigin) return;
42
+ if (window.parent === window) return;
43
+
44
+ try {
45
+ window.parent.postMessage(
46
+ {
47
+ channel: CHANNEL,
48
+ version: VERSION,
49
+ type: type,
50
+ sessionId: state.sessionId,
51
+ nonce: state.nonce,
52
+ payload: payload,
53
+ },
54
+ state.parentOrigin,
55
+ );
56
+ } catch (_error) {
57
+ // Ignore postMessage failures.
58
+ }
59
+ }
60
+
61
+ function ensureOverlay() {
62
+ if (state.overlayEl && document.body.contains(state.overlayEl)) return state.overlayEl;
63
+ var overlay = document.createElement('div');
64
+ overlay.setAttribute('data-imagicma-picker-overlay', 'true');
65
+ overlay.style.position = 'fixed';
66
+ overlay.style.left = '0';
67
+ overlay.style.top = '0';
68
+ overlay.style.width = '0';
69
+ overlay.style.height = '0';
70
+ overlay.style.zIndex = '2147483647';
71
+ overlay.style.pointerEvents = 'none';
72
+ overlay.style.border = '2px solid #1d4ed8';
73
+ overlay.style.background = 'rgba(29, 78, 216, 0.10)';
74
+ overlay.style.boxSizing = 'border-box';
75
+ overlay.style.display = 'none';
76
+ document.body.appendChild(overlay);
77
+ state.overlayEl = overlay;
78
+ return overlay;
79
+ }
80
+
81
+ function hideOverlay() {
82
+ if (!state.overlayEl) return;
83
+ state.overlayEl.style.display = 'none';
84
+ }
85
+
86
+ function updateOverlay() {
87
+ if (!state.active || !state.hoverEl) {
88
+ hideOverlay();
89
+ return;
90
+ }
91
+
92
+ var rect = state.hoverEl.getBoundingClientRect();
93
+ var overlay = ensureOverlay();
94
+ if (rect.width <= 0 || rect.height <= 0) {
95
+ overlay.style.display = 'none';
96
+ return;
97
+ }
98
+
99
+ overlay.style.display = 'block';
100
+ overlay.style.left = rect.left + 'px';
101
+ overlay.style.top = rect.top + 'px';
102
+ overlay.style.width = rect.width + 'px';
103
+ overlay.style.height = rect.height + 'px';
104
+ }
105
+
106
+ function queueOverlayUpdate() {
107
+ if (state.rafId) return;
108
+ state.rafId = window.requestAnimationFrame(function () {
109
+ state.rafId = 0;
110
+ updateOverlay();
111
+ });
112
+ }
113
+
114
+ function isValidElementTarget(target) {
115
+ if (!(target instanceof Element)) return false;
116
+ if (state.overlayEl && state.overlayEl.contains(target)) return false;
117
+ return true;
118
+ }
119
+
120
+ function cssEscapeIdent(value) {
121
+ if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') {
122
+ return window.CSS.escape(value);
123
+ }
124
+ return value.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
125
+ }
126
+
127
+ function selectorByClassChain(element) {
128
+ var tokens = [];
129
+ if (element.classList && element.classList.length > 0) {
130
+ for (var i = 0; i < element.classList.length && i < 3; i += 1) {
131
+ var cls = trimText(element.classList[i]);
132
+ if (cls) tokens.push('.' + cssEscapeIdent(cls));
133
+ }
134
+ }
135
+ return tokens.length > 0 ? element.tagName.toLowerCase() + tokens.join('') : '';
136
+ }
137
+
138
+ function selectorByPath(element) {
139
+ var parts = [];
140
+ var current = element;
141
+ var depth = 0;
142
+ while (current && current.nodeType === 1 && depth < 6) {
143
+ var part = current.tagName.toLowerCase();
144
+ if (current.id) {
145
+ part += '#' + cssEscapeIdent(current.id);
146
+ parts.unshift(part);
147
+ break;
148
+ }
149
+
150
+ var classSelector = selectorByClassChain(current);
151
+ if (classSelector) {
152
+ part = classSelector;
153
+ } else if (current.parentElement) {
154
+ var siblings = current.parentElement.children;
155
+ var sameTagIndex = 0;
156
+ var sameTagCount = 0;
157
+ for (var i = 0; i < siblings.length; i += 1) {
158
+ var sibling = siblings[i];
159
+ if (sibling.tagName === current.tagName) {
160
+ sameTagCount += 1;
161
+ if (sibling === current) {
162
+ sameTagIndex = sameTagCount;
163
+ }
164
+ }
165
+ }
166
+ if (sameTagCount > 1 && sameTagIndex > 0) {
167
+ part += ':nth-of-type(' + sameTagIndex + ')';
168
+ }
169
+ }
170
+
171
+ parts.unshift(part);
172
+ current = current.parentElement;
173
+ depth += 1;
174
+ }
175
+
176
+ return parts.join(' > ');
177
+ }
178
+
179
+ function getElementSelector(element) {
180
+ if (element.id) {
181
+ return '#' + cssEscapeIdent(element.id);
182
+ }
183
+
184
+ var testId = element.getAttribute('data-testid');
185
+ if (testId) {
186
+ return '[data-testid="' + testId.replace(/"/g, '\\"') + '"]';
187
+ }
188
+
189
+ var ariaLabel = element.getAttribute('aria-label');
190
+ if (ariaLabel) {
191
+ return element.tagName.toLowerCase() + '[aria-label="' + ariaLabel.replace(/"/g, '\\"') + '"]';
192
+ }
193
+
194
+ var role = element.getAttribute('role');
195
+ if (role) {
196
+ return element.tagName.toLowerCase() + '[role="' + role.replace(/"/g, '\\"') + '"]';
197
+ }
198
+
199
+ var byClass = selectorByClassChain(element);
200
+ if (byClass) return byClass;
201
+
202
+ return selectorByPath(element) || element.tagName.toLowerCase();
203
+ }
204
+
205
+ function buildSelectionPayload(element) {
206
+ var rect = element.getBoundingClientRect();
207
+ var selector = truncateText(getElementSelector(element), MAX_SELECTOR_LENGTH);
208
+ var textSource = trimText(element.innerText || element.textContent || '');
209
+ var attributes = {};
210
+
211
+ function putAttr(name, value) {
212
+ if (!name || typeof value !== 'string') return;
213
+ var key = trimText(name).toLowerCase();
214
+ var val = trimText(value);
215
+ if (!key || !val) return;
216
+ attributes[key] = truncateText(val, 240);
217
+ }
218
+
219
+ putAttr('id', element.id || '');
220
+ putAttr('class', element.className || '');
221
+ putAttr('role', element.getAttribute('role') || '');
222
+ putAttr('name', element.getAttribute('name') || '');
223
+ putAttr('type', element.getAttribute('type') || '');
224
+ putAttr('title', element.getAttribute('title') || '');
225
+ putAttr('aria-label', element.getAttribute('aria-label') || '');
226
+ putAttr('aria-labelledby', element.getAttribute('aria-labelledby') || '');
227
+ putAttr('aria-describedby', element.getAttribute('aria-describedby') || '');
228
+ putAttr('placeholder', element.getAttribute('placeholder') || '');
229
+ putAttr('href', element.getAttribute('href') || '');
230
+ putAttr('src', element.getAttribute('src') || '');
231
+ putAttr('alt', element.getAttribute('alt') || '');
232
+
233
+ if (element.attributes && element.attributes.length > 0) {
234
+ for (var i = 0; i < element.attributes.length; i += 1) {
235
+ var attr = element.attributes[i];
236
+ if (!attr) continue;
237
+ var attrName = trimText(attr.name || '').toLowerCase();
238
+ if (!attrName) continue;
239
+ if (attrName.indexOf('data-') === 0 || attrName.indexOf('aria-') === 0) {
240
+ putAttr(attrName, attr.value || '');
241
+ }
242
+ }
243
+ }
244
+
245
+ return {
246
+ pageUrl: window.location.href,
247
+ selector: selector,
248
+ tagName: element.tagName.toLowerCase(),
249
+ textSnippet: truncateText(textSource, MAX_TEXT_LENGTH),
250
+ attributes: attributes,
251
+ rect: {
252
+ x: Number(rect.left.toFixed(2)),
253
+ y: Number(rect.top.toFixed(2)),
254
+ width: Number(rect.width.toFixed(2)),
255
+ height: Number(rect.height.toFixed(2)),
256
+ },
257
+ };
258
+ }
259
+
260
+ function handleMouseMove(event) {
261
+ if (!state.active) return;
262
+ var target = event.target;
263
+ if (!isValidElementTarget(target)) return;
264
+ state.hoverEl = target;
265
+ queueOverlayUpdate();
266
+ }
267
+
268
+ function handleScrollOrResize() {
269
+ if (!state.active) return;
270
+ queueOverlayUpdate();
271
+ }
272
+
273
+ function handleClickCapture(event) {
274
+ if (!state.active) return;
275
+ var target = event.target;
276
+ if (!isValidElementTarget(target)) return;
277
+
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+ if (typeof event.stopImmediatePropagation === 'function') {
281
+ event.stopImmediatePropagation();
282
+ }
283
+
284
+ try {
285
+ var payload = buildSelectionPayload(target);
286
+ safePostMessage('IMAGICMA_PICKER_SELECTED', payload);
287
+ } catch (error) {
288
+ safePostMessage('IMAGICMA_PICKER_ERROR', {
289
+ message: error instanceof Error ? error.message : 'Failed to build selected element payload',
290
+ });
291
+ }
292
+ }
293
+
294
+ function bindInteractionListeners() {
295
+ if (listenersBound) return;
296
+ listenersBound = true;
297
+ document.addEventListener('mousemove', handleMouseMove, true);
298
+ document.addEventListener('click', handleClickCapture, true);
299
+ window.addEventListener('scroll', handleScrollOrResize, true);
300
+ window.addEventListener('resize', handleScrollOrResize, true);
301
+ }
302
+
303
+ function unbindInteractionListeners() {
304
+ if (!listenersBound) return;
305
+ listenersBound = false;
306
+ document.removeEventListener('mousemove', handleMouseMove, true);
307
+ document.removeEventListener('click', handleClickCapture, true);
308
+ window.removeEventListener('scroll', handleScrollOrResize, true);
309
+ window.removeEventListener('resize', handleScrollOrResize, true);
310
+ }
311
+
312
+ function activatePicking() {
313
+ state.active = true;
314
+ state.hoverEl = null;
315
+ bindInteractionListeners();
316
+ document.documentElement.style.cursor = 'crosshair';
317
+ document.body.style.cursor = 'crosshair';
318
+ safePostMessage('IMAGICMA_PICKER_READY', {
319
+ pageUrl: window.location.href,
320
+ });
321
+ }
322
+
323
+ function deactivatePicking() {
324
+ state.active = false;
325
+ state.hoverEl = null;
326
+ if (state.rafId) {
327
+ window.cancelAnimationFrame(state.rafId);
328
+ state.rafId = 0;
329
+ }
330
+ hideOverlay();
331
+ unbindInteractionListeners();
332
+ document.documentElement.style.cursor = '';
333
+ document.body.style.cursor = '';
334
+ }
335
+
336
+ function isValidMessage(data) {
337
+ if (!isRecord(data)) return false;
338
+ if (data.channel !== CHANNEL) return false;
339
+ if (data.version !== VERSION) return false;
340
+ if (typeof data.type !== 'string') return false;
341
+ if (typeof data.sessionId !== 'string' || data.sessionId.length === 0) return false;
342
+ if (typeof data.nonce !== 'string' || data.nonce.length === 0) return false;
343
+ return true;
344
+ }
345
+
346
+ function handleParentMessage(event) {
347
+ if (!isAllowedParentOrigin(event.origin)) return;
348
+ if (!isValidMessage(event.data)) return;
349
+
350
+ var data = event.data;
351
+ state.parentOrigin = event.origin;
352
+ state.sessionId = data.sessionId;
353
+ state.nonce = data.nonce;
354
+
355
+ if (data.type === 'IMAGICMA_PICKER_PING') {
356
+ safePostMessage('IMAGICMA_PICKER_READY', {
357
+ pageUrl: window.location.href,
358
+ });
359
+ return;
360
+ }
361
+
362
+ if (data.type === 'IMAGICMA_PICKER_START') {
363
+ activatePicking();
364
+ return;
365
+ }
366
+
367
+ if (data.type === 'IMAGICMA_PICKER_STOP') {
368
+ deactivatePicking();
369
+ }
370
+ }
371
+
372
+ window.addEventListener('message', handleParentMessage);
373
+ window.addEventListener('beforeunload', deactivatePicking);
374
+ })();
@@ -0,0 +1,8 @@
1
+ # 复制为 .env.local 并填入真实值(.env.local 不会被提交)
2
+ #
3
+ # Postgres 连接串(Drizzle + pg 使用)
4
+ # 示例:
5
+ # DATABASE_URL="postgres://user:pass@localhost:5432/mydb"
6
+ DATABASE_URL=""
7
+
8
+
@@ -0,0 +1,7 @@
1
+
2
+ ## 开发规则
3
+
4
+ - `/.imagicma/port.json` 是初始化生成的锁定端口文件,禁止修改。
5
+ - 禁止修改 `scripts/` 下的受保护启动文件:`imagicma-common.mjs`、`imagicma-guard.mjs`、`imagicma-dev.mjs`、`imagicma-start.mjs`。
6
+ - 禁止修改 `package.json` 中 `scripts.dev` 与 `scripts.start`(以及对应 `predev`、`prestart`)命令。
7
+ - 禁止直接执行 `vite` 或 `node dist/server/index.js` 启动项目;只能通过 `pnpm dev` / `pnpm start` 启动。
@@ -0,0 +1,5 @@
1
+ {
2
+ "port": 5001,
3
+ "locked": true,
4
+ "version": 1
5
+ }
@@ -0,0 +1,39 @@
1
+ # 项目 Agent 指南(Hono + Vite + React + Drizzle)
2
+
3
+ 默认使用中文沟通与输出(除非用户明确要求其他语言)。
4
+
5
+ ## 技术栈与约束
6
+
7
+ - 框架:Hono(Node runtime)+ Vite(单进程开发)
8
+ - 前端:React 19 + React Router + Tailwind v4 + shadcn/ui
9
+ - 请求层:React Query + fetch
10
+ - 契约:Zod(`shared/routes.ts`)
11
+ - 数据库:Postgres + Drizzle
12
+
13
+ ## 目录约定
14
+
15
+ - `client/src/`:前端应用入口、页面、Provider、错误边界
16
+ - `server/`:Hono 入口、路由、存储、DB
17
+ - `components/ui/`:shadcn/ui 组件
18
+ - `hooks/`:客户端 hooks
19
+ - `lib/`:通用工具
20
+ - `shared/`:前后端共享 schema/契约
21
+ - `public/`:静态资源(由 Vite/Hono 提供)
22
+
23
+ ## 常用命令
24
+
25
+ - `pnpm dev`:单进程开发(Vite + Hono dev middleware)
26
+ - `pnpm build`:构建前端并编译 server
27
+ - `pnpm start`:生产启动
28
+ - `pnpm check`:类型检查
29
+ - `pnpm lint`:ESLint
30
+ - `pnpm db:push`:同步数据库结构
31
+
32
+ ## 开发规则
33
+
34
+ - 服务端数据库逻辑放在 `server/`,不要泄漏到客户端。
35
+ - API 响应必须经过 `shared/routes.ts` 的 Zod schema 校验。
36
+ - 不提交 `.env.local`、数据库密钥。
37
+ - 优先复用 `components/ui` 与已有 hooks,避免重复造轮子。
38
+
39
+
@@ -0,0 +1,48 @@
1
+ # hono-app
2
+
3
+ 基于 **Hono + Vite + React** 的全栈模板,目标是保留原 `nextjs-app` 的前端体验,同时降低服务端运行开销。
4
+
5
+ ## 技术栈
6
+
7
+ - 前端:React 19、React Router、Tailwind CSS v4、shadcn/ui、React Query
8
+ - 后端:Hono(Node runtime)
9
+ - 数据层:Drizzle ORM + PostgreSQL (`pg`)
10
+ - 校验契约:Zod(`shared/routes.ts`)
11
+
12
+ ## 开发
13
+
14
+ ```bash
15
+ pnpm install
16
+ pnpm dev
17
+ ```
18
+
19
+ 默认端口由 `/.imagicma/port.json` 锁定(模板默认 `5001`)。
20
+ 请勿直接执行 `vite` 启动,必须使用 `pnpm dev`。
21
+
22
+ ## 构建与运行
23
+
24
+ ```bash
25
+ pnpm build
26
+ pnpm start
27
+ ```
28
+
29
+ - `build` 产出:
30
+ - 前端:`dist/client`
31
+ - 后端:`dist/server`
32
+ - `start`:由受保护脚本启动服务(禁止直接 `node dist/server/index.js`),并由 Hono 承载 API + 静态资源 + SPA fallback。
33
+ - 生产端口同样由 `/.imagicma/port.json` 锁定,不接受 `PORT` 覆盖。
34
+
35
+ ## 数据库
36
+
37
+ 1. 复制 `.env.example` 为 `.env.local`
38
+ 2. 填写 `DATABASE_URL`
39
+ 3. 初始化结构:
40
+
41
+ ```bash
42
+ pnpm db:push
43
+ ```
44
+
45
+ ## 验证路径
46
+
47
+ - API:`GET /api/greeting`
48
+ - 页面:`/hello`
@@ -0,0 +1,13 @@
1
+ import { Navigate, Route, Routes } from "react-router-dom";
2
+ import { HelloPage } from "./pages/HelloPage";
3
+ import { HomePage } from "./pages/HomePage";
4
+
5
+ export function App() {
6
+ return (
7
+ <Routes>
8
+ <Route path="/" element={<HomePage />} />
9
+ <Route path="/hello" element={<HelloPage />} />
10
+ <Route path="*" element={<Navigate to="/" replace />} />
11
+ </Routes>
12
+ );
13
+ }
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+
3
+ type AppErrorBoundaryState = {
4
+ error: Error | null;
5
+ };
6
+
7
+ export class AppErrorBoundary extends React.Component<
8
+ { children: React.ReactNode },
9
+ AppErrorBoundaryState
10
+ > {
11
+ state: AppErrorBoundaryState = { error: null };
12
+
13
+ static getDerivedStateFromError(error: Error): AppErrorBoundaryState {
14
+ return { error };
15
+ }
16
+
17
+ componentDidCatch(error: Error) {
18
+ console.error(error);
19
+ }
20
+
21
+ private reset = () => {
22
+ this.setState({ error: null });
23
+ };
24
+
25
+ render() {
26
+ const { error } = this.state;
27
+ if (!error) {
28
+ return this.props.children;
29
+ }
30
+
31
+ const isDev = import.meta.env.DEV;
32
+ const message = isDev ? (error.message || "发生未知错误").trim() : "";
33
+
34
+ return (
35
+ <div
36
+ className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-indigo-600 via-purple-600 to-sky-600 px-6 py-16 text-white"
37
+ role="alert"
38
+ >
39
+ <div className="absolute inset-0 bg-[radial-gradient(60%_60%_at_50%_40%,rgba(255,255,255,0.20),transparent_60%)]" />
40
+ <main className="relative w-full max-w-xl rounded-3xl border border-white/15 bg-white/10 p-10 shadow-2xl backdrop-blur-xl">
41
+ <div className="flex flex-col items-center text-center">
42
+ <h1 className="text-3xl font-semibold tracking-tight">预览暂时不可用</h1>
43
+ <p className="mt-4 max-w-md text-base leading-7 text-white/80">
44
+ 检测到错误。修复后可点击“重试”恢复,或直接刷新页面。
45
+ </p>
46
+
47
+ {isDev && message ? (
48
+ <pre className="mt-6 w-full max-h-40 overflow-auto rounded-2xl border border-white/10 bg-black/25 p-4 text-left text-sm leading-6 text-white/85 shadow-inner">
49
+ <code>{message}</code>
50
+ </pre>
51
+ ) : null}
52
+
53
+ <div className="mt-8 flex w-full flex-col gap-3 sm:flex-row sm:justify-center">
54
+ <button
55
+ type="button"
56
+ className="inline-flex h-11 w-full items-center justify-center rounded-full bg-white px-5 text-sm font-medium text-black transition-colors hover:bg-white/90 sm:w-auto"
57
+ onClick={this.reset}
58
+ >
59
+ 重试
60
+ </button>
61
+ <button
62
+ type="button"
63
+ className="inline-flex h-11 w-full items-center justify-center rounded-full border border-white/20 bg-white/10 px-5 text-sm font-medium text-white transition-colors hover:bg-white/15 sm:w-auto"
64
+ onClick={() => window.location.reload()}
65
+ >
66
+ 刷新页面
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </main>
71
+ </div>
72
+ );
73
+ }
74
+ }