react-scanner-ui 0.0.9 → 0.0.11

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.
@@ -0,0 +1,14 @@
1
+ # Usage
2
+
3
+ ## Basic Usage
4
+
5
+ Run the scanner in your React project directory:
6
+
7
+ ```bash
8
+ react-scanner-ui
9
+ ```
10
+
11
+ ## Options
12
+
13
+ - `--port`: Specify the port for the UI server
14
+ - `--help`: Show available options
package/docs/index.md ADDED
@@ -0,0 +1,23 @@
1
+ ---
2
+ layout: home
3
+
4
+ hero:
5
+ name: 'React Scanner UI'
6
+ text: 'Component Scanner & Visualization Tool'
7
+ tagline: 'Scan, analyze, and visualize your React components with ease'
8
+ actions:
9
+ - theme: brand
10
+ text: Get Started
11
+ link: /guide/
12
+ - theme: alt
13
+ text: View on GitHub
14
+ link: https://github.com/vimalmunjani/react-scanner-ui
15
+
16
+ features:
17
+ - title: Component Scanning
18
+ details: Automatically scan and analyze React components in your project
19
+ - title: Visual Interface
20
+ details: Interactive UI for exploring component structure and relationships
21
+ - title: Easy Integration
22
+ details: Simple CLI tool that works with any React project
23
+ ---
@@ -0,0 +1,90 @@
1
+ import js from '@eslint/js';
2
+ import typescript from '@typescript-eslint/eslint-plugin';
3
+ import typescriptParser from '@typescript-eslint/parser';
4
+ import react from 'eslint-plugin-react';
5
+ import reactHooks from 'eslint-plugin-react-hooks';
6
+ import reactRefresh from 'eslint-plugin-react-refresh';
7
+ import prettier from 'eslint-plugin-prettier';
8
+ import prettierConfig from 'eslint-config-prettier';
9
+
10
+ export default [
11
+ js.configs.recommended,
12
+ {
13
+ files: ['**/*.{js,jsx,ts,tsx}'],
14
+ languageOptions: {
15
+ ecmaVersion: 'latest',
16
+ sourceType: 'module',
17
+ parser: typescriptParser,
18
+ parserOptions: {
19
+ ecmaFeatures: {
20
+ jsx: true,
21
+ },
22
+ },
23
+ globals: {
24
+ console: 'readonly',
25
+ process: 'readonly',
26
+ Buffer: 'readonly',
27
+ __dirname: 'readonly',
28
+ __filename: 'readonly',
29
+ module: 'readonly',
30
+ require: 'readonly',
31
+ exports: 'readonly',
32
+ global: 'readonly',
33
+ window: 'readonly',
34
+ document: 'readonly',
35
+ navigator: 'readonly',
36
+ setTimeout: 'readonly',
37
+ clearTimeout: 'readonly',
38
+ setInterval: 'readonly',
39
+ clearInterval: 'readonly',
40
+ NodeJS: 'readonly',
41
+ fetch: 'readonly',
42
+ },
43
+ },
44
+ plugins: {
45
+ '@typescript-eslint': typescript,
46
+ react: react,
47
+ 'react-hooks': reactHooks,
48
+ 'react-refresh': reactRefresh,
49
+ prettier: prettier,
50
+ },
51
+ rules: {
52
+ ...typescript.configs.recommended.rules,
53
+ ...react.configs.recommended.rules,
54
+ ...reactHooks.configs.recommended.rules,
55
+ ...prettierConfig.rules,
56
+ 'prettier/prettier': 'error',
57
+ 'react/react-in-jsx-scope': 'off',
58
+ 'react/prop-types': 'off',
59
+ '@typescript-eslint/no-unused-vars': [
60
+ 'error',
61
+ { argsIgnorePattern: '^_' },
62
+ ],
63
+ '@typescript-eslint/explicit-function-return-type': 'off',
64
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
65
+ '@typescript-eslint/no-explicit-any': 'warn',
66
+ 'react-hooks/rules-of-hooks': 'error',
67
+ 'react-hooks/exhaustive-deps': 'warn',
68
+ 'react-refresh/only-export-components': [
69
+ 'warn',
70
+ { allowConstantExport: true },
71
+ ],
72
+ },
73
+ settings: {
74
+ react: {
75
+ version: 'detect',
76
+ },
77
+ },
78
+ },
79
+ {
80
+ ignores: [
81
+ 'dist/',
82
+ 'node_modules/',
83
+ 'build/',
84
+ '*.min.js',
85
+ '*.min.css',
86
+ 'docs/.vitepress/dist/',
87
+ 'docs/.vitepress/cache/',
88
+ ],
89
+ },
90
+ ];
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "react-scanner-ui",
3
- "version": "0.0.9",
3
+ "type": "module",
4
+ "version": "0.0.11",
4
5
  "main": "dist/index.js",
