vista-core-js 0.0.2

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 (53) hide show
  1. package/dist/ErrorOverlay.d.ts +7 -0
  2. package/dist/ErrorOverlay.js +68 -0
  3. package/dist/app.d.ts +21 -0
  4. package/dist/app.js +119 -0
  5. package/dist/client/link.d.ts +23 -0
  6. package/dist/client/link.js +42 -0
  7. package/dist/client.d.ts +2 -0
  8. package/dist/client.js +290 -0
  9. package/dist/components/PixelBlast.d.ts +28 -0
  10. package/dist/components/PixelBlast.js +584 -0
  11. package/dist/entry-client.d.ts +1 -0
  12. package/dist/entry-client.js +56 -0
  13. package/dist/entry-server.d.ts +9 -0
  14. package/dist/entry-server.js +33 -0
  15. package/dist/error-overlay.d.ts +1 -0
  16. package/dist/error-overlay.js +166 -0
  17. package/dist/font/google/index.d.ts +1923 -0
  18. package/dist/font/google/index.js +1948 -0
  19. package/dist/image/get-img-props.d.ts +20 -0
  20. package/dist/image/get-img-props.js +46 -0
  21. package/dist/image/image-config.d.ts +20 -0
  22. package/dist/image/image-config.js +17 -0
  23. package/dist/image/image-loader.d.ts +7 -0
  24. package/dist/image/image-loader.js +10 -0
  25. package/dist/image/index.d.ts +12 -0
  26. package/dist/image/index.js +12 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +4 -0
  29. package/dist/plugin.d.ts +5 -0
  30. package/dist/plugin.js +88 -0
  31. package/dist/router.d.ts +18 -0
  32. package/dist/router.js +55 -0
  33. package/package.json +47 -0
  34. package/src/ErrorOverlay.tsx +194 -0
  35. package/src/app.tsx +138 -0
  36. package/src/assets/vista.gif +0 -0
  37. package/src/client/link.tsx +85 -0
  38. package/src/client.tsx +368 -0
  39. package/src/entry-client.tsx +70 -0
  40. package/src/entry-server.tsx +58 -0
  41. package/src/error-overlay.ts +187 -0
  42. package/src/font/google/index.d.ts +19011 -0
  43. package/src/font/google/index.ts +1968 -0
  44. package/src/font/types.d.ts +13 -0
  45. package/src/image/get-img-props.ts +100 -0
  46. package/src/image/image-config.ts +22 -0
  47. package/src/image/image-loader.ts +23 -0
  48. package/src/image/index.tsx +21 -0
  49. package/src/index.ts +7 -0
  50. package/src/plugin.ts +100 -0
  51. package/src/router-loader.ts +51 -0
  52. package/src/router.tsx +80 -0
  53. package/tsconfig.json +23 -0
