minwebide demo app: deploys to GitHub Pages
Consumes minwebide as a sibling checkout (file:../minwebide); the deploy
workflow checks out magland/minwebide next to this repo, installs both,
builds with the /minwebide-demo/ base path, and publishes dist/ to Pages.
15 changed files+2041−0
.github/workflows/deploy.ymladded+79−0View file
@@ -0,0 +1,79 @@
1+name: Deploy to GitHub Pages
2+
3+on:
4+ push:
5+ branches: [main]
6+ workflow_dispatch:
7+
8+permissions:
9+ contents: read
10+ pages: write
11+ id-token: write
12+
13+concurrency:
14+ group: pages
15+ cancel-in-progress: false
16+
17+jobs:
18+ build:
19+ runs-on: ubuntu-latest
20+ steps:
21+ - name: Checkout minwebide-demo
22+ uses: actions/checkout@v4
23+ with:
24+ path: minwebide-demo
25+
26+ # minwebide is consumed as a sibling checkout (file:../minwebide)
27+ - name: Checkout minwebide
28+ uses: actions/checkout@v4
29+ with:
30+ repository: magland/minwebide
31+ path: minwebide
32+
33+ - uses: actions/setup-node@v4
34+ with:
35+ node-version: 22
36+
37+ # the pinned VS Code source checkout is large; cache it by pinned version
38+ - name: Cache VS Code source
39+ uses: actions/cache@v4
40+ with:
41+ path: minwebide/vendor/vscode
42+ key: vscode-vendor-${{ hashFiles('minwebide/.vscode-version') }}
43+
44+ - name: Install minwebide (fetches VS Code source on postinstall)
45+ working-directory: minwebide
46+ env:
47+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
48+ run: npm ci
49+
50+ - name: Install minwebide-demo
51+ working-directory: minwebide-demo
52+ run: npm ci
53+
54+ - name: Build
55+ working-directory: minwebide-demo
56+ env:
57+ DEPLOY_BASE: /minwebide-demo/
58+ run: npm run build
59+
60+ - name: Setup Pages
61+ uses: actions/configure-pages@v5
62+ with:
63+ enablement: true
64+
65+ - name: Upload artifact
66+ uses: actions/upload-pages-artifact@v3
67+ with:
68+ path: minwebide-demo/dist
69+
70+ deploy:
71+ needs: build
72+ runs-on: ubuntu-latest
73+ environment:
74+ name: github-pages
75+ url: ${{ steps.deployment.outputs.page_url }}
76+ steps:
77+ - name: Deploy to GitHub Pages
78+ id: deployment
79+ uses: actions/deploy-pages@v4
.gitignoreadded+2−0View file
@@ -0,0 +1,2 @@
1+node_modules/
2+dist/
README.mdadded+30−0View file
@@ -0,0 +1,30 @@
1+# minwebide-demo
2+
3+Live demo of [minwebide](https://github.com/magland/minwebide) — a
4+minimalistic web IDE built from VS Code's own source code, with the file
5+system persisted in your browser's IndexedDB.
6+
7+**Live site:** https://concept-collection.github.io/minwebide-demo/
8+
9+Things to try:
10+
11+- Open files from the Explorer; edit and press **Ctrl+S**; reload — changes persist.
12+- `data/measurements.csv` opens as a table; `README.md` has a markdown preview
13+ (icons in the tab bar switch representations).
14+- Open `scripts/sine-wave.js` and press **▶**: console output goes to the
15+ bottom Output panel, plots render in the secondary side bar.
16+- Search across files from the activity bar.
17+
18+## Development
19+
20+minwebide is consumed as a sibling checkout (`file:../minwebide`):
21+
22+```sh
23+git clone https://github.com/magland/minwebide ../minwebide
24+(cd ../minwebide && npm install) # fetches the pinned VS Code source
25+npm install
26+npm run dev
27+```
28+
29+CI does the same: the deploy workflow checks out `magland/minwebide` next to
30+this repo, installs both, builds, and publishes `dist/` to GitHub Pages.
index.htmladded+24−0View file
@@ -0,0 +1,24 @@
1+<!doctype html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8" />
5+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230078d4'/%3E%3C/svg%3E" />
7+ <title>minwebide-demo</title>
8+ <style>
9+ html,
10+ body {
11+ height: 100%;
12+ margin: 0;
13+ padding: 0;
14+ }
15+ #app {
16+ height: 100%;
17+ }
18+ </style>
19+ </head>
20+ <body>
21+ <div id="app"></div>
22+ <script type="module" src="/src/main.ts"></script>
23+ </body>
24+</html>
package-lock.jsonadded+1183−0View file
@@ -0,0 +1,1183 @@
1+{
2+ "name": "minwebide-demo",
3+ "version": "0.1.0",
4+ "lockfileVersion": 3,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "minwebide-demo",
9+ "version": "0.1.0",
10+ "dependencies": {
11+ "minwebide": "file:../minwebide"
12+ },
13+ "devDependencies": {
14+ "typescript": "^5.9.0",
15+ "vite": "^7.0.0"
16+ }
17+ },
18+ "../minwebide": {
19+ "version": "0.1.0",
20+ "hasInstallScript": true,
21+ "license": "MIT",
22+ "dependencies": {
23+ "@vscode/codicons": "^0.0.46-21",
24+ "vscode-oniguruma": "1.7.0",
25+ "vscode-textmate": "^9.3.2"
26+ },
27+ "devDependencies": {
28+ "@types/wicg-file-system-access": "^2023.10.7",
29+ "@webgpu/types": "^0.1.71",
30+ "playwright": "^1.61.1",
31+ "trusted-types": "^2.0.0",
32+ "typescript": "^5.9.0",
33+ "vite": "^7.0.0"
34+ }
35+ },
36+ "node_modules/@esbuild/aix-ppc64": {
37+ "version": "0.28.1",
38+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
39+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
40+ "cpu": [
41+ "ppc64"
42+ ],
43+ "dev": true,
44+ "license": "MIT",
45+ "optional": true,
46+ "os": [
47+ "aix"
48+ ],
49+ "engines": {
50+ "node": ">=18"
51+ }
52+ },
53+ "node_modules/@esbuild/android-arm": {
54+ "version": "0.28.1",
55+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
56+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
57+ "cpu": [
58+ "arm"
59+ ],
60+ "dev": true,
61+ "license": "MIT",
62+ "optional": true,
63+ "os": [
64+ "android"
65+ ],
66+ "engines": {
67+ "node": ">=18"
68+ }
69+ },
70+ "node_modules/@esbuild/android-arm64": {
71+ "version": "0.28.1",
72+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
73+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
74+ "cpu": [
75+ "arm64"
76+ ],
77+ "dev": true,
78+ "license": "MIT",
79+ "optional": true,
80+ "os": [
81+ "android"
82+ ],
83+ "engines": {
84+ "node": ">=18"
85+ }
86+ },
87+ "node_modules/@esbuild/android-x64": {
88+ "version": "0.28.1",
89+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
90+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
91+ "cpu": [
92+ "x64"
93+ ],
94+ "dev": true,
95+ "license": "MIT",
96+ "optional": true,
97+ "os": [
98+ "android"
99+ ],
100+ "engines": {
101+ "node": ">=18"
102+ }
103+ },
104+ "node_modules/@esbuild/darwin-arm64": {
105+ "version": "0.28.1",
106+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
107+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
108+ "cpu": [
109+ "arm64"
110+ ],
111+ "dev": true,
112+ "license": "MIT",
113+ "optional": true,
114+ "os": [
115+ "darwin"
116+ ],
117+ "engines": {
118+ "node": ">=18"
119+ }
120+ },
121+ "node_modules/@esbuild/darwin-x64": {
122+ "version": "0.28.1",
123+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
124+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
125+ "cpu": [
126+ "x64"
127+ ],
128+ "dev": true,
129+ "license": "MIT",
130+ "optional": true,
131+ "os": [
132+ "darwin"
133+ ],
134+ "engines": {
135+ "node": ">=18"
136+ }
137+ },
138+ "node_modules/@esbuild/freebsd-arm64": {
139+ "version": "0.28.1",
140+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
141+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
142+ "cpu": [
143+ "arm64"
144+ ],
145+ "dev": true,
146+ "license": "MIT",
147+ "optional": true,
148+ "os": [
149+ "freebsd"
150+ ],
151+ "engines": {
152+ "node": ">=18"
153+ }
154+ },
155+ "node_modules/@esbuild/freebsd-x64": {
156+ "version": "0.28.1",
157+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
158+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
159+ "cpu": [
160+ "x64"
161+ ],
162+ "dev": true,
163+ "license": "MIT",
164+ "optional": true,
165+ "os": [
166+ "freebsd"
167+ ],
168+ "engines": {
169+ "node": ">=18"
170+ }
171+ },
172+ "node_modules/@esbuild/linux-arm": {
173+ "version": "0.28.1",
174+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
175+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
176+ "cpu": [
177+ "arm"
178+ ],
179+ "dev": true,
180+ "license": "MIT",
181+ "optional": true,
182+ "os": [
183+ "linux"
184+ ],
185+ "engines": {
186+ "node": ">=18"
187+ }
188+ },
189+ "node_modules/@esbuild/linux-arm64": {
190+ "version": "0.28.1",
191+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
192+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
193+ "cpu": [
194+ "arm64"
195+ ],
196+ "dev": true,
197+ "license": "MIT",
198+ "optional": true,
199+ "os": [
200+ "linux"
201+ ],
202+ "engines": {
203+ "node": ">=18"
204+ }
205+ },
206+ "node_modules/@esbuild/linux-ia32": {
207+ "version": "0.28.1",
208+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
209+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
210+ "cpu": [
211+ "ia32"
212+ ],
213+ "dev": true,
214+ "license": "MIT",
215+ "optional": true,
216+ "os": [
217+ "linux"
218+ ],
219+ "engines": {
220+ "node": ">=18"
221+ }
222+ },
223+ "node_modules/@esbuild/linux-loong64": {
224+ "version": "0.28.1",
225+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
226+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
227+ "cpu": [
228+ "loong64"
229+ ],
230+ "dev": true,
231+ "license": "MIT",
232+ "optional": true,
233+ "os": [
234+ "linux"
235+ ],
236+ "engines": {
237+ "node": ">=18"
238+ }
239+ },
240+ "node_modules/@esbuild/linux-mips64el": {
241+ "version": "0.28.1",
242+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
243+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
244+ "cpu": [
245+ "mips64el"
246+ ],
247+ "dev": true,
248+ "license": "MIT",
249+ "optional": true,
250+ "os": [
251+ "linux"
252+ ],
253+ "engines": {
254+ "node": ">=18"
255+ }
256+ },
257+ "node_modules/@esbuild/linux-ppc64": {
258+ "version": "0.28.1",
259+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
260+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
261+ "cpu": [
262+ "ppc64"
263+ ],
264+ "dev": true,
265+ "license": "MIT",
266+ "optional": true,
267+ "os": [
268+ "linux"
269+ ],
270+ "engines": {
271+ "node": ">=18"
272+ }
273+ },
274+ "node_modules/@esbuild/linux-riscv64": {
275+ "version": "0.28.1",
276+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
277+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
278+ "cpu": [
279+ "riscv64"
280+ ],
281+ "dev": true,
282+ "license": "MIT",
283+ "optional": true,
284+ "os": [
285+ "linux"
286+ ],
287+ "engines": {
288+ "node": ">=18"
289+ }
290+ },
291+ "node_modules/@esbuild/linux-s390x": {
292+ "version": "0.28.1",
293+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
294+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
295+ "cpu": [
296+ "s390x"
297+ ],
298+ "dev": true,
299+ "license": "MIT",
300+ "optional": true,
301+ "os": [
302+ "linux"
303+ ],
304+ "engines": {
305+ "node": ">=18"
306+ }
307+ },
308+ "node_modules/@esbuild/linux-x64": {
309+ "version": "0.28.1",
310+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
311+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
312+ "cpu": [
313+ "x64"
314+ ],
315+ "dev": true,
316+ "license": "MIT",
317+ "optional": true,
318+ "os": [
319+ "linux"
320+ ],
321+ "engines": {
322+ "node": ">=18"
323+ }
324+ },
325+ "node_modules/@esbuild/netbsd-arm64": {
326+ "version": "0.28.1",
327+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
328+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
329+ "cpu": [
330+ "arm64"
331+ ],
332+ "dev": true,
333+ "license": "MIT",
334+ "optional": true,
335+ "os": [
336+ "netbsd"
337+ ],
338+ "engines": {
339+ "node": ">=18"
340+ }
341+ },
342+ "node_modules/@esbuild/netbsd-x64": {
343+ "version": "0.28.1",
344+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
345+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
346+ "cpu": [
347+ "x64"
348+ ],
349+ "dev": true,
350+ "license": "MIT",
351+ "optional": true,
352+ "os": [
353+ "netbsd"
354+ ],
355+ "engines": {
356+ "node": ">=18"
357+ }
358+ },
359+ "node_modules/@esbuild/openbsd-arm64": {
360+ "version": "0.28.1",
361+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
362+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
363+ "cpu": [
364+ "arm64"
365+ ],
366+ "dev": true,
367+ "license": "MIT",
368+ "optional": true,
369+ "os": [
370+ "openbsd"
371+ ],
372+ "engines": {
373+ "node": ">=18"
374+ }
375+ },
376+ "node_modules/@esbuild/openbsd-x64": {
377+ "version": "0.28.1",
378+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
379+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
380+ "cpu": [
381+ "x64"
382+ ],
383+ "dev": true,
384+ "license": "MIT",
385+ "optional": true,
386+ "os": [
387+ "openbsd"
388+ ],
389+ "engines": {
390+ "node": ">=18"
391+ }
392+ },
393+ "node_modules/@esbuild/openharmony-arm64": {
394+ "version": "0.28.1",
395+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
396+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
397+ "cpu": [
398+ "arm64"
399+ ],
400+ "dev": true,
401+ "license": "MIT",
402+ "optional": true,
403+ "os": [
404+ "openharmony"
405+ ],
406+ "engines": {
407+ "node": ">=18"
408+ }
409+ },
410+ "node_modules/@esbuild/sunos-x64": {
411+ "version": "0.28.1",
412+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
413+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
414+ "cpu": [
415+ "x64"
416+ ],
417+ "dev": true,
418+ "license": "MIT",
419+ "optional": true,
420+ "os": [
421+ "sunos"
422+ ],
423+ "engines": {
424+ "node": ">=18"
425+ }
426+ },
427+ "node_modules/@esbuild/win32-arm64": {
428+ "version": "0.28.1",
429+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
430+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
431+ "cpu": [
432+ "arm64"
433+ ],
434+ "dev": true,
435+ "license": "MIT",
436+ "optional": true,
437+ "os": [
438+ "win32"
439+ ],
440+ "engines": {
441+ "node": ">=18"
442+ }
443+ },
444+ "node_modules/@esbuild/win32-ia32": {
445+ "version": "0.28.1",
446+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
447+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
448+ "cpu": [
449+ "ia32"
450+ ],
451+ "dev": true,
452+ "license": "MIT",
453+ "optional": true,
454+ "os": [
455+ "win32"
456+ ],
457+ "engines": {
458+ "node": ">=18"
459+ }
460+ },
461+ "node_modules/@esbuild/win32-x64": {
462+ "version": "0.28.1",
463+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
464+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
465+ "cpu": [
466+ "x64"
467+ ],
468+ "dev": true,
469+ "license": "MIT",
470+ "optional": true,
471+ "os": [
472+ "win32"
473+ ],
474+ "engines": {
475+ "node": ">=18"
476+ }
477+ },
478+ "node_modules/@rollup/rollup-android-arm-eabi": {
479+ "version": "4.62.2",
480+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
481+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
482+ "cpu": [
483+ "arm"
484+ ],
485+ "dev": true,
486+ "license": "MIT",
487+ "optional": true,
488+ "os": [
489+ "android"
490+ ]
491+ },
492+ "node_modules/@rollup/rollup-android-arm64": {
493+ "version": "4.62.2",
494+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
495+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
496+ "cpu": [
497+ "arm64"
498+ ],
499+ "dev": true,
500+ "license": "MIT",
501+ "optional": true,
502+ "os": [
503+ "android"
504+ ]
505+ },
506+ "node_modules/@rollup/rollup-darwin-arm64": {
507+ "version": "4.62.2",
508+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
509+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
510+ "cpu": [
511+ "arm64"
512+ ],
513+ "dev": true,
514+ "license": "MIT",
515+ "optional": true,
516+ "os": [
517+ "darwin"
518+ ]
519+ },
520+ "node_modules/@rollup/rollup-darwin-x64": {
521+ "version": "4.62.2",
522+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
523+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
524+ "cpu": [
525+ "x64"
526+ ],
527+ "dev": true,
528+ "license": "MIT",
529+ "optional": true,
530+ "os": [
531+ "darwin"
532+ ]
533+ },
534+ "node_modules/@rollup/rollup-freebsd-arm64": {
535+ "version": "4.62.2",
536+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
537+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
538+ "cpu": [
539+ "arm64"
540+ ],
541+ "dev": true,
542+ "license": "MIT",
543+ "optional": true,
544+ "os": [
545+ "freebsd"
546+ ]
547+ },
548+ "node_modules/@rollup/rollup-freebsd-x64": {
549+ "version": "4.62.2",
550+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
551+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
552+ "cpu": [
553+ "x64"
554+ ],
555+ "dev": true,
556+ "license": "MIT",
557+ "optional": true,
558+ "os": [
559+ "freebsd"
560+ ]
561+ },
562+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
563+ "version": "4.62.2",
564+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
565+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
566+ "cpu": [
567+ "arm"
568+ ],
569+ "dev": true,
570+ "libc": [
571+ "glibc"
572+ ],
573+ "license": "MIT",
574+ "optional": true,
575+ "os": [
576+ "linux"
577+ ]
578+ },
579+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
580+ "version": "4.62.2",
581+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
582+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
583+ "cpu": [
584+ "arm"
585+ ],
586+ "dev": true,
587+ "libc": [
588+ "musl"
589+ ],
590+ "license": "MIT",
591+ "optional": true,
592+ "os": [
593+ "linux"
594+ ]
595+ },
596+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
597+ "version": "4.62.2",
598+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
599+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
600+ "cpu": [
601+ "arm64"
602+ ],
603+ "dev": true,
604+ "libc": [
605+ "glibc"
606+ ],
607+ "license": "MIT",
608+ "optional": true,
609+ "os": [
610+ "linux"
611+ ]
612+ },
613+ "node_modules/@rollup/rollup-linux-arm64-musl": {
614+ "version": "4.62.2",
615+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
616+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
617+ "cpu": [
618+ "arm64"
619+ ],
620+ "dev": true,
621+ "libc": [
622+ "musl"
623+ ],
624+ "license": "MIT",
625+ "optional": true,
626+ "os": [
627+ "linux"
628+ ]
629+ },
630+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
631+ "version": "4.62.2",
632+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
633+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
634+ "cpu": [
635+ "loong64"
636+ ],
637+ "dev": true,
638+ "libc": [
639+ "glibc"
640+ ],
641+ "license": "MIT",
642+ "optional": true,
643+ "os": [
644+ "linux"
645+ ]
646+ },
647+ "node_modules/@rollup/rollup-linux-loong64-musl": {
648+ "version": "4.62.2",
649+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
650+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
651+ "cpu": [
652+ "loong64"
653+ ],
654+ "dev": true,
655+ "libc": [
656+ "musl"
657+ ],
658+ "license": "MIT",
659+ "optional": true,
660+ "os": [
661+ "linux"
662+ ]
663+ },
664+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
665+ "version": "4.62.2",
666+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
667+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
668+ "cpu": [
669+ "ppc64"
670+ ],
671+ "dev": true,
672+ "libc": [
673+ "glibc"
674+ ],
675+ "license": "MIT",
676+ "optional": true,
677+ "os": [
678+ "linux"
679+ ]
680+ },
681+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
682+ "version": "4.62.2",
683+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
684+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
685+ "cpu": [
686+ "ppc64"
687+ ],
688+ "dev": true,
689+ "libc": [
690+ "musl"
691+ ],
692+ "license": "MIT",
693+ "optional": true,
694+ "os": [
695+ "linux"
696+ ]
697+ },
698+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
699+ "version": "4.62.2",
700+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
701+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
702+ "cpu": [
703+ "riscv64"
704+ ],
705+ "dev": true,
706+ "libc": [
707+ "glibc"
708+ ],
709+ "license": "MIT",
710+ "optional": true,
711+ "os": [
712+ "linux"
713+ ]
714+ },
715+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
716+ "version": "4.62.2",
717+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
718+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
719+ "cpu": [
720+ "riscv64"
721+ ],
722+ "dev": true,
723+ "libc": [
724+ "musl"
725+ ],
726+ "license": "MIT",
727+ "optional": true,
728+ "os": [
729+ "linux"
730+ ]
731+ },
732+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
733+ "version": "4.62.2",
734+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
735+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
736+ "cpu": [
737+ "s390x"
738+ ],
739+ "dev": true,
740+ "libc": [
741+ "glibc"
742+ ],
743+ "license": "MIT",
744+ "optional": true,
745+ "os": [
746+ "linux"
747+ ]
748+ },
749+ "node_modules/@rollup/rollup-linux-x64-gnu": {
750+ "version": "4.62.2",
751+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
752+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
753+ "cpu": [
754+ "x64"
755+ ],
756+ "dev": true,
757+ "libc": [
758+ "glibc"
759+ ],
760+ "license": "MIT",
761+ "optional": true,
762+ "os": [
763+ "linux"
764+ ]
765+ },
766+ "node_modules/@rollup/rollup-linux-x64-musl": {
767+ "version": "4.62.2",
768+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
769+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
770+ "cpu": [
771+ "x64"
772+ ],
773+ "dev": true,
774+ "libc": [
775+ "musl"
776+ ],
777+ "license": "MIT",
778+ "optional": true,
779+ "os": [
780+ "linux"
781+ ]
782+ },
783+ "node_modules/@rollup/rollup-openbsd-x64": {
784+ "version": "4.62.2",
785+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
786+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
787+ "cpu": [
788+ "x64"
789+ ],
790+ "dev": true,
791+ "license": "MIT",
792+ "optional": true,
793+ "os": [
794+ "openbsd"
795+ ]
796+ },
797+ "node_modules/@rollup/rollup-openharmony-arm64": {
798+ "version": "4.62.2",
799+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
800+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
801+ "cpu": [
802+ "arm64"
803+ ],
804+ "dev": true,
805+ "license": "MIT",
806+ "optional": true,
807+ "os": [
808+ "openharmony"
809+ ]
810+ },
811+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
812+ "version": "4.62.2",
813+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
814+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
815+ "cpu": [
816+ "arm64"
817+ ],
818+ "dev": true,
819+ "license": "MIT",
820+ "optional": true,
821+ "os": [
822+ "win32"
823+ ]
824+ },
825+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
826+ "version": "4.62.2",
827+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
828+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
829+ "cpu": [
830+ "ia32"
831+ ],
832+ "dev": true,
833+ "license": "MIT",
834+ "optional": true,
835+ "os": [
836+ "win32"
837+ ]
838+ },
839+ "node_modules/@rollup/rollup-win32-x64-gnu": {
840+ "version": "4.62.2",
841+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
842+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
843+ "cpu": [
844+ "x64"
845+ ],
846+ "dev": true,
847+ "license": "MIT",
848+ "optional": true,
849+ "os": [
850+ "win32"
851+ ]
852+ },
853+ "node_modules/@rollup/rollup-win32-x64-msvc": {
854+ "version": "4.62.2",
855+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
856+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
857+ "cpu": [
858+ "x64"
859+ ],
860+ "dev": true,
861+ "license": "MIT",
862+ "optional": true,
863+ "os": [
864+ "win32"
865+ ]
866+ },
867+ "node_modules/@types/estree": {
868+ "version": "1.0.9",
869+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
870+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
871+ "dev": true,
872+ "license": "MIT"
873+ },
874+ "node_modules/esbuild": {
875+ "version": "0.28.1",
876+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
877+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
878+ "dev": true,
879+ "hasInstallScript": true,
880+ "license": "MIT",
881+ "bin": {
882+ "esbuild": "bin/esbuild"
883+ },
884+ "engines": {
885+ "node": ">=18"
886+ },
887+ "optionalDependencies": {
888+ "@esbuild/aix-ppc64": "0.28.1",
889+ "@esbuild/android-arm": "0.28.1",
890+ "@esbuild/android-arm64": "0.28.1",
891+ "@esbuild/android-x64": "0.28.1",
892+ "@esbuild/darwin-arm64": "0.28.1",
893+ "@esbuild/darwin-x64": "0.28.1",
894+ "@esbuild/freebsd-arm64": "0.28.1",
895+ "@esbuild/freebsd-x64": "0.28.1",
896+ "@esbuild/linux-arm": "0.28.1",
897+ "@esbuild/linux-arm64": "0.28.1",
898+ "@esbuild/linux-ia32": "0.28.1",
899+ "@esbuild/linux-loong64": "0.28.1",
900+ "@esbuild/linux-mips64el": "0.28.1",
901+ "@esbuild/linux-ppc64": "0.28.1",
902+ "@esbuild/linux-riscv64": "0.28.1",
903+ "@esbuild/linux-s390x": "0.28.1",
904+ "@esbuild/linux-x64": "0.28.1",
905+ "@esbuild/netbsd-arm64": "0.28.1",
906+ "@esbuild/netbsd-x64": "0.28.1",
907+ "@esbuild/openbsd-arm64": "0.28.1",
908+ "@esbuild/openbsd-x64": "0.28.1",
909+ "@esbuild/openharmony-arm64": "0.28.1",
910+ "@esbuild/sunos-x64": "0.28.1",
911+ "@esbuild/win32-arm64": "0.28.1",
912+ "@esbuild/win32-ia32": "0.28.1",
913+ "@esbuild/win32-x64": "0.28.1"
914+ }
915+ },
916+ "node_modules/fdir": {
917+ "version": "6.5.0",
918+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
919+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
920+ "dev": true,
921+ "license": "MIT",
922+ "engines": {
923+ "node": ">=12.0.0"
924+ },
925+ "peerDependencies": {
926+ "picomatch": "^3 || ^4"
927+ },
928+ "peerDependenciesMeta": {
929+ "picomatch": {
930+ "optional": true
931+ }
932+ }
933+ },
934+ "node_modules/fsevents": {
935+ "version": "2.3.3",
936+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
937+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
938+ "dev": true,
939+ "hasInstallScript": true,
940+ "license": "MIT",
941+ "optional": true,
942+ "os": [
943+ "darwin"
944+ ],
945+ "engines": {
946+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
947+ }
948+ },
949+ "node_modules/minwebide": {
950+ "resolved": "../minwebide",
951+ "link": true
952+ },
953+ "node_modules/nanoid": {
954+ "version": "3.3.15",
955+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
956+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
957+ "dev": true,
958+ "funding": [
959+ {
960+ "type": "github",
961+ "url": "https://github.com/sponsors/ai"
962+ }
963+ ],
964+ "license": "MIT",
965+ "bin": {
966+ "nanoid": "bin/nanoid.cjs"
967+ },
968+ "engines": {
969+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
970+ }
971+ },
972+ "node_modules/picocolors": {
973+ "version": "1.1.1",
974+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
975+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
976+ "dev": true,
977+ "license": "ISC"
978+ },
979+ "node_modules/picomatch": {
980+ "version": "4.0.5",
981+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
982+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
983+ "dev": true,
984+ "license": "MIT",
985+ "engines": {
986+ "node": ">=12"
987+ },
988+ "funding": {
989+ "url": "https://github.com/sponsors/jonschlinkert"
990+ }
991+ },
992+ "node_modules/postcss": {
993+ "version": "8.5.16",
994+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
995+ "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
996+ "dev": true,
997+ "funding": [
998+ {
999+ "type": "opencollective",
1000+ "url": "https://opencollective.com/postcss/"
1001+ },
1002+ {
1003+ "type": "tidelift",
1004+ "url": "https://tidelift.com/funding/github/npm/postcss"
1005+ },
1006+ {
1007+ "type": "github",
1008+ "url": "https://github.com/sponsors/ai"
1009+ }
1010+ ],
1011+ "license": "MIT",
1012+ "dependencies": {
1013+ "nanoid": "^3.3.12",
1014+ "picocolors": "^1.1.1",
1015+ "source-map-js": "^1.2.1"
1016+ },
1017+ "engines": {
1018+ "node": "^10 || ^12 || >=14"
1019+ }
1020+ },
1021+ "node_modules/rollup": {
1022+ "version": "4.62.2",
1023+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
1024+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
1025+ "dev": true,
1026+ "license": "MIT",
1027+ "dependencies": {
1028+ "@types/estree": "1.0.9"
1029+ },
1030+ "bin": {
1031+ "rollup": "dist/bin/rollup"
1032+ },
1033+ "engines": {
1034+ "node": ">=18.0.0",
1035+ "npm": ">=8.0.0"
1036+ },
1037+ "optionalDependencies": {
1038+ "@rollup/rollup-android-arm-eabi": "4.62.2",
1039+ "@rollup/rollup-android-arm64": "4.62.2",
1040+ "@rollup/rollup-darwin-arm64": "4.62.2",
1041+ "@rollup/rollup-darwin-x64": "4.62.2",
1042+ "@rollup/rollup-freebsd-arm64": "4.62.2",
1043+ "@rollup/rollup-freebsd-x64": "4.62.2",
1044+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
1045+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
1046+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
1047+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
1048+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
1049+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
1050+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
1051+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
1052+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
1053+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
1054+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
1055+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
1056+ "@rollup/rollup-linux-x64-musl": "4.62.2",
1057+ "@rollup/rollup-openbsd-x64": "4.62.2",
1058+ "@rollup/rollup-openharmony-arm64": "4.62.2",
1059+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
1060+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
1061+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
1062+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
1063+ "fsevents": "~2.3.2"
1064+ }
1065+ },
1066+ "node_modules/source-map-js": {
1067+ "version": "1.2.1",
1068+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1069+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1070+ "dev": true,
1071+ "license": "BSD-3-Clause",
1072+ "engines": {
1073+ "node": ">=0.10.0"
1074+ }
1075+ },
1076+ "node_modules/tinyglobby": {
1077+ "version": "0.2.17",
1078+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
1079+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
1080+ "dev": true,
1081+ "license": "MIT",
1082+ "dependencies": {
1083+ "fdir": "^6.5.0",
1084+ "picomatch": "^4.0.4"
1085+ },
1086+ "engines": {
1087+ "node": ">=12.0.0"
1088+ },
1089+ "funding": {
1090+ "url": "https://github.com/sponsors/SuperchupuDev"
1091+ }
1092+ },
1093+ "node_modules/typescript": {
1094+ "version": "5.9.3",
1095+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1096+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1097+ "dev": true,
1098+ "license": "Apache-2.0",
1099+ "bin": {
1100+ "tsc": "bin/tsc",
1101+ "tsserver": "bin/tsserver"
1102+ },
1103+ "engines": {
1104+ "node": ">=14.17"
1105+ }
1106+ },
1107+ "node_modules/vite": {
1108+ "version": "7.3.6",
1109+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
1110+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
1111+ "dev": true,
1112+ "license": "MIT",
1113+ "dependencies": {
1114+ "esbuild": "^0.27.0 || ^0.28.0",
1115+ "fdir": "^6.5.0",
1116+ "picomatch": "^4.0.3",
1117+ "postcss": "^8.5.6",
1118+ "rollup": "^4.43.0",
1119+ "tinyglobby": "^0.2.15"
1120+ },
1121+ "bin": {
1122+ "vite": "bin/vite.js"
1123+ },
1124+ "engines": {
1125+ "node": "^20.19.0 || >=22.12.0"
1126+ },
1127+ "funding": {
1128+ "url": "https://github.com/vitejs/vite?sponsor=1"
1129+ },
1130+ "optionalDependencies": {
1131+ "fsevents": "~2.3.3"
1132+ },
1133+ "peerDependencies": {
1134+ "@types/node": "^20.19.0 || >=22.12.0",
1135+ "jiti": ">=1.21.0",
1136+ "less": "^4.0.0",
1137+ "lightningcss": "^1.21.0",
1138+ "sass": "^1.70.0",
1139+ "sass-embedded": "^1.70.0",
1140+ "stylus": ">=0.54.8",
1141+ "sugarss": "^5.0.0",
1142+ "terser": "^5.16.0",
1143+ "tsx": "^4.8.1",
1144+ "yaml": "^2.4.2"
1145+ },
1146+ "peerDependenciesMeta": {
1147+ "@types/node": {
1148+ "optional": true
1149+ },
1150+ "jiti": {
1151+ "optional": true
1152+ },
1153+ "less": {
1154+ "optional": true
1155+ },
1156+ "lightningcss": {
1157+ "optional": true
1158+ },
1159+ "sass": {
1160+ "optional": true
1161+ },
1162+ "sass-embedded": {
1163+ "optional": true
1164+ },
1165+ "stylus": {
1166+ "optional": true
1167+ },
1168+ "sugarss": {
1169+ "optional": true
1170+ },
1171+ "terser": {
1172+ "optional": true
1173+ },
1174+ "tsx": {
1175+ "optional": true
1176+ },
1177+ "yaml": {
1178+ "optional": true
1179+ }
1180+ }
1181+ }
1182+ }
1183+}
package.jsonadded+19−0View file
@@ -0,0 +1,19 @@
1+{
2+ "name": "minwebide-demo",
3+ "version": "0.1.0",
4+ "private": true,
5+ "type": "module",
6+ "scripts": {
7+ "dev": "vite",
8+ "build": "vite build",
9+ "preview": "vite preview",
10+ "typecheck": "bash scripts/typecheck.sh"
11+ },
12+ "dependencies": {
13+ "minwebide": "file:../minwebide"
14+ },
15+ "devDependencies": {
16+ "typescript": "^5.9.0",
17+ "vite": "^7.0.0"
18+ }
19+}
scripts/typecheck.shadded+21−0View file
@@ -0,0 +1,21 @@
1+#!/usr/bin/env bash
2+# Typecheck the app. Diagnostics inside the minwebide vendor tree (VS Code
3+# source) are reported as a count only — they stem from TS-version and
4+# ambient-type differences with VS Code's own build and never affect the
5+# bundle. Errors in the app's own code fail the check.
6+set -uo pipefail
7+
8+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9+OUTPUT="$(cd "$ROOT" && npx tsc --noEmit --pretty false 2>&1)"
10+
11+VENDOR_COUNT="$(printf '%s\n' "$OUTPUT" | grep -cE '/vendor/vscode/' || true)"
12+OURS="$(printf '%s\n' "$OUTPUT" | grep -vE '/vendor/vscode/|^ ' | grep -v '^$' || true)"
13+
14+if [ -n "$VENDOR_COUNT" ] && [ "$VENDOR_COUNT" != "0" ]; then
15+ echo "note: $VENDOR_COUNT vendor diagnostics suppressed (run 'npx tsc --noEmit' to see them)"
16+fi
17+if [ -n "$OURS" ]; then
18+ printf '%s\n' "$OURS"
19+ exit 1
20+fi
21+echo "typecheck OK"
src/customEditors.cssadded+133−0View file
@@ -0,0 +1,133 @@
1+/* Styles for the demo custom editors — themed exclusively with the same
2+ * --vscode-* variables the rest of the workbench uses. */
3+
4+.demo-markdown-preview {
5+ padding: 20px 32px;
6+ max-width: 800px;
7+ line-height: 1.6;
8+ user-select: text;
9+ -webkit-user-select: text;
10+}
11+
12+.demo-markdown-preview h1,
13+.demo-markdown-preview h2 {
14+ border-bottom: 1px solid var(--vscode-editorWidget-border, var(--vscode-panel-border));
15+ padding-bottom: 0.3em;
16+ font-weight: 600;
17+}
18+
19+.demo-markdown-preview a {
20+ color: var(--vscode-textLink-foreground);
21+ text-decoration: none;
22+}
23+
24+.demo-markdown-preview a:hover {
25+ text-decoration: underline;
26+}
27+
28+.demo-markdown-preview code {
29+ background-color: var(--vscode-textCodeBlock-background);
30+ border-radius: 4px;
31+ padding: 1px 4px;
32+ font-family: ui-monospace, Menlo, Monaco, monospace;
33+ font-size: 0.9em;
34+}
35+
36+.demo-markdown-preview pre code {
37+ display: block;
38+ padding: 12px;
39+ overflow-x: auto;
40+}
41+
42+.demo-markdown-preview blockquote {
43+ margin: 0;
44+ padding-left: 12px;
45+ border-left: 4px solid var(--vscode-textBlockQuote-border);
46+ background-color: var(--vscode-textBlockQuote-background);
47+ color: var(--vscode-descriptionForeground);
48+}
49+
50+.demo-csv-viewer {
51+ padding: 12px;
52+}
53+
54+.demo-csv-viewer table {
55+ border-collapse: collapse;
56+ font-size: 13px;
57+ user-select: text;
58+ -webkit-user-select: text;
59+}
60+
61+.demo-csv-viewer th,
62+.demo-csv-viewer td {
63+ padding: 4px 12px;
64+ text-align: left;
65+ border: 1px solid var(--vscode-editorWidget-border, var(--vscode-panel-border));
66+}
67+
68+.demo-csv-viewer th {
69+ background-color: var(--vscode-keybindingTable-headerBackground, var(--vscode-editorWidget-background));
70+ font-weight: 600;
71+}
72+
73+.demo-csv-viewer tr:nth-child(even) td {
74+ background-color: var(--vscode-keybindingTable-rowsBackground, transparent);
75+}
76+
77+.demo-plots {
78+ padding: 10px;
79+ display: flex;
80+ flex-direction: column;
81+ gap: 10px;
82+}
83+
84+.demo-plot {
85+ background-color: var(--vscode-editorWidget-background, transparent);
86+ border: 1px solid var(--vscode-editorWidget-border, var(--vscode-panel-border));
87+ border-radius: 4px;
88+ padding: 8px 10px 6px;
89+}
90+
91+.demo-plot-title {
92+ font-size: 12px;
93+ font-weight: 600;
94+ margin-bottom: 4px;
95+ color: var(--vscode-foreground);
96+}
97+
98+.demo-plot-area {
99+ position: relative;
100+}
101+
102+.demo-plot-tooltip {
103+ position: absolute;
104+ pointer-events: none;
105+ padding: 2px 6px;
106+ font-size: 11px;
107+ border-radius: 3px;
108+ background-color: var(--vscode-editorHoverWidget-background);
109+ color: var(--vscode-editorHoverWidget-foreground, var(--vscode-foreground));
110+ border: 1px solid var(--vscode-editorHoverWidget-border);
111+ white-space: nowrap;
112+}
113+
114+.demo-image-viewer {
115+ height: 100%;
116+ display: flex;
117+ flex-direction: column;
118+ align-items: center;
119+ justify-content: center;
120+ gap: 10px;
121+}
122+
123+.demo-image-viewer img {
124+ max-width: calc(100% - 40px);
125+ max-height: calc(100% - 60px);
126+ object-fit: contain;
127+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
128+}
129+
130+.demo-image-caption {
131+ font-size: 12px;
132+ color: var(--vscode-descriptionForeground);
133+}
src/customEditors.tsadded+144−0View file
@@ -0,0 +1,144 @@
1+import { Delayer } from 'vs/base/common/async';
2+import { DisposableStore } from 'vs/base/common/lifecycle';
3+import type { CustomEditorPane, CustomEditorProvider } from 'minwebide';
4+import './customEditors.css';
5+
6+// Three example custom editors exercising the app-level custom editor API.
7+// The registration shape mirrors VS Code's `contributes.customEditors`.
8+
9+/**
10+ * Markdown preview (priority 'option': the text editor stays the default,
11+ * a tab-bar action offers the preview). Rendered by VS Code's own
12+ * vs/base markdownRenderer, live-updating against the shared text model.
13+ */
14+export const markdownPreview: CustomEditorProvider = {
15+ viewType: 'demo.markdownPreview',
16+ displayName: 'Markdown Preview',
17+ selector: [{ filenamePattern: '*.md' }],
18+ priority: 'option',
19+ async resolveCustomEditor(document): Promise<CustomEditorPane> {
20+ // markdownRenderer drags in marked + dompurify; load it only when a
21+ // preview is actually opened
22+ const { renderMarkdown } = await import('vs/base/browser/markdownRenderer');
23+ const disposables = new DisposableStore();
24+ const element = window.document.createElement('div');
25+ element.className = 'demo-markdown-preview';
26+
27+ const model = await document.getTextModel();
28+ const render = () => {
29+ const rendered = disposables.add(renderMarkdown({ value: model.getValue() }));
30+ element.replaceChildren(rendered.element);
31+ };
32+ const delayer = disposables.add(new Delayer<void>(200));
33+ disposables.add(model.onDidChangeContent(() => delayer.trigger(() => render())));
34+ render();
35+
36+ return { element, dispose: () => disposables.dispose() };
37+ },
38+};
39+
40+function parseCsvLine(line: string): string[] {
41+ const cells: string[] = [];
42+ let current = '';
43+ let inQuotes = false;
44+ for (let i = 0; i < line.length; i++) {
45+ const ch = line[i];
46+ if (inQuotes) {
47+ if (ch === '"' && line[i + 1] === '"') {
48+ current += '"';
49+ i++;
50+ } else if (ch === '"') {
51+ inQuotes = false;
52+ } else {
53+ current += ch;
54+ }
55+ } else if (ch === '"') {
56+ inQuotes = true;
57+ } else if (ch === ',') {
58+ cells.push(current);
59+ current = '';
60+ } else {
61+ current += ch;
62+ }
63+ }
64+ cells.push(current);
65+ return cells;
66+}
67+
68+/**
69+ * CSV table viewer (priority 'default': replaces the text editor for .csv;
70+ * "Reopen as Text Editor" stays available in the tab bar).
71+ */
72+export const csvViewer: CustomEditorProvider = {
73+ viewType: 'demo.csvViewer',
74+ displayName: 'CSV Table',
75+ selector: [{ filenamePattern: '*.csv' }],
76+ priority: 'default',
77+ async resolveCustomEditor(document): Promise<CustomEditorPane> {
78+ const disposables = new DisposableStore();
79+ const element = window.document.createElement('div');
80+ element.className = 'demo-csv-viewer';
81+
82+ const model = await document.getTextModel();
83+ const render = () => {
84+ const lines = model.getValue().split(/\r\n|\r|\n/).filter(line => line.length > 0);
85+ const table = window.document.createElement('table');
86+ lines.forEach((line, index) => {
87+ const row = table.insertRow();
88+ for (const cell of parseCsvLine(line)) {
89+ const cellEl = window.document.createElement(index === 0 ? 'th' : 'td');
90+ cellEl.textContent = cell;
91+ row.appendChild(cellEl);
92+ }
93+ });
94+ element.replaceChildren(table);
95+ };
96+ const delayer = disposables.add(new Delayer<void>(200));
97+ disposables.add(model.onDidChangeContent(() => delayer.trigger(() => render())));
98+ render();
99+
100+ return { element, dispose: () => disposables.dispose() };
101+ },
102+};
103+
104+const IMAGE_MIME: Record<string, string> = {
105+ png: 'image/png',
106+ jpg: 'image/jpeg',
107+ jpeg: 'image/jpeg',
108+ gif: 'image/gif',
109+ webp: 'image/webp',
110+ svg: 'image/svg+xml',
111+};
112+
113+/**
114+ * Image viewer — exercises the binary document path (readBytes).
115+ */
116+export const imageViewer: CustomEditorProvider = {
117+ viewType: 'demo.imageViewer',
118+ displayName: 'Image Viewer',
119+ selector: [{ filenamePattern: '*.{png,jpg,jpeg,gif,webp,svg}' }],
120+ priority: 'default',
121+ async resolveCustomEditor(document): Promise<CustomEditorPane> {
122+ const element = window.document.createElement('div');
123+ element.className = 'demo-image-viewer';
124+
125+ const bytes = await document.readBytes();
126+ const extension = document.uri.path.split('.').pop()?.toLowerCase() ?? '';
127+ const blob = new Blob([bytes as BlobPart], { type: IMAGE_MIME[extension] ?? 'application/octet-stream' });
128+ const url = URL.createObjectURL(blob);
129+
130+ const img = window.document.createElement('img');
131+ img.src = url;
132+ element.appendChild(img);
133+ const caption = window.document.createElement('div');
134+ caption.className = 'demo-image-caption';
135+ img.addEventListener('load', () => {
136+ caption.textContent = `${img.naturalWidth} × ${img.naturalHeight} ${bytes.byteLength.toLocaleString()} bytes`;
137+ });
138+ element.appendChild(caption);
139+
140+ return { element, dispose: () => URL.revokeObjectURL(url) };
141+ },
142+};
143+
144+export const demoCustomEditors: CustomEditorProvider[] = [markdownPreview, csvViewer, imageViewer];
src/main.tsadded+49−0View file
@@ -0,0 +1,49 @@
1+import { createIndexedDBFileSystem, createWorkbench, loadBuiltinTheme, registerBuiltinLanguages } from 'minwebide';
2+import { demoCustomEditors } from './customEditors';
3+import { demoRunners } from './runners';
4+import { sampleWorkspace } from './sampleWorkspace';
5+
6+/** A sample PNG, generated on the fly, to exercise the binary file path. */
7+async function generateSampleImage(): Promise<Uint8Array> {
8+ const canvas = document.createElement('canvas');
9+ canvas.width = 320;
10+ canvas.height = 200;
11+ const ctx = canvas.getContext('2d')!;
12+ const gradient = ctx.createLinearGradient(0, 0, 320, 200);
13+ gradient.addColorStop(0, '#0078d4');
14+ gradient.addColorStop(1, '#4ec9b0');
15+ ctx.fillStyle = gradient;
16+ ctx.fillRect(0, 0, 320, 200);
17+ ctx.fillStyle = 'white';
18+ ctx.font = 'bold 28px sans-serif';
19+ ctx.textAlign = 'center';
20+ ctx.fillText('minwebide', 160, 108);
21+ const blob = await new Promise<Blob>((resolve, reject) =>
22+ canvas.toBlob(b => b ? resolve(b) : reject(new Error('toBlob failed')), 'image/png'));
23+ return new Uint8Array(await blob.arrayBuffer());
24+}
25+
26+async function main(): Promise<void> {
27+ const fs = await createIndexedDBFileSystem({ dbName: 'minwebide-demo' });
28+ await fs.seed({
29+ ...sampleWorkspace,
30+ '/assets/banner.png': await generateSampleImage(),
31+ });
32+
33+ const theme = await loadBuiltinTheme('dark_modern');
34+ await registerBuiltinLanguages(theme);
35+
36+ const workbench = createWorkbench(document.getElementById('app')!, {
37+ fileSystem: fs,
38+ theme,
39+ workspaceName: 'demo workspace',
40+ customEditors: demoCustomEditors,
41+ });
42+ for (const runner of demoRunners) {
43+ workbench.registerRunner(runner);
44+ }
45+
46+ await workbench.openFile(fs.root.with({ path: '/README.md' }));
47+}
48+
49+main();
src/plot.tsadded+173−0View file
@@ -0,0 +1,173 @@
1+// A small single-series line-plot renderer for the demo runner's plot() API.
2+// Everything is themed with the --vscode-* variables (including VS Code's own
3+// charts.* colors), so plots follow the active color theme.
4+
5+export type PlotColor = 'blue' | 'orange' | 'green' | 'purple' | 'red' | 'yellow';
6+
7+export interface PlotSpec {
8+ readonly y: number[];
9+ readonly x?: number[];
10+ readonly title?: string;
11+ readonly color?: PlotColor;
12+}
13+
14+const SVG_NS = 'http://www.w3.org/2000/svg';
15+
16+function svgEl<K extends keyof SVGElementTagNameMap>(tag: K, attrs: Record<string, string | number> = {}): SVGElementTagNameMap[K] {
17+ const el = document.createElementNS(SVG_NS, tag);
18+ for (const [key, value] of Object.entries(attrs)) {
19+ el.setAttribute(key, String(value));
20+ }
21+ return el;
22+}
23+
24+function niceTicks(min: number, max: number, count = 4): number[] {
25+ const span = (max - min) || 1;
26+ const rough = span / count;
27+ const magnitude = 10 ** Math.floor(Math.log10(rough));
28+ const step = [1, 2, 5, 10].map(m => m * magnitude).find(s => span / s <= count) ?? magnitude * 10;
29+ const ticks: number[] = [];
30+ for (let v = Math.ceil(min / step) * step; v <= max + 1e-9; v += step) {
31+ ticks.push(Number(v.toFixed(10)));
32+ }
33+ return ticks;
34+}
35+
36+function fmt(value: number): string {
37+ if (Number.isInteger(value) && Math.abs(value) < 1e6) {
38+ return String(value);
39+ }
40+ return String(Number(value.toPrecision(4)));
41+}
42+
43+/** Renders one plot card into the container and keeps it sized to fit. */
44+export function renderPlot(container: HTMLElement, spec: PlotSpec): void {
45+ const card = document.createElement('div');
46+ card.className = 'demo-plot';
47+ if (spec.title) {
48+ const title = document.createElement('div');
49+ title.className = 'demo-plot-title';
50+ title.textContent = spec.title;
51+ card.appendChild(title);
52+ }
53+ const plotArea = document.createElement('div');
54+ plotArea.className = 'demo-plot-area';
55+ card.appendChild(plotArea);
56+ container.appendChild(card);
57+
58+ const draw = () => {
59+ plotArea.textContent = '';
60+ const width = Math.max(200, plotArea.clientWidth || 280);
61+ drawChart(plotArea, spec, width);
62+ };
63+ draw();
64+ let lastWidth = plotArea.clientWidth;
65+ const observer = new ResizeObserver(() => {
66+ if (plotArea.isConnected && plotArea.clientWidth !== lastWidth && plotArea.clientWidth > 0) {
67+ lastWidth = plotArea.clientWidth;
68+ requestAnimationFrame(draw);
69+ }
70+ });
71+ observer.observe(plotArea);
72+}
73+
74+function drawChart(host: HTMLElement, spec: PlotSpec, width: number): void {
75+ const height = 200;
76+ const margin = { top: 8, right: 12, bottom: 26, left: 46 };
77+ const innerWidth = width - margin.left - margin.right;
78+ const innerHeight = height - margin.top - margin.bottom;
79+ const color = `var(--vscode-charts-${spec.color ?? 'blue'})`;
80+
81+ const ys = spec.y;
82+ const xs = spec.x ?? ys.map((_, i) => i);
83+ const n = Math.min(xs.length, ys.length);
84+ if (n === 0) {
85+ return;
86+ }
87+
88+ const xMin = Math.min(...xs), xMax = Math.max(...xs);
89+ let yMin = Math.min(...ys), yMax = Math.max(...ys);
90+ const yPad = (yMax - yMin || 1) * 0.05;
91+ yMin -= yPad;
92+ yMax += yPad;
93+
94+ const px = (x: number) => margin.left + ((x - xMin) / (xMax - xMin || 1)) * innerWidth;
95+ const py = (y: number) => margin.top + (1 - (y - yMin) / (yMax - yMin || 1)) * innerHeight;
96+
97+ const svg = svgEl('svg', { width, height });
98+
99+ // recessive horizontal grid + y tick labels
100+ for (const tick of niceTicks(yMin, yMax)) {
101+ const y = py(tick);
102+ const grid = svgEl('line', { x1: margin.left, x2: width - margin.right, y1: y, y2: y });
103+ grid.style.stroke = 'var(--vscode-panel-border)';
104+ grid.style.opacity = '0.5';
105+ svg.appendChild(grid);
106+ const label = svgEl('text', { x: margin.left - 6, y: y + 3, 'text-anchor': 'end', 'font-size': 10 });
107+ label.style.fill = 'var(--vscode-descriptionForeground)';
108+ label.textContent = fmt(tick);
109+ svg.appendChild(label);
110+ }
111+ // x axis labels
112+ for (const tick of niceTicks(xMin, xMax, 5)) {
113+ const label = svgEl('text', { x: px(tick), y: height - margin.bottom + 14, 'text-anchor': 'middle', 'font-size': 10 });
114+ label.style.fill = 'var(--vscode-descriptionForeground)';
115+ label.textContent = fmt(tick);
116+ svg.appendChild(label);
117+ }
118+ const axis = svgEl('line', { x1: margin.left, x2: width - margin.right, y1: py(yMin) , y2: py(yMin) });
119+ axis.style.stroke = 'var(--vscode-panel-border)';
120+ svg.appendChild(axis);
121+
122+ // the series: a thin 2px line
123+ const path = svgEl('path', {
124+ d: Array.from({ length: n }, (_, i) => `${i === 0 ? 'M' : 'L'}${px(xs[i]).toFixed(1)},${py(ys[i]).toFixed(1)}`).join(''),
125+ fill: 'none', 'stroke-width': 2, 'stroke-linejoin': 'round',
126+ });
127+ path.style.stroke = color;
128+ svg.appendChild(path);
129+
130+ // hover layer: crosshair + ringed marker + tooltip on nearest point
131+ const crosshair = svgEl('line', { y1: margin.top, y2: height - margin.bottom, 'stroke-width': 1 });
132+ crosshair.style.stroke = 'var(--vscode-panel-border)';
133+ crosshair.style.display = 'none';
134+ svg.appendChild(crosshair);
135+ const marker = svgEl('circle', { r: 4, 'stroke-width': 2 });
136+ marker.style.fill = color;
137+ marker.style.stroke = 'var(--vscode-sideBar-background)';
138+ marker.style.display = 'none';
139+ svg.appendChild(marker);
140+ const tooltip = document.createElement('div');
141+ tooltip.className = 'demo-plot-tooltip';
142+ tooltip.style.display = 'none';
143+ host.appendChild(tooltip);
144+
145+ svg.addEventListener('pointermove', (e) => {
146+ const rect = svg.getBoundingClientRect();
147+ const mouseX = e.clientX - rect.left;
148+ let best = 0;
149+ for (let i = 1; i < n; i++) {
150+ if (Math.abs(px(xs[i]) - mouseX) < Math.abs(px(xs[best]) - mouseX)) {
151+ best = i;
152+ }
153+ }
154+ const cx = px(xs[best]), cy = py(ys[best]);
155+ crosshair.setAttribute('x1', String(cx));
156+ crosshair.setAttribute('x2', String(cx));
157+ crosshair.style.display = '';
158+ marker.setAttribute('cx', String(cx));
159+ marker.setAttribute('cy', String(cy));
160+ marker.style.display = '';
161+ tooltip.textContent = `${fmt(xs[best])}, ${fmt(ys[best])}`;
162+ tooltip.style.display = '';
163+ tooltip.style.left = `${Math.min(cx + 8, width - 90)}px`;
164+ tooltip.style.top = `${Math.max(cy - 26, 2)}px`;
165+ });
166+ svg.addEventListener('pointerleave', () => {
167+ crosshair.style.display = 'none';
168+ marker.style.display = 'none';
169+ tooltip.style.display = 'none';
170+ });
171+
172+ host.appendChild(svg);
173+}
src/runners.tsadded+78−0View file
@@ -0,0 +1,78 @@
1+import type { FileRunner } from 'minwebide';
2+import { renderPlot, type PlotSpec } from './plot';
3+
4+// Two example file runners. Execution strategy is entirely app-defined —
5+// here: in-page JavaScript evaluation with a captured console, and a trivial
6+// text statistics command. Output goes to VS Code-style output channels.
7+
8+function formatValue(value: unknown): string {
9+ if (typeof value === 'string') {
10+ return value;
11+ }
12+ if (value instanceof Error) {
13+ return value.stack ?? value.message;
14+ }
15+ try {
16+ return JSON.stringify(value, undefined, 2) ?? String(value);
17+ } catch {
18+ return String(value);
19+ }
20+}
21+
22+/**
23+ * Runs JavaScript files in the page (the app is trusted code; a real app
24+ * might instead target a worker, an iframe sandbox, or Pyodide).
25+ * console.* is captured into the output channel; a plot() function renders
26+ * charts into the runner's view in the secondary side bar.
27+ */
28+export const jsRunner: FileRunner = {
29+ id: 'demo.runJavaScript',
30+ displayName: 'Run JavaScript',
31+ selector: [{ filenamePattern: '*.{js,mjs}' }],
32+ async run({ uri, getText, output, getView }) {
33+ output.info(`Running ${uri.path}`);
34+ const code = await getText();
35+ const capturedConsole = {
36+ log: (...args: unknown[]) => output.appendLine(args.map(formatValue).join(' ')),
37+ info: (...args: unknown[]) => output.appendLine(args.map(formatValue).join(' ')),
38+ warn: (...args: unknown[]) => output.warn(args.map(formatValue).join(' ')),
39+ error: (...args: unknown[]) => output.error(args.map(formatValue).join(' ')),
40+ };
41+ let plotsEl: HTMLElement | undefined;
42+ const plot = (spec: PlotSpec | number[]) => {
43+ if (!plotsEl) {
44+ // first plot of this run: take over the runner's side bar
45+ // view and reveal it
46+ const view = getView();
47+ view.element.textContent = '';
48+ plotsEl = document.createElement('div');
49+ plotsEl.className = 'demo-plots';
50+ view.element.appendChild(plotsEl);
51+ view.show();
52+ }
53+ renderPlot(plotsEl, Array.isArray(spec) ? { y: spec } : spec);
54+ };
55+ const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor as new (...args: string[]) => (...fnArgs: unknown[]) => Promise<unknown>;
56+ const started = performance.now();
57+ try {
58+ await new AsyncFunction('console', 'plot', code)(capturedConsole, plot);
59+ output.info(`Finished in ${Math.round(performance.now() - started)}ms`);
60+ } catch (error) {
61+ output.error(error instanceof Error ? error : String(error));
62+ }
63+ },
64+};
65+
66+export const wordCountRunner: FileRunner = {
67+ id: 'demo.wordCount',
68+ displayName: 'Word Count',
69+ selector: [{ filenamePattern: '*.{md,txt}' }],
70+ async run({ uri, getText, output }) {
71+ const text = await getText();
72+ const lines = text.split(/\r\n|\r|\n/).length;
73+ const words = (text.match(/\S+/g) ?? []).length;
74+ output.info(`${uri.path}: ${lines} lines, ${words} words, ${text.length} characters`);
75+ },
76+};
77+
78+export const demoRunners: FileRunner[] = [jsRunner, wordCountRunner];
src/sampleWorkspace.tsadded+91−0View file
@@ -0,0 +1,91 @@
1+export const sampleWorkspace: Record<string, string> = {
2+ '/README.md': `# Demo workspace
3+
4+This workspace lives entirely in your browser's IndexedDB, served through
5+VS Code's own \`IndexedDBFileSystemProvider\` and \`FileService\`.
6+
7+- Edit a file and press **Ctrl+S** to save it.
8+- Reload the page: your changes persist.
9+- Open this app in a second tab: changes broadcast across tabs.
10+`,
11+ '/src/main.ts': `import { greet } from './utils';
12+
13+export function main(): void {
14+ const message = greet('minwebide');
15+ console.log(message);
16+}
17+
18+main();
19+`,
20+ '/src/utils.ts': `const EXCLAMATIONS = 3;
21+
22+export function greet(name: string): string {
23+ return \`Hello, \${name}\` + '!'.repeat(EXCLAMATIONS);
24+}
25+`,
26+ '/src/styles.css': `:root {
27+ --accent: #0078d4;
28+}
29+
30+body {
31+ font-family: system-ui, sans-serif;
32+ color: var(--accent);
33+}
34+`,
35+ '/data/measurements.csv': `sample,voltage_mV,current_uA,temperature_C,status
36+s-001,12.4,340,21.5,ok
37+s-002,11.9,332,21.7,ok
38+s-003,12.1,,21.4,"missing current"
39+s-004,13.0,355,22.1,ok
40+s-005,12.6,349,21.9,recalibrated
41+s-006,12.2,338,21.6,ok
42+`,
43+ '/data/config.json': `{
44+ "name": "minwebide-demo",
45+ "version": "0.1.0",
46+ "features": {
47+ "indexeddb": true,
48+ "textmate": true
49+ }
50+}
51+`,
52+ '/scripts/fibonacci.js': `// Press the ▶ button in the tab bar to run this file.
53+// Output appears in the Output view of the bottom panel.
54+
55+function fib(n) {
56+ return n < 2 ? n : fib(n - 1) + fib(n - 2);
57+}
58+
59+for (let i = 1; i <= 10; i++) {
60+ console.log(\`fib(\${i}) = \${fib(i)}\`);
61+}
62+
63+console.warn('warnings and errors are colorized by the log grammar');
64+`,
65+ '/scripts/sine-wave.js': `// Run this file (▶): console output goes to the bottom panel,
66+// while plot() renders into the runner's view in the secondary side bar.
67+
68+const xs = [];
69+const ys = [];
70+for (let i = 0; i <= 200; i++) {
71+ const x = (i / 200) * 4 * Math.PI;
72+ xs.push(x);
73+ ys.push(Math.sin(x) * Math.exp(-x / 8));
74+}
75+plot({ x: xs, y: ys, title: 'Damped sine wave' });
76+console.log(\`plotted \${xs.length} samples\`);
77+
78+const primes = [];
79+for (let n = 2; primes.length < 40; n++) {
80+ if (primes.every((p) => n % p)) primes.push(n);
81+}
82+plot({ y: primes, title: 'First 40 primes', color: 'orange' });
83+console.log('largest prime plotted:', primes[primes.length - 1]);
84+`,
85+ '/docs/notes.md': `# Notes
86+
87+Everything you see is either VS Code's own code (editor, file tree,
88+splitters, colors, icons) or a thin shell styled with VS Code's theme
89+variables.
90+`,
91+};
tsconfig.jsonadded+7−0View file
@@ -0,0 +1,7 @@
1+{
2+ "extends": "minwebide/tsconfig.base.json",
3+ "compilerOptions": {
4+ "types": ["vite/client"]
5+ },
6+ "include": ["src"]
7+}
vite.config.tsadded+8−0View file
@@ -0,0 +1,8 @@
1+import { defineConfig, mergeConfig } from 'vite';
2+import { minwebide } from 'minwebide/vite';
3+
4+// DEPLOY_BASE is set by CI when building for GitHub Pages
5+// (the site is served from /minwebide-demo/, not the domain root).
6+export default defineConfig(mergeConfig(minwebide(), {
7+ base: process.env.DEPLOY_BASE ?? '/',
8+}));