5
6
  "bin": {
6
7
  "react-scanner-ui": "dist/index.js"
@@ -17,11 +18,19 @@
17
18
  "scripts": {
18
19
  "build": "tsc",
19
20
  "build:ui": "vite build --config ui/vite.config.ts",
21
+ "build:docs": "vitepress build docs",
20
22
  "start": "node dist/index.js",
21
23
  "dev": "ts-node src/index.ts",
22
24
  "dev:ui": "vite --config ui/vite.config.ts",
25
+ "dev:docs": "vitepress dev docs",
26
+ "preview:docs": "vitepress preview docs",
23
27
  "prepublishOnly": "yarn build",
24
- "publish:npm": "yarn build && npm publish"
28
+ "publish:npm": "yarn build && npm publish",
29
+ "prepare": "husky",
30
+ "lint": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
31
+ "lint:check": "eslint . --ext .ts,.tsx,.js,.jsx",
32
+ "format": "prettier --write .",
33
+ "format:check": "prettier --check ."
25
34
  },
26
35
  "dependencies": {
27
36
  "@vitejs/plugin-react": "^4.3.4",
@@ -36,7 +45,28 @@
36
45
  "@types/polka": "^0.5.7",
37
46
  "@types/react": "^18.3.18",
38
47
  "@types/react-dom": "^18.3.5",
48
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
49
+ "@typescript-eslint/parser": "^8.52.0",
50
+ "eslint": "^9.39.2",
51
+ "eslint-config-prettier": "^10.1.8",
52
+ "eslint-plugin-prettier": "^5.5.4",
53
+ "eslint-plugin-react": "^7.37.5",
54
+ "eslint-plugin-react-hooks": "^7.0.1",
55
+ "eslint-plugin-react-refresh": "^0.4.26",
56
+ "husky": "^9.1.7",
57
+ "lint-staged": "^16.2.7",
58
+ "prettier": "^3.7.4",
39
59
  "ts-node": "^10.9.2",
40
- "typescript": "^5.9.3"
60
+ "typescript": "^5.9.3",
61
+ "vitepress": "^1.6.4"
62
+ },
63
+ "lint-staged": {
64
+ "*.{ts,tsx,js,jsx}": [
65
+ "eslint --fix",
66
+ "prettier --write"
67
+ ],
68
+ "*.{json,md,css,scss,yaml,yml}": [
69
+ "prettier --write"
70
+ ]
41
71
  }
42
72
  }
@@ -1,6 +1,6 @@
1
- import { useState, useEffect } from "react";
2
- import { ComponentTable } from "./ComponentTable";
3
- import "./App.css";
1
+ import { useState, useEffect } from 'react';
2
+ import { ComponentTable } from './ComponentTable';
3
+ import './App.css';
4
4
 
5
5
  export interface ScanData {
6
6
  [componentName: string]: {
@@ -20,8 +20,8 @@ function App() {
20
20
  const [loading, setLoading] = useState(true);
21
21
 
22
22
  useEffect(() => {
23
- fetch("/api/scan-data")
24
- .then((res) => res.json())
23
+ fetch('/api/scan-data')
24
+ .then(res => res.json())
25
25
  .then((result: ApiResponse) => {
26
26
  if (result.error) {
27
27
  setError(result.error);
@@ -30,20 +30,20 @@ function App() {
30
30
  }
31
31
  setLoading(false);
32
32
  })
33
- .catch((err) => {
34
- setError("Failed to fetch scan data: " + err.message);
33
+ .catch(err => {
34
+ setError('Failed to fetch scan data: ' + err.message);
35
35
  setLoading(false);
36
36
  });
37
37
  }, []);
38
38
 
39
39
  return (
40
- <div className="container">
40
+ <div className='container'>
41
41
  <h1>React Scanner UI</h1>
42
- {loading && <div className="loading">Loading scan data...</div>}
43
- {error && <div className="error">{error}</div>}
42
+ {loading && <div className='loading'>Loading scan data...</div>}
43
+ {error && <div className='error'>{error}</div>}
44
44
  {!loading && !error && data && <ComponentTable data={data} />}
45
45
  {!loading && !error && !data && (
46
- <div className="error">No component data found.</div>
46
+ <div className='error'>No component data found.</div>
47
47
  )}
48
48
  </div>
49
49
  );
@@ -1,149 +1,149 @@
1
1
  .component-table {
2
- width: 100%;
3
- border-collapse: collapse;
4
- background-color: var(--color-bg-card);
5
- box-shadow: var(--shadow-sm);
6
- border-radius: var(--radius-md);
7
- overflow: hidden;
2
+ width: 100%;
3
+ border-collapse: collapse;
4
+ background-color: var(--color-bg-card);
5
+ box-shadow: var(--shadow-sm);
6
+ border-radius: var(--radius-md);
7
+ overflow: hidden;
8
8
  }
9
9
 
10
10
  .component-table th,
11
11
  .component-table td {
12
- padding: 12px 16px;
13
- text-align: left;
14
- border-bottom: 1px solid var(--color-border);
12
+ padding: 12px 16px;
13
+ text-align: left;
14
+ border-bottom: 1px solid var(--color-border);
15
15
  }
16
16
 
17
17
  .component-table th {
18
- background-color: var(--color-bg-header);
19
- font-weight: 600;
20
- color: var(--color-text);
21
- cursor: pointer;
22
- user-select: none;
23
- transition: background-color 0.15s ease;
18
+ background-color: var(--color-bg-header);
19
+ font-weight: 600;
20
+ color: var(--color-text);
21
+ cursor: pointer;
22
+ user-select: none;
23
+ transition: background-color 0.15s ease;
24
24
  }
25
25
 
26
26
  .component-table th:hover {
27
- background-color: #e9ecef;
27
+ background-color: #e9ecef;
28
28
  }
29
29
 
30
30
  .component-table th .sort-indicator {
31
- margin-left: 5px;
32
- color: var(--color-text-muted);
33
- font-size: 0.75em;
31
+ margin-left: 5px;
32
+ color: var(--color-text-muted);
33
+ font-size: 0.75em;
34
34
  }
35
35
 
36
36
  .component-table tbody tr:last-child td {
37
- border-bottom: none;
37
+ border-bottom: none;
38
38
  }
39
39
 
40
40
  .component-table tbody tr:hover td {
41
- background-color: var(--color-bg-hover);
41
+ background-color: var(--color-bg-hover);
42
42
  }
43
43
 
44
44
  .component-table .count {
45
- font-family: var(--font-mono);
46
- font-weight: 500;
45
+ font-family: var(--font-mono);
46
+ font-weight: 500;
47
47
  }
48
48
 
49
49
  .component-table .total-row {
50
- font-weight: 600;
51
- background-color: var(--color-bg-total);
50
+ font-weight: 600;
51
+ background-color: var(--color-bg-total);
52
52
  }
53
53
 
54
54
  .component-table .total-row td {
55
- border-top: 2px solid var(--color-border-strong);
56
- border-bottom: none;
55
+ border-top: 2px solid var(--color-border-strong);
56
+ border-bottom: none;
57
57
  }
58
58
 
59
59
  .component-table .total-row:hover td {
60
- background-color: var(--color-bg-total);
60
+ background-color: var(--color-bg-total);
61
61
  }
62
62
 
63
63
  /* Expandable rows */
64
64
  .clickable-row {
65
- cursor: pointer;
66
- transition: background-color 0.15s ease;
65
+ cursor: pointer;
66
+ transition: background-color 0.15s ease;
67
67
  }
68
68
 
69
69
  .clickable-row.has-props:hover td {
70
- background-color: var(--color-bg-hover);
70
+ background-color: var(--color-bg-hover);
71
71
  }
72
72
 
73
73
  .clickable-row.expanded td {
74
- background-color: var(--color-bg-hover);
75
- border-bottom: none;
74
+ background-color: var(--color-bg-hover);
75
+ border-bottom: none;
76
76
  }
77
77
 
78
78
  .expand-icon {
79
- display: inline-block;
80
- width: 16px;
81
- margin-right: 8px;
82
- font-size: 0.7em;
83
- color: var(--color-text-muted);
84
- transition: transform 0.2s ease;
79
+ display: inline-block;
80
+ width: 16px;
81
+ margin-right: 8px;
82
+ font-size: 0.7em;
83
+ color: var(--color-text-muted);
84
+ transition: transform 0.2s ease;
85
85
  }
86
86
 
87
87
  .expand-icon.expanded {
88
- transform: rotate(90deg);
88
+ transform: rotate(90deg);
89
89
  }
90
90
 
91
91
  /* Props row */
92
92
  .props-row td {
93
- padding: 8px 16px 16px 16px;
94
- background-color: var(--color-bg-hover);
93
+ padding: 8px 16px 16px 16px;
94
+ background-color: var(--color-bg-hover);
95
95
  }
96
96
 
97
97
  .props-container {
98
- display: flex;
99
- align-items: flex-start;
100
- gap: 12px;
98
+ display: flex;
99
+ align-items: flex-start;
100
+ gap: 12px;
101
101
  }
102
102
 
103
103
  .props-label {
104
- font-size: 0.85em;
105
- font-weight: 600;
106
- color: var(--color-text-muted);
107
- padding-top: 4px;
104
+ font-size: 0.85em;
105
+ font-weight: 600;
106
+ color: var(--color-text-muted);
107
+ padding-top: 4px;
108
108
  }
109
109
 
110
110
  .props-chips {
111
- display: flex;
112
- flex-wrap: wrap;
113
- gap: 8px;
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ gap: 8px;
114
114
  }
115
115
 
116
116
  .prop-chip {
117
- display: inline-flex;
118
- align-items: center;
119
- gap: 6px;
120
- padding: 4px 10px;
121
- background-color: var(--color-bg-card);
122
- border: 1px solid var(--color-border);
123
- border-radius: 16px;
124
- font-size: 0.85em;
125
- color: var(--color-text);
126
- transition:
127
- background-color 0.15s ease,
128
- border-color 0.15s ease;
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 6px;
120
+ padding: 4px 10px;
121
+ background-color: var(--color-bg-card);
122
+ border: 1px solid var(--color-border);
123
+ border-radius: 16px;
124
+ font-size: 0.85em;
125
+ color: var(--color-text);
126
+ transition:
127
+ background-color 0.15s ease,
128
+ border-color 0.15s ease;
129
129
  }
130
130
 
131
131
  .prop-chip:hover {
132
- background-color: var(--color-bg-header);
133
- border-color: var(--color-border-strong);
132
+ background-color: var(--color-bg-header);
133
+ border-color: var(--color-border-strong);
134
134
  }
135
135
 
136
136
  .prop-count {
137
- display: inline-flex;
138
- align-items: center;
139
- justify-content: center;
140
- min-width: 20px;
141
- height: 20px;
142
- padding: 0 6px;
143
- background-color: var(--color-primary, #4a90d9);
144
- color: white;
145
- border-radius: 10px;
146
- font-size: 0.8em;
147
- font-weight: 600;
148
- font-family: var(--font-mono);
137
+ display: inline-flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ min-width: 20px;
141
+ height: 20px;
142
+ padding: 0 6px;
143
+ background-color: var(--color-primary, #4a90d9);
144
+ color: white;
145
+ border-radius: 10px;
146
+ font-size: 0.8em;
147
+ font-weight: 600;
148
+ font-family: var(--font-mono);
149
149
  }
@@ -1,13 +1,13 @@
1
- import { useState } from "react";
2
- import type { ScanData } from "./App";
3
- import "./ComponentTable.css";
1
+ import { useState } from 'react';
2
+ import type { ScanData } from './App';
3
+ import './ComponentTable.css';
4
4
 
5
5
  interface ComponentTableProps {
6
6
  data: ScanData;
7
7
  }
8
8
 
9
- type SortKey = "name" | "count";
10
- type SortDirection = "asc" | "desc";
9
+ type SortKey = 'name' | 'count';
10
+ type SortDirection = 'asc' | 'desc';
11
11
 
12
12
  interface SortConfig {
13
13
  key: SortKey;
@@ -16,25 +16,25 @@ interface SortConfig {
16
16
 
17
17
  export function ComponentTable({ data }: ComponentTableProps) {
18
18
  const [sortConfig, setSortConfig] = useState<SortConfig>({
19
- key: "count",
20
- direction: "desc",
19
+ key: 'count',
20
+ direction: 'desc',
21
21
  });
22
22
  const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
23
23
 
24
24
  const handleSort = (key: SortKey) => {
25
- setSortConfig((prev) => ({
25
+ setSortConfig(prev => ({
26
26
  key,
27
- direction: prev.key === key && prev.direction === "asc" ? "desc" : "asc",
27
+ direction: prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc',
28
28
  }));
29
29
  };
30
30
 
31
31
  const getSortIndicator = (key: SortKey) => {
32
- if (sortConfig.key !== key) return "";
33
- return sortConfig.direction === "asc" ? "" : "";
32
+ if (sortConfig.key !== key) return '';
33
+ return sortConfig.direction === 'asc' ? '' : '';
34
34
  };
35
35
 
36
36
  const toggleRow = (name: string) => {
37
- setExpandedRows((prev) => {
37
+ setExpandedRows(prev => {
38
38
  const next = new Set(prev);
39
39
  if (next.has(name)) {
40
40
  next.delete(name);
@@ -53,43 +53,43 @@ export function ComponentTable({ data }: ComponentTableProps) {
53
53
  }));
54
54
 
55
55
  components.sort((a, b) => {
56
- const aValue = sortConfig.key === "name" ? a.name.toLowerCase() : a.count;
57
- const bValue = sortConfig.key === "name" ? b.name.toLowerCase() : b.count;
56
+ const aValue = sortConfig.key === 'name' ? a.name.toLowerCase() : a.count;
57
+ const bValue = sortConfig.key === 'name' ? b.name.toLowerCase() : b.count;
58
58
 
59
- if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
60
- if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
59
+ if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1;
60
+ if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1;
61
61
  return 0;
62
62
  });
63
63
 
64
64
  const totalCount = components.reduce((sum, c) => sum + c.count, 0);
65
65
 
66
66
  if (components.length === 0) {
67
- return <div className="empty-state">No components found in scan data.</div>;
67
+ return <div className='empty-state'>No components found in scan data.</div>;
68
68
  }
69
69
 
70
70
  return (
71
- <div className="table-container">
72
- <table className="component-table">
71
+ <div className='table-container'>
72
+ <table className='component-table'>
73
73
  <thead>
74
74
  <tr>
75
- <th onClick={() => handleSort("name")}>
75
+ <th onClick={() => handleSort('name')}>
76
76
  Component
77
- <span className="sort-indicator">{getSortIndicator("name")}</span>
77
+ <span className='sort-indicator'>{getSortIndicator('name')}</span>
78
78
  </th>
79
- <th onClick={() => handleSort("count")}>
79
+ <th onClick={() => handleSort('count')}>
80
80
  Usage Count
81
- <span className="sort-indicator">
82
- {getSortIndicator("count")}
81
+ <span className='sort-indicator'>
82
+ {getSortIndicator('count')}
83
83
  </span>
84
84
  </th>
85
85
  </tr>
86
86
  </thead>
87
87
  <tbody>
88
- {components.map((component) => {
88
+ {components.map(component => {
89
89
  const isExpanded = expandedRows.has(component.name);
90
90
  const hasProps = Object.keys(component.props).length > 0;
91
91
  const sortedProps = Object.entries(component.props).sort(
92
- ([, a], [, b]) => b - a,
92
+ ([, a], [, b]) => b - a
93
93
  );
94
94
 
95
95
  return (
@@ -97,28 +97,28 @@ export function ComponentTable({ data }: ComponentTableProps) {
97
97
  <tr
98
98
  key={component.name}
99
99
  onClick={() => toggleRow(component.name)}
100
- className={`clickable-row ${isExpanded ? "expanded" : ""} ${hasProps ? "has-props" : ""}`}
100
+ className={`clickable-row ${isExpanded ? 'expanded' : ''} ${hasProps ? 'has-props' : ''}`}
101
101
  >
102
- <td className="component-name">
102
+ <td className='component-name'>
103
103
  <span
104
- className={`expand-icon ${isExpanded ? "expanded" : ""}`}
104
+ className={`expand-icon ${isExpanded ? 'expanded' : ''}`}
105
105
  >
106
- {hasProps ? "" : ""}
106
+ {hasProps ? '' : ''}
107
107
  </span>
108
108
  {component.name}
109
109
  </td>
110
- <td className="count">{component.count}</td>
110
+ <td className='count'>{component.count}</td>
111
111
  </tr>
112
112
  {isExpanded && hasProps && (
113
- <tr key={`${component.name}-props`} className="props-row">
113
+ <tr key={`${component.name}-props`} className='props-row'>
114
114
  <td colSpan={2}>
115
- <div className="props-container">
116
- <span className="props-label">Props:</span>
117
- <div className="props-chips">
115
+ <div className='props-container'>
116
+ <span className='props-label'>Props:</span>
117
+ <div className='props-chips'>
118
118
  {sortedProps.map(([propName, propCount]) => (
119
- <span key={propName} className="prop-chip">
119
+ <span key={propName} className='prop-chip'>
120
120
  {propName}
121
- <span className="prop-count">{propCount}</span>
121
+ <span className='prop-count'>{propCount}</span>
122
122
  </span>
123
123
  ))}
124
124
  </div>
@@ -129,9 +129,9 @@ export function ComponentTable({ data }: ComponentTableProps) {
129
129
  </>
130
130
  );
131
131
  })}
132
- <tr className="total-row">
132
+ <tr className='total-row'>
133
133
  <td>Total</td>
134
- <td className="count">{totalCount}</td>
134
+ <td className='count'>{totalCount}</td>
135
135
  </tr>
136
136
  </tbody>
137
137
  </table>
package/ui/index.css CHANGED
@@ -23,7 +23,9 @@
23
23
  --radius-sm: 4px;
24
24
  --radius-md: 8px;
25
25
  --font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
26
- --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
26
+ --font-sans:
27
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
28
+ sans-serif;
27
29
  }
28
30
 
29
31
  body {
@@ -54,6 +56,8 @@ button {
54
56
  cursor: pointer;
55
57
  }
56
58
 
57
- input, select, textarea {
59
+ input,
60
+ select,
61
+ textarea {
58
62
  font-family: inherit;
59
63
  }
package/ui/index.html CHANGED
@@ -1,12 +1,12 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>React Scanner UI</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="./main.tsx"></script>
11
- </body>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>React Scanner UI</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
12
  </html>