@@ -0,0 +1,20 @@
1
+ import { ImageConfigComplete } from './image-config';
2
+ import { ImageLoader } from './image-loader';
3
+ export type ImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
4
+ src: string;
5
+ alt: string;
6
+ width?: number | string;
7
+ height?: number | string;
8
+ fill?: boolean;
9
+ loader?: ImageLoader;
10
+ quality?: number | string;
11
+ priority?: boolean;
12
+ unoptimized?: boolean;
13
+ };
14
+ type ImgProps = React.ImgHTMLAttributes<HTMLImageElement> & {
15
+ width?: number;
16
+ height?: number;
17
+ srcSet?: string;
18
+ };
19
+ export declare function getImgProps(props: ImageProps, config: ImageConfigComplete | undefined, defaultLoader: ImageLoader): ImgProps;
20
+ export {};
@@ -0,0 +1,46 @@
1
+ import { imageConfigDefault } from './image-config';
2
+ // Helper: Generate srcSet
3
+ function generateSrcSet(src, width, loader, config, unoptimized) {
4
+ if (unoptimized)
5
+ return undefined;
6
+ const { deviceSizes, imageSizes } = config;
7
+ const sizes = [...deviceSizes, ...imageSizes].sort((a, b) => a - b);
8
+ // If width is known, only generate sizes up to that width (plus maybe 2x/3x for density)
9
+ // For simplicity MVP, we'll generate device sizes.
10
+ return sizes
11
+ .map((size) => {
12
+ const url = loader({ src, width: size });
13
+ return `${url} ${size}w`;
14
+ })
15
+ .join(', ');
16
+ }
17
+ export function getImgProps(props, config = imageConfigDefault, defaultLoader) {
18
+ const { src, alt, width, height, fill, loader = defaultLoader, quality, priority, unoptimized, style, sizes, className, loading, ...rest } = props;
19
+ const imgStyle = { ...style };
20
+ // Handle Fill Mode
21
+ if (fill) {
22
+ imgStyle.position = 'absolute';
23
+ imgStyle.height = '100%';
24
+ imgStyle.width = '100%';
25
+ imgStyle.inset = 0;
26
+ imgStyle.objectFit = 'cover'; // Default to cover for bg images
27
+ }
28
+ // Handle Dimensions
29
+ let widthInt = width ? Number(width) : undefined;
30
+ let heightInt = height ? Number(height) : undefined;
31
+ // Generate SrcSet
32
+ const srcSet = generateSrcSet(src, widthInt, loader, config, !!unoptimized);
33
+ return {
34
+ ...rest,
35
+ src,
36
+ alt,
37
+ width: widthInt,
38
+ height: heightInt,
39
+ loading: priority ? 'eager' : (loading || 'lazy'),
40
+ // fetchPriority: priority ? 'high' : undefined, // React types might strict on this
41
+ style: imgStyle,
42
+ sizes: sizes || (fill ? '100vw' : undefined),
43
+ srcSet,
44
+ className,
45
+ };
46
+ }
@@ -0,0 +1,20 @@
1
+ export declare const imageConfigDefault: {
2
+ deviceSizes: number[];
3
+ imageSizes: number[];
4
+ path: string;
5
+ loader: "default";
6
+ loaderFile: string;
7
+ domains: never[];
8
+ disableStaticImages: boolean;
9
+ minimumCacheTTL: number;
10
+ formats: readonly ["image/webp"];
11
+ dangerouslyAllowSVG: boolean;
12
+ contentSecurityPolicy: string;
13
+ contentDispositionType: "inline";
14
+ remotePatterns: never[];
15
+ unoptimized: boolean;
16
+ };
17
+ export type ImageConfigComplete = typeof imageConfigDefault;
18
+ export type ImageConfig = Partial<ImageConfigComplete>;
19
+ export declare const VALID_LOADERS: readonly ["default", "imgix", "cloudinary", "akamai", "custom"];
20
+ export type LoaderValue = typeof VALID_LOADERS[number];
@@ -0,0 +1,17 @@
1
+ export const imageConfigDefault = {
2
+ deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
3
+ imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
4
+ path: '/_next/image',
5
+ loader: 'default',
6
+ loaderFile: '',
7
+ domains: [],
8
+ disableStaticImages: false,
9
+ minimumCacheTTL: 60,
10
+ formats: ['image/webp'],
11
+ dangerouslyAllowSVG: false,
12
+ contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;",
13
+ contentDispositionType: 'inline',
14
+ remotePatterns: [],
15
+ unoptimized: false,
16
+ };
17
+ export const VALID_LOADERS = ['default', 'imgix', 'cloudinary', 'akamai', 'custom'];
@@ -0,0 +1,7 @@
1
+ export type ImageLoaderProps = {
2
+ src: string;
3
+ width: number;
4
+ quality?: number;
5
+ };
6
+ export type ImageLoader = (p: ImageLoaderProps) => string;
7
+ export declare const defaultLoader: ImageLoader;
@@ -0,0 +1,10 @@
1
+ // Default loader: simple pass-through for now,
2
+ // envisioning a future where this hits an optimization endpoint.
3
+ export const defaultLoader = ({ src, width, quality }) => {
4
+ // If user wants optimization, they would hook up a real loader here.
5
+ // For static dev, we just return the src.
6
+ // In a real Next.js app, this would be: `/_next/image?url=${encodeURIComponent(src)}&w=${width}&q=${quality || 75}`
7
+ // NOTE: Emulating optimizing behavior by appending query params for now (if backend supported it)
8
+ // return `${src}?w=${width}&q=${quality || 75}`;
9
+ return src;
10
+ };
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export declare const Image: React.ForwardRefExoticComponent<React.ImgHTMLAttributes<HTMLImageElement> & {
3
+ src: string;
4
+ alt: string;
5
+ width?: number | string;
6
+ height?: number | string;
7
+ fill?: boolean;
8
+ loader?: import("./image-loader").ImageLoader;
9
+ quality?: number | string;
10
+ priority?: boolean;
11
+ unoptimized?: boolean;
12
+ } & React.RefAttributes<HTMLImageElement>>;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { getImgProps } from './get-img-props';
4
+ import { imageConfigDefault } from './image-config';
5
+ import { defaultLoader } from './image-loader';
6
+ export const Image = forwardRef((props, ref) => {
7
+ // In a real implementation, we might consume config from a Context Provider
8
+ // For now, we use the default config.
9
+ const imgProps = getImgProps(props, imageConfigDefault, defaultLoader);
10
+ return (_jsx("img", { ...imgProps, ref: ref }));
11
+ });
12
+ Image.displayName = 'Image';
@@ -0,0 +1,6 @@
1
+ import { useRouter, Link } from './router.js';
2
+ import { mount, mountError } from './client.js';
3
+ export { Image } from './image/index.js';
4
+ export type { ImageProps } from './image/get-img-props.js';
5
+ export type { ImageLoaderProps } from './image/image-loader.js';
6
+ export { useRouter, mount, mountError, Link };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { useRouter, Link } from './router.js';
2
+ import { mount, mountError } from './client.js';
3
+ export { Image } from './image/index.js';
4
+ export { useRouter, mount, mountError, Link };
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from 'vite';
2
+ export interface VistaPluginOptions {
3
+ logo?: string;
4
+ }
5
+ export declare function vistaPlugin(options?: VistaPluginOptions): Plugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,88 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
4
+ const clientPath = path.resolve(__dirname, './client.tsx'); // Resolves to src/client.tsx
5
+ export function vistaPlugin(options = {}) {
6
+ const logoPath = options.logo || '/favicon.ico';
7
+ return {
8
+ name: 'vista-core',
9
+ enforce: 'pre',
10
+ configureServer(server) {
11
+ if (server.config.appType === 'custom') {
12
+ return; // Skip SPA middleware for SSR
13
+ }
14
+ // Disable default Vite overlay
15
+ server.config.server.hmr = {
16
+ ...(typeof server.config.server.hmr === 'object' ? server.config.server.hmr : {}),
17
+ overlay: false
18
+ };
19
+ server.middlewares.use(async (req, res, next) => {
20
+ const url = req.url || '/';
21
+ const pathname = url.split('?')[0];
22
+ // SPA Fallback logic:
23
+ // Serve index.html if request is:
24
+ // 1. Root (/)
25
+ // 2. Not an internal Vite request (starts with /@)
26
+ // 3. Not a file with an extension (check pathname, ignore query params)
27
+ const isFile = pathname.includes('.');
28
+ const isInternal = url.startsWith('/@');
29
+ if (url === '/' || (!isFile && !isInternal)) {
30
+ const html = `
31
+ <!DOCTYPE html>
32
+ <html lang="en">
33
+ <head>
34
+ <meta charset="UTF-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
36
+ <title>Vista App</title>
37
+ <link rel="icon" href="${logoPath}" />
38
+ </head>
39
+ <body>
40
+ <div id="root"></div>
41
+ <script type="module">
42
+ import { mount, mountError } from '@vista/core';
43
+ console.log('[Vista] Bootstrapping...');
44
+ try {
45
+ // use lazy loading (default) to allow catching syntax errors per module
46
+ // APP Folder scanning: Scan app/**/index.tsx (Pages) and app/**/layout.tsx (Layouts)
47
+ const modules = import.meta.glob(['/app/**/index.tsx', '/app/**/layout.tsx', '/app/layout.tsx', '/app/index.tsx']); console.log('[Vista] Modules discovered:', Object.keys(modules));
48
+ mount(modules);
49
+ } catch (e) {
50
+ console.error('[Vista] Module load failed, calling mountError', e);
51
+ if (typeof mountError === 'function') {
52
+ mountError(e);
53
+ } else {
54
+ console.error('[Vista] mountError is NOT a function! Check exports.', mountError);
55
+ document.body.innerHTML = '<div style="color:red; padding:20px;">CRITICAL: mountError missing. Check console.</div>';
56
+ }
57
+ }
58
+ </script>
59
+ </body>
60
+ </html>
61
+ `;
62
+ // Transform the HTML to handle imports/HMR
63
+ const transformedHtml = await server.transformIndexHtml(url, html);
64
+ res.statusCode = 200;
65
+ res.setHeader('Content-Type', 'text/html');
66
+ res.end(transformedHtml);
67
+ return;
68
+ }
69
+ next();
70
+ });
71
+ },
72
+ resolveId(id) {
73
+ if (id === 'virtual:vista-routes') {
74
+ return '\0virtual:vista-routes';
75
+ }
76
+ },
77
+ load(id) {
78
+ if (id === '\0virtual:vista-routes') {
79
+ return `export const routes = []`;
80
+ }
81
+ },
82
+ transform(code, id) {
83
+ // REMOVED: <head> injection caused Hydration Mismatch because React 18 component tree didn't include it.
84
+ // We will enforce <head> being present in the User's Layout instead.
85
+ return null;
86
+ }
87
+ };
88
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ type RouterContextType = {
3
+ push: (path: string) => void;
4
+ replace: (path: string) => void;
5
+ back: () => void;
6
+ path: string;
7
+ };
8
+ export declare function useRouter(): RouterContextType;
9
+ export declare function RouterProvider({ children, url }: {
10
+ children: React.ReactNode;
11
+ url?: string;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export declare function Link({ href, children, className }: {
14
+ href: string;
15
+ children: React.ReactNode;
16
+ className?: string;
17
+ }): import("react/jsx-runtime").JSX.Element;
18
+ export {};
package/dist/router.js ADDED
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { createContext, useContext, useEffect, useState } from 'react';
3
+ // Create Context
4
+ const RouterContext = createContext(null);
5
+ // Hook
6
+ export function useRouter() {
7
+ const context = useContext(RouterContext);
8
+ if (!context) {
9
+ throw new Error('useRouter must be used within a RouterProvider. This is handled automatically by Vista.');
10
+ }
11
+ return context;
12
+ }
13
+ // Internal Provider
14
+ export function RouterProvider({ children, url }) {
15
+ const [path, setPath] = useState(() => {
16
+ if (url)
17
+ return url;
18
+ if (typeof window !== 'undefined')
19
+ return window.location.pathname;
20
+ return '/';
21
+ });
22
+ useEffect(() => {
23
+ if (typeof window === 'undefined')
24
+ return;
25
+ const onPopState = () => setPath(window.location.pathname);
26
+ window.addEventListener('popstate', onPopState);
27
+ return () => window.removeEventListener('popstate', onPopState);
28
+ }, []);
29
+ // Import startTransition
30
+ const { startTransition } = React;
31
+ const push = (newPath) => {
32
+ window.history.pushState({}, '', newPath);
33
+ setPath(newPath);
34
+ };
35
+ const replace = (newPath) => {
36
+ window.history.replaceState({}, '', newPath);
37
+ setPath(newPath);
38
+ };
39
+ const back = () => {
40
+ window.history.back();
41
+ };
42
+ const value = { push, replace, back, path };
43
+ return (_jsx(RouterContext.Provider, { value: value, children: children }));
44
+ }
45
+ export function Link({ href, children, className }) {
46
+ const { push } = useRouter();
47
+ const handleClick = (e) => {
48
+ // Allow default behavior for modifier keys (new tab)
49
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
50
+ return;
51
+ e.preventDefault();
52
+ push(href);
53
+ };
54
+ return (_jsx("a", { href: href, onClick: handleClick, className: className, children: children }));
55
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "vista-core-js",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "vite": "./src/index.ts",
9
+ "import": "./dist/index.js",
10
+ "require": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./client": "./src/client.tsx",
14
+ "./entry-client": "./src/entry-client.tsx",
15
+ "./entry-server": "./src/entry-server.tsx",
16
+ "./client/link": "./src/client/link.tsx",
17
+ "./font/google": {
18
+ "types": "./src/font/google/index.d.ts",
19
+ "import": "./dist/font/google/index.js",
20
+ "default": "./dist/font/google/index.js"
21
+ },
22
+ "./plugin": "./dist/plugin.js"
23
+ },
24
+ "scripts": {
25
+ "dev": "tsc --watch",
26
+ "build": "tsc"
27
+ },
28
+ "peerDependencies": {
29
+ "vite": "^5.0.0",
30
+ "react": "^18.2.0",
31
+ "react-dom": "^18.2.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.0.0",
35
+ "@types/react": "^18.2.0",
36
+ "@types/react-dom": "^18.2.0",
37
+ "react": "^18.2.0",
38
+ "react-dom": "^18.2.0",
39
+ "typescript": "^5.0.0",
40
+ "vite": "^5.0.0"
41
+ },
42
+ "dependencies": {
43
+ "@types/three": "^0.181.0",
44
+ "postprocessing": "^6.38.0",
45
+ "three": "^0.181.2"
46
+ }
47
+ }
@@ -0,0 +1,194 @@
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+
4
+ interface ErrorOverlayProps {
5
+ error: any;
6
+ onClose?: () => void;
7
+ }
8
+
9
+ const VSCodeColors = {
10
+ bg: '#1e1e1e',
11
+ sidebar: '#252526',
12
+ activityBar: '#333333',
13
+ statusBar: '#007acc',
14
+ titleBar: '#3c3c3c',
15
+ text: '#d4d4d4',
16
+ red: '#f14c4c',
17
+ yellow: '#cca700',
18
+ green: '#238636',
19
+ terminalBg: '#1e1e1e',
20
+ editorBg: '#1e1e1e',
21
+ lineNo: '#858585',
22
+ };
23
+
24
+ export const ErrorOverlay: React.FC<ErrorOverlayProps> = ({ error, onClose }) => {
25
+ if (!error) return null;
26
+
27
+ const isBuildError = error.type === 'error' || error.plugin; // Vite error shape
28
+ const message = error.message || 'Unknown Error';
29
+ const file = error.id || error.file || error.filename || 'Unknown File';
30
+ const frame = error.frame || '';
31
+ const stack = error.stack || '';
32
+
33
+ // Parse location if available
34
+ const loc = error.loc ? `${error.loc.line}:${error.loc.column}` : '';
35
+
36
+ return (
37
+ <div
38
+ style={{
39
+ position: 'fixed',
40
+ top: 0,
41
+ left: 0,
42
+ width: '100vw',
43
+ height: '100vh',
44
+ zIndex: 99999,
45
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
46
+ backdropFilter: 'blur(8px)',
47
+ display: 'flex',
48
+ alignItems: 'center',
49
+ justifyContent: 'center',
50
+ fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
51
+ }}
52
+ >
53
+ <div
54
+ style={{
55
+ width: '85vw',
56
+ maxWidth: '1000px',
57
+ height: '80vh',
58
+ backgroundColor: VSCodeColors.bg,
59
+ borderRadius: '10px',
60
+ boxShadow: '0 20px 50px rgba(0,0,0,0.5)',
61
+ overflow: 'hidden',
62
+ display: 'flex',
63
+ flexDirection: 'column',
64
+ border: '1px solid #454545',
65
+ animation: 'vista-slide-up 0.3s ease-out',
66
+ }}
67
+ >
68
+ {/* Title Bar */}
69
+ <div
70
+ style={{
71
+ height: '35px',
72
+ backgroundColor: VSCodeColors.titleBar,
73
+ display: 'flex',
74
+ alignItems: 'center',
75
+ padding: '0 15px',
76
+ borderBottom: '1px solid #333',
77
+ }}
78
+ >
79
+ <div style={{ display: 'flex', gap: '8px', marginRight: '20px' }}>
80
+ <div onClick={onClose} style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#ff5f56', cursor: 'pointer' }}></div>
81
+ <div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#ffbd2e' }}></div>
82
+ <div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#27c93f' }}></div>
83
+ </div>
84
+ <div style={{ color: '#ccc', fontSize: '13px', flex: 1, textAlign: 'center', opacity: 0.8 }}>
85
+ {file.split('/').pop()} {loc ? `— ${loc}` : ''}
86
+ </div>
87
+ </div>
88
+
89
+ <div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
90
+ {/* Activity Bar (Left Strip) */}
91
+ <div style={{ width: '50px', backgroundColor: VSCodeColors.activityBar, display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: '15px' }}>
92
+ <div style={{ marginBottom: '20px', opacity: 0.6, cursor: 'pointer' }} title="Explorer">
93
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#d4d4d4" strokeWidth="1.5"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
94
+ </div>
95
+ <div style={{ marginBottom: '20px', opacity: 0.6, cursor: 'pointer' }} title="Search">
96
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#d4d4d4" strokeWidth="1.5"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
97
+ </div>
98
+ <div style={{ marginBottom: '20px', opacity: 1, borderLeft: '2px solid white', paddingLeft: '11px', cursor: 'pointer' }} title="Run and Debug">
99
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#d4d4d4" strokeWidth="1.5"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
100
+ </div>
101
+ </div>
102
+
103
+ {/* Sidebar (File Explorer Simulation) */}
104
+ <div className="vista-sidebar" style={{ width: '200px', backgroundColor: VSCodeColors.sidebar, fontSize: '13px', color: VSCodeColors.text, padding: '10px', borderRight: '1px solid #333' }}>
105
+ <div style={{ fontSize: '11px', fontWeight: 'bold', marginBottom: '10px', opacity: 0.7, textTransform: 'uppercase' }}>Open Editors</div>
106
+ <div style={{ padding: '5px 10px', backgroundColor: '#37373d', borderRadius: '3px', cursor: 'pointer', display: 'flex', alignItems: 'center' }}>
107
+ <span style={{ marginRight: '8px', display: 'flex', alignItems: 'center' }}>
108
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#61dafb" strokeWidth="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
109
+ </span>
110
+ {file.split('/').pop()}
111
+ </div>
112
+ </div>
113
+
114
+ {/* Main Content Area */}
115
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', backgroundColor: VSCodeColors.editorBg }}>
116
+
117
+ {/* Editor Tabs */}
118
+ <div style={{ height: '35px', backgroundColor: VSCodeColors.bg, display: 'flex', alignItems: 'center', borderBottom: '1px solid #333' }}>
119
+ <div style={{ padding: '0 15px', height: '100%', backgroundColor: '#1e1e1e', borderTop: '2px solid #e11c1c', color: '#fff', fontSize: '13px', display: 'flex', alignItems: 'center', gap: '8px' }}>
120
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#61dafb" strokeWidth="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
121
+ {file.split('/').pop()}
122
+ <span style={{ marginLeft: '10px', cursor: 'pointer', opacity: 0.7 }}>
123
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
124
+ </span>
125
+ </div>
126
+ </div>
127
+
128
+ {/* Editor Pane (Code Frame or Stack Fallback) */}
129
+ <div style={{ flex: 2, padding: '20px', overflow: 'auto', fontFamily: "'Fira Code', 'Consolas', monospace", fontSize: '14px', lineHeight: '1.5', color: '#d4d4d4' }}>
130
+ {/* Explicitly show message if it's a build error to ensure context is seen */}
131
+ {isBuildError && (
132
+ <div style={{ color: '#f14c4c', marginBottom: '15px', fontWeight: 'bold' }}>
133
+ {message}
134
+ </div>
135
+ )}
136
+
137
+ {frame ? (
138
+ <pre style={{ margin: 0, whiteSpace: 'pre-wrap', fontFamily: 'inherit' }}>{frame}</pre>
139
+ ) : (
140
+ <div style={{ color: '#d4d4d4' }}>
141
+ <div style={{ color: '#666', marginBottom: '20px' }}>// Source code not available for runtime execution error.</div>
142
+ <pre style={{ margin: 0, color: '#9cdcfe', whiteSpace: 'pre-wrap' }}>{stack}</pre>
143
+ </div>
144
+ )}
145
+ </div>
146
+
147
+ {/* Terminal Pane */}
148
+ <div style={{ flex: 1, backgroundColor: '#1e1e1e', borderTop: '1px solid #444', display: 'flex', flexDirection: 'column' }}>
149
+ <div style={{ display: 'flex', padding: '5px 15px', borderBottom: '1px solid #333', fontSize: '11px', color: '#ccc', gap: '15px' }}>
150
+ <span style={{ textDecoration: 'underline', cursor: 'pointer' }}>PROBLEMS</span>
151
+ <span style={{ opacity: 0.5, cursor: 'pointer' }}>OUTPUT</span>
152
+ <span style={{ opacity: 0.5, cursor: 'pointer' }}>DEBUG CONSOLE</span>
153
+ <span style={{ opacity: 0.5, cursor: 'pointer' }}>TERMINAL</span>
154
+ </div>
155
+ <div style={{ padding: '15px', overflow: 'auto', fontFamily: 'monospace', fontSize: '13px', color: '#f14c4c' }}>
156
+ <div style={{ color: '#d4d4d4', marginBottom: '5px' }}>$ npm run dev</div>
157
+ <div>&gt; Error: {message}</div>
158
+ </div>
159
+ </div>
160
+
161
+ </div>
162
+ </div>
163
+
164
+ {/* Status Bar */}
165
+ <div style={{ height: '22px', backgroundColor: '#e11c1c', display: 'flex', alignItems: 'center', padding: '0 10px', fontSize: '12px', color: 'white', justifyContent: 'space-between' }}>
166
+ <div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
167
+ <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
168
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
169
+ 1 Error
170
+ </span>
171
+ <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
172
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
173
+ 0 Warnings
174
+ </span>
175
+ </div>
176
+ <div>
177
+ Ln {error.loc?.line || 0}, Col {error.loc?.column || 0}
178
+ </div>
179
+ </div>
180
+
181
+ </div>
182
+ <style>{`
183
+ .vista-sidebar { display: none; }
184
+ @media (min-width: 768px) {
185
+ .vista-sidebar { display: block; }
186
+ }
187
+ @keyframes vista-slide-up {
188
+ from { opacity: 0; transform: translateY(20px) scale(0.95); }
189
+ to { opacity: 1; transform: translateY(0) scale(1); }
190
+ }
191
+ `}</style>
192
+ </div>
193
+ );
194
+ };