Spaces:
Sleeping
Sleeping
feat(amiga): scaffold MagicWB /amiga/ MPA at 2.4.0
Browse filesAdd parallel Workbench skin with self-hosted Topaz, MagicWB tokens,
and Vite MPA entry; keep legacy and v25 skins untouched.
Co-authored-by: Cursor <cursoragent@cursor.com>
- .agents/skills/dev-protocol/lessons-learned.md +16 -7
- CHANGELOG.md +5 -0
- CONTEXT.md +5 -0
- amiga/index.html +14 -0
- backend/server.py +1 -1
- manifest.json +1 -1
- package.json +1 -1
- roadmap/PROMPT-amiga-workbench.md +81 -0
- roadmap/README.md +2 -0
- roadmap/amiga-workbench.md +241 -0
- src/amiga/assets/fonts/LICENSE.txt +10 -0
- src/amiga/assets/fonts/Topaz_a1200_v1.0.ttf +0 -0
- src/amiga/main.js +30 -0
- src/amiga/style.css +113 -0
- src/amiga/tokenValues.js +28 -0
- src/amiga/tokens.css +30 -0
- src/amiga/version.js +3 -0
- src/ui/Navbar.js +1 -1
- tests/amigaTokens.test.js +38 -0
- tests/viteMpaInputs.test.js +10 -2
- vite.mpa.js +8 -7
.agents/skills/dev-protocol/lessons-learned.md
CHANGED
|
@@ -288,11 +288,20 @@ Portable findings from VHectorLab 3D `v2.1.0` — apply if the older app shares
|
|
| 288 |
|
| 289 |
## 6. Dev tooling / ngrok / Vite ↔ backend
|
| 290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
### 6.0d. `src/v25/**` — no tocar salvo pedido explícito del humano
|
| 292 |
- **Regla**: el árbol `src/v25/` (y `v25/index.html` / assets MPA asociados) es un **proyecto aparte**. En roadmaps/epics del UI legado (`src/main.js`, `src/ui/*`, `src/visualizer/*`) está **prohibido** editar `src/v25/**`.
|
| 293 |
- **Invariante**: no “sync de cortesía” (versión, copy, presets compartidos, imports) en v25 **a menos que el humano lo indique explícitamente** en el prompt (“tocá v25”, “sync version en v25”, “también en /v25/”, etc.). Silencio ≠ permiso.
|
| 294 |
- **Verificación**: antes de approval gate / merge, `git diff -- src/v25` debe estar vacío si el epic no autorizó v25.
|
| 295 |
- **Excepción**: solo cuando el humano lo pida; no inferir desde SemVer, CHANGELOG, o que v25 importe módulos de `src/ui/`.
|
|
|
|
| 296 |
|
| 297 |
### 6.0c. `v25:` MODE switch = visibility toggle, never wipe left host
|
| 298 |
- **Problema**: montar Compare con `zones.left.innerHTML = …` destruye el panel Arithmetic (form state + Top-10). Además `display: flex` en `.lab-left-host > [data-panel]` **pisa** el UA `[hidden]{display:none}` → el slot Arithmetic oculto sigue ocupando flex y deja un **hueco negro** arriba del Compare (lista cosine aplastada abajo).
|
|
@@ -305,15 +314,15 @@ Portable findings from VHectorLab 3D `v2.1.0` — apply if the older app shares
|
|
| 305 |
- **Invariante**: no fork de `SceneSetup` solo por MPA; no contaminar `src/style.css` legado. Startup canvas = `ARITHMETIC|ANALYSIS|POINTS` (`canvasStartupContext` / `appViewDefaults`).
|
| 306 |
- **No hacer en Fase 8+ inmediata**: wire live de sliders (Fase 9) ni MODE Compare (Fase 10) desde header tabs.
|
| 307 |
|
| 308 |
-
### 6.0. `v25:` Vite MPA + FastAPI directory index
|
| 309 |
-
- **Problema**: un solo `index.html` en la raíz no alcanza para
|
| 310 |
- **Solución Obligatoria**:
|
| 311 |
-
- Vite `build.rollupOptions.input` vía `getViteInputs(root)` (`vite.mpa.js`) → `main` + `v25` → emite `dist/index.html`
|
| 312 |
-
- `appType: 'mpa'` + plugin `mpaTrailingSlashRedirect` →
|
| 313 |
- FastAPI: `resolve_dist_file(dist, path)` → archivo exacto, si no `path/index.html`, si no root `index.html`.
|
| 314 |
-
- **Invariante**: nuevas páginas MPA = entry en `getViteInputs` **y** redirect bare-dir → slash; no hardcodear solo `/
|
| 315 |
-
- **No hacer**: contaminar `src/style.css` / `src/main.js` legado desde v25.
|
| 316 |
-
- **URL de prueba**: preferí `http://127.0.0.1:5173/v25/` (con slash); sin slash debe redirigir, no cargar el legado.
|
| 317 |
|
| 318 |
### 6.1. Proxy `/api` en Vite: prefijo general, no ruta-a-ruta
|
| 319 |
- **Problema**: Exponer frontend y backend con **el mismo** hostname ngrok (dos agentes → `:5173` y `:8000`) se pisa; el celu no puede llamar a `127.0.0.1:8000`.
|
|
|
|
| 288 |
|
| 289 |
## 6. Dev tooling / ngrok / Vite ↔ backend
|
| 290 |
|
| 291 |
+
### 6.0e. `/amiga` (MagicWB) — proyecto aparte; v25 y legado off-limits
|
| 292 |
+
- **Regla**: el skin Amiga Workbench / MagicWB vive **solo** bajo `amiga/` + `src/amiga/**` (MPA `/amiga/`). Es un epic **independiente** de `gui-art` / `gui-art-v25` / `/v25/`.
|
| 293 |
+
- **PROHIBIDO** en el epic Amiga: editar `src/v25/**`, `v25/**`, o “alinear” v25 (versión, tokens, copy). También evitar contaminar `src/style.css` / `src/main.js` / `src/ui/*` legado salvo cambios MPA compartidos mínimos (`vite.mpa.js`, redirect `/amiga`, tests MPA).
|
| 294 |
+
- **Invariante**: SemVer de producto en este epic = línea **`2.4.x`** (MINOR `2.4.0` al primer scaffold demoable). HF Space público = **último** slice del roadmap, no en slices intermedios.
|
| 295 |
+
- **Verificación**: antes de approval / merge Amiga → `git diff -- src/v25 v25` vacío; revisar que el diff legado no meta skin Amiga en `/`.
|
| 296 |
+
- **Norte visual**: Topaz self-hosted + sin antialiasing (múltiplos de 8px) + paleta MagicWB 8 colores; CSS estático (no inyección `<style>` por JS). Cosplay full (iconos/drawers) = roadmap futuro, no este epic.
|
| 297 |
+
- **Mobile**: desktop y mobile desde el inicio; si Topaz/density falla → preguntar al humano, no bajar de múltiplos de 8 “a ciegas”.
|
| 298 |
+
|
| 299 |
### 6.0d. `src/v25/**` — no tocar salvo pedido explícito del humano
|
| 300 |
- **Regla**: el árbol `src/v25/` (y `v25/index.html` / assets MPA asociados) es un **proyecto aparte**. En roadmaps/epics del UI legado (`src/main.js`, `src/ui/*`, `src/visualizer/*`) está **prohibido** editar `src/v25/**`.
|
| 301 |
- **Invariante**: no “sync de cortesía” (versión, copy, presets compartidos, imports) en v25 **a menos que el humano lo indique explícitamente** en el prompt (“tocá v25”, “sync version en v25”, “también en /v25/”, etc.). Silencio ≠ permiso.
|
| 302 |
- **Verificación**: antes de approval gate / merge, `git diff -- src/v25` debe estar vacío si el epic no autorizó v25.
|
| 303 |
- **Excepción**: solo cuando el humano lo pida; no inferir desde SemVer, CHANGELOG, o que v25 importe módulos de `src/ui/`.
|
| 304 |
+
- **Nota**: el epic `/amiga` (§6.0e) también deja v25 intacto; no usar Amiga como excusa para tocar v25.
|
| 305 |
|
| 306 |
### 6.0c. `v25:` MODE switch = visibility toggle, never wipe left host
|
| 307 |
- **Problema**: montar Compare con `zones.left.innerHTML = …` destruye el panel Arithmetic (form state + Top-10). Además `display: flex` en `.lab-left-host > [data-panel]` **pisa** el UA `[hidden]{display:none}` → el slot Arithmetic oculto sigue ocupando flex y deja un **hueco negro** arriba del Compare (lista cosine aplastada abajo).
|
|
|
|
| 314 |
- **Invariante**: no fork de `SceneSetup` solo por MPA; no contaminar `src/style.css` legado. Startup canvas = `ARITHMETIC|ANALYSIS|POINTS` (`canvasStartupContext` / `appViewDefaults`).
|
| 315 |
- **No hacer en Fase 8+ inmediata**: wire live de sliders (Fase 9) ni MODE Compare (Fase 10) desde header tabs.
|
| 316 |
|
| 317 |
+
### 6.0. `v25:` / `amiga:` Vite MPA + FastAPI directory index
|
| 318 |
+
- **Problema**: un solo `index.html` en la raíz no alcanza para banderas blancas (`/v25/`, `/amiga/`). En prod, el catch-all que solo chequea `is_file()` trata `dist/<dir>/` como miss y cae al SPA legado. En **dev**, Vite SPA fallback sirve el legado en `/v25` o `/amiga` **sin** trailing slash.
|
| 319 |
- **Solución Obligatoria**:
|
| 320 |
+
- Vite `build.rollupOptions.input` vía `getViteInputs(root)` (`vite.mpa.js`) → `main` + `v25` + `amiga` → emite `dist/index.html`, `dist/v25/index.html`, `dist/amiga/index.html`.
|
| 321 |
+
- `appType: 'mpa'` + plugin `mpaTrailingSlashRedirect` → bare dir 302 → trailing slash (`/v25/`, `/amiga/`).
|
| 322 |
- FastAPI: `resolve_dist_file(dist, path)` → archivo exacto, si no `path/index.html`, si no root `index.html`.
|
| 323 |
+
- **Invariante**: nuevas páginas MPA = entry en `getViteInputs` **y** redirect bare-dir → slash; no hardcodear solo `…/index.html` en el cliente si `/dir/` debe funcionar.
|
| 324 |
+
- **No hacer**: contaminar `src/style.css` / `src/main.js` legado desde v25 o amiga; no editar `src/v25/**` desde el epic amiga (ver §6.0e).
|
| 325 |
+
- **URL de prueba**: preferí `http://127.0.0.1:5173/v25/` y `http://127.0.0.1:5173/amiga/` (con slash); sin slash debe redirigir, no cargar el legado.
|
| 326 |
|
| 327 |
### 6.1. Proxy `/api` en Vite: prefijo general, no ruta-a-ruta
|
| 328 |
- **Problema**: Exponer frontend y backend con **el mismo** hostname ngrok (dos agentes → `:5173` y `:8000`) se pisa; el celu no puede llamar a `127.0.0.1:8000`.
|
CHANGELOG.md
CHANGED
|
@@ -23,6 +23,11 @@ All notable changes to VHectorLab 3D will be documented in this file.
|
|
| 23 |
- **v25 layout shell** (Fase 3): 5-zone grid (header / left / canvas / right / footer); mobile stack via legacy `MOBILE_MQ`.
|
| 24 |
- **v25 design tokens** (Fase 2): dark/fluo lab `:root`, Oxanium + IBM Plex Mono, `.lab-panel` / `.lab-btn` pressable utilities, WCAG contrast checks via `contrastRatio`.
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
## [2.3.0] - 2026-08-08
|
| 27 |
|
| 28 |
### Added
|
|
|
|
| 23 |
- **v25 layout shell** (Fase 3): 5-zone grid (header / left / canvas / right / footer); mobile stack via legacy `MOBILE_MQ`.
|
| 24 |
- **v25 design tokens** (Fase 2): dark/fluo lab `:root`, Oxanium + IBM Plex Mono, `.lab-panel` / `.lab-btn` pressable utilities, WCAG contrast checks via `contrastRatio`.
|
| 25 |
|
| 26 |
+
## [2.4.0] - 2026-08-08
|
| 27 |
+
|
| 28 |
+
### Added
|
| 29 |
+
- **`/amiga/` MPA scaffold** (Slice 1): Magic Workbench skin — self-hosted Topaz (`Topaz_a1200`), serrucho (no AA), 8-pen MagicWB tokens; Vite MPA entry + `/amiga` → `/amiga/` redirect. Parallel to legacy `/` and `/v25/` (neither skin touched). Plan: `roadmap/amiga-workbench.md`.
|
| 30 |
+
|
| 31 |
## [2.3.0] - 2026-08-08
|
| 32 |
|
| 33 |
### Added
|
CONTEXT.md
CHANGED
|
@@ -61,6 +61,11 @@ Percent of the |t| range held at the zero color (default black) before blending
|
|
| 61 |
### Group Contrast
|
| 62 |
Visualization paint for Compare with ≥2 groups: **Shared noise** blackens same-sign dims by similarity; **Sign conflict** highlights opposite-sign dims (custom color × |Δ|) and can blacken them by difference. Uses group means on raw embeddings; geometry Y unchanged.
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
### Shared Noise Similarity
|
| 65 |
`1 − |mean_G1 − mean_G2| / (|mean_G1| + |mean_G2|)` per dimension — high values mean shared-sign “noise” between groups.
|
| 66 |
|
|
|
|
| 61 |
### Group Contrast
|
| 62 |
Visualization paint for Compare with ≥2 groups: **Shared noise** blackens same-sign dims by similarity; **Sign conflict** highlights opposite-sign dims (custom color × |Δ|) and can blacken them by difference. Uses group means on raw embeddings; geometry Y unchanged.
|
| 63 |
|
| 64 |
+
### Amiga Skin
|
| 65 |
+
Parallel MPA UI at `/amiga/` with Magic Workbench palette and Topaz pixel typography. Independent of legacy `/` and of `/v25/`; does not change product math or API contracts.
|
| 66 |
+
|
| 67 |
+
_Avoid_: treating Amiga as a reskin of v25 or legacy; editing `src/v25/**` for Amiga work.
|
| 68 |
+
|
| 69 |
### Shared Noise Similarity
|
| 70 |
`1 − |mean_G1 − mean_G2| / (|mean_G1| + |mean_G2|)` per dimension — high values mean shared-sign “noise” between groups.
|
| 71 |
|
amiga/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.0, viewport-fit=cover" />
|
| 6 |
+
<title>VHectorLab-3D Amiga</title>
|
| 7 |
+
<meta name="description" content="VHectorLab-3D Amiga — Magic Workbench skin (parallel MPA)." />
|
| 8 |
+
<link rel="stylesheet" href="/src/amiga/style.css" />
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<div id="app"></div>
|
| 12 |
+
<script type="module" src="/src/amiga/main.js"></script>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
backend/server.py
CHANGED
|
@@ -51,7 +51,7 @@ async def lifespan(app: FastAPI):
|
|
| 51 |
app = FastAPI(
|
| 52 |
title="VHectorLab 3D API",
|
| 53 |
description="Backend API for 3D Vector Arithmetic & Semantic Embedding Visualizer",
|
| 54 |
-
version="2.
|
| 55 |
lifespan=lifespan,
|
| 56 |
)
|
| 57 |
|
|
|
|
| 51 |
app = FastAPI(
|
| 52 |
title="VHectorLab 3D API",
|
| 53 |
description="Backend API for 3D Vector Arithmetic & Semantic Embedding Visualizer",
|
| 54 |
+
version="2.4.0",
|
| 55 |
lifespan=lifespan,
|
| 56 |
)
|
| 57 |
|
manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"project": "vhectorlab-3d",
|
| 3 |
-
"version": "2.
|
| 4 |
"bootstrap_run": false,
|
| 5 |
"state_schema": {
|
| 6 |
"model_name": "all-mpnet-base-v2",
|
|
|
|
| 1 |
{
|
| 2 |
"project": "vhectorlab-3d",
|
| 3 |
+
"version": "2.4.0",
|
| 4 |
"bootstrap_run": false,
|
| 5 |
"state_schema": {
|
| 6 |
"model_name": "all-mpnet-base-v2",
|
package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "vhectorlab-3d",
|
| 3 |
-
"version": "2.
|
| 4 |
"type": "module",
|
| 5 |
"scripts": {
|
| 6 |
"dev": "vite",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "vhectorlab-3d",
|
| 3 |
+
"version": "2.4.0",
|
| 4 |
"type": "module",
|
| 5 |
"scripts": {
|
| 6 |
"dev": "vite",
|
roadmap/PROMPT-amiga-workbench.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Prompt — Amiga Workbench `/amiga` (MagicWB)
|
| 2 |
+
|
| 3 |
+
**Modo:** **serial** — una sesión / un slice. No agentes en paralelo.
|
| 4 |
+
Empezá por el slice que indique el humano (default: **Slice 1** tras Slice 0 docs). No re-litigar Q1–Q10.
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
```text
|
| 9 |
+
Usando dev-protocol, implementá el roadmap Amiga Workbench `/amiga`.
|
| 10 |
+
|
| 11 |
+
## Contexto
|
| 12 |
+
Repo: VHectorLab 3D / vhectorlab (Python/uv + Vite/Three.js + FastAPI).
|
| 13 |
+
Base: `main` actualizado (`git pull`).
|
| 14 |
+
SemVer: línea **2.4.x** (Slice 1 cierra con MINOR **2.4.0**).
|
| 15 |
+
|
| 16 |
+
Roadmap canónico (leelo COMPLETO antes de código):
|
| 17 |
+
`roadmap/amiga-workbench.md`
|
| 18 |
+
Lecciones: `.agents/skills/dev-protocol/lessons-learned.md` — **§6.0e obligatorio**
|
| 19 |
+
(+ §6.0d v25; §6.0 MPA).
|
| 20 |
+
|
| 21 |
+
## AISLAMIENTO (NO negociable)
|
| 22 |
+
1. **PROHIBIDO** tocar `src/v25/**`, `v25/**`, o “sync” a v25.
|
| 23 |
+
2. Trabajo nuevo SOLO en `amiga/` + `src/amiga/**`.
|
| 24 |
+
3. Legado `/` intacto salvo cambios MPA compartidos mínimos (`vite.mpa.js`, `vite.config.js`, tests MPA).
|
| 25 |
+
4. Antes de approval: `git diff -- src/v25 v25` vacío.
|
| 26 |
+
5. Este epic **NO** es gui-art / gui-art-v25. Cero dependencia de ese norte visual.
|
| 27 |
+
|
| 28 |
+
## Decisiones CERRADAS (no re-preguntar)
|
| 29 |
+
- Bandera blanca `/amiga/`; Magic Workbench palette; Topaz self-host; CSS estático.
|
| 30 |
+
- Q2-A only (tipo+paleta+chrome mínimo). Cosplay Q2-C = otro roadmap.
|
| 31 |
+
- Canvas 3D look no se artea (Q3-A).
|
| 32 |
+
- Desktop + mobile; si mobile se complica → preguntar con diseño, no improvisar.
|
| 33 |
+
- HF Space = Slice 7 / final solamente.
|
| 34 |
+
|
| 35 |
+
## Estilo
|
| 36 |
+
No-fluff; TDD donde haya helpers puros (tokens, MPA inputs); módulos profundos.
|
| 37 |
+
Seguí `.agents/skills/dev-protocol/SKILL.md`
|
| 38 |
+
(clarify → branch `feat/amiga-<slice>` → implement → verify → docs → approval gate → git delivery con OK).
|
| 39 |
+
|
| 40 |
+
## Slice 1 — DoD (sesión típica de arranque)
|
| 41 |
+
1. `amiga/index.html` + `src/amiga/main.js` + `tokens.css` / `style.css`.
|
| 42 |
+
2. Topaz self-hosted bajo `src/amiga/assets/fonts/` (+ nota LICENSE en carpeta o README amiga).
|
| 43 |
+
3. Clase raíz / `:root` con serrucho (no AA) + font-size 16px (múltiplos de 8) + paleta MagicWB (§3.2 roadmap).
|
| 44 |
+
4. Vite MPA: entry `amiga` + redirect `/amiga` → `/amiga/`.
|
| 45 |
+
5. Tests MPA actualizados (amiga en `getViteInputs` / redirect).
|
| 46 |
+
6. Bump producto a **2.4.0** (manifest / package / CHANGELOG; Navbar legado solo si el DoD de SemVer del repo lo exige — **no** tocar version.js de v25).
|
| 47 |
+
7. Smoke: `/` y `/v25/` sin cambios visuales; `/amiga/` se ve MagicWB+Topaz.
|
| 48 |
+
8. APPROVAL GATE; ESPERÁ OK antes de push/merge.
|
| 49 |
+
|
| 50 |
+
## Slices siguientes
|
| 51 |
+
Ver tabla §6 de `roadmap/amiga-workbench.md`. Un slice por sesión.
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## Prompt — Slice 2+
|
| 57 |
+
|
| 58 |
+
```text
|
| 59 |
+
Usando dev-protocol, continuá Amiga `/amiga` — SOLO Slice N.
|
| 60 |
+
|
| 61 |
+
Roadmap: `roadmap/amiga-workbench.md` §6 Slice N.
|
| 62 |
+
PROHIBIDO: `src/v25/**`, `v25/**`.
|
| 63 |
+
Legado `/` solo MPA shared si hace falta.
|
| 64 |
+
SemVer: PATCH 2.4.x salvo que el humano diga otro bump.
|
| 65 |
+
|
| 66 |
+
DoD: el de la fila Slice N del roadmap.
|
| 67 |
+
APPROVAL GATE; no push/merge sin OK.
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Prompt — Slice 7 (HF Space)
|
| 73 |
+
|
| 74 |
+
```text
|
| 75 |
+
Usando dev-protocol, cerrá Amiga `/amiga` — Slice 7 polish + HF Space.
|
| 76 |
+
|
| 77 |
+
Roadmap: `roadmap/amiga-workbench.md` §7–§9.
|
| 78 |
+
Confirmá con el humano si Space default queda en `/`, `/amiga/`, o link.
|
| 79 |
+
PROHIBIDO: tocar v25.
|
| 80 |
+
APPROVAL GATE → OK → git delivery + publish Space según proceso del repo.
|
| 81 |
+
```
|
roadmap/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Documentos de planificación **activos** viven en esta carpeta. Históricos en *
|
|
| 4 |
|
| 5 |
| Doc | Tema | Estado |
|
| 6 |
|---|---|---|
|
|
|
|
|
|
|
| 7 |
| [`galaxy-view.md`](./galaxy-view.md) | VIEW **Galaxy**: UMAP backend, 1 punto/token, COMPARE+POINTS locked, IT core×100 | **Activo (plan)** |
|
| 8 |
| [`PROMPT-galaxy-view.md`](./PROMPT-galaxy-view.md) | Prompt serial por slice (1→7) para agentes Galaxy | **Activo** |
|
| 9 |
| [`gui-art-v25.md`](./gui-art-v25.md) | GUI/Art **v25**: layout+skin paralelo en `/v25/` (fases 0–13, hand-offs) | **Activo (plan)** |
|
|
|
|
| 4 |
|
| 5 |
| Doc | Tema | Estado |
|
| 6 |
|---|---|---|
|
| 7 |
+
| [`amiga-workbench.md`](./amiga-workbench.md) | Skin **Amiga**/MagicWB en bandera blanca `/amiga/` (Topaz + paleta; SemVer 2.4.x) | **Activo (plan)** |
|
| 8 |
+
| [`PROMPT-amiga-workbench.md`](./PROMPT-amiga-workbench.md) | Prompt serial por slice para agentes `/amiga` | **Activo** |
|
| 9 |
| [`galaxy-view.md`](./galaxy-view.md) | VIEW **Galaxy**: UMAP backend, 1 punto/token, COMPARE+POINTS locked, IT core×100 | **Activo (plan)** |
|
| 10 |
| [`PROMPT-galaxy-view.md`](./PROMPT-galaxy-view.md) | Prompt serial por slice (1→7) para agentes Galaxy | **Activo** |
|
| 11 |
| [`gui-art-v25.md`](./gui-art-v25.md) | GUI/Art **v25**: layout+skin paralelo en `/v25/` (fases 0–13, hand-offs) | **Activo (plan)** |
|
roadmap/amiga-workbench.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Amiga Workbench skin — `/amiga` (MagicWB)
|
| 2 |
+
|
| 3 |
+
**Estado:** activo — Slice 1 listo (approval gate)
|
| 4 |
+
**Protocolo:** `.agents/skills/dev-protocol/` (ciclo completo + approval gate)
|
| 5 |
+
**Prompt agente:** [`PROMPT-amiga-workbench.md`](./PROMPT-amiga-workbench.md)
|
| 6 |
+
**SemVer:** línea **`2.4.x`** (MINOR `2.4.0` en Slice 1)
|
| 7 |
+
**HF Space público:** **solo al final** (§7 Slice final) — no en slices intermedios
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. Norte
|
| 12 |
+
|
| 13 |
+
Bandera blanca **`/amiga/`**: UI paralela con estética **Commodore Amiga Workbench 3.0 + Magic Workbench** (tipografía pixel / efecto serrucho + paleta MagicWB).
|
| 14 |
+
|
| 15 |
+
- **No** es un reskin del legado `/`.
|
| 16 |
+
- **No** es v25 ni continúa `gui-art` / `gui-art-v25`.
|
| 17 |
+
- Canvas 3D **no se toca** en este epic (clear color / shaders / POINTS / RIBBONS quedan como el engine actual cuando se cablee).
|
| 18 |
+
- Profundidad de este epic = **Q2-A**: tipografía + paleta + chrome mínimo legible.
|
| 19 |
+
**Q2-C** (cosplay full: iconos MagicWB, drawers, pointer, metaphors) → roadmap **aparte**, después de que `/amiga` esté usable.
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## 1. Decisiones cerradas (Q1–Q10)
|
| 24 |
+
|
| 25 |
+
| ID | Decisión |
|
| 26 |
+
| :--- | :--- |
|
| 27 |
+
| **Q1** | Bandera blanca **`/amiga/`**. Legado `/` intacto. **`src/v25/**` y `v25/**` — PROHIBIDO tocar (explícito; ver lessons §6.0e). |
|
| 28 |
+
| **Q2** | **A** — tipografía pixel + paleta MagicWB (+ chrome 2D mínimo para que se lea como WB). **C** (cosplay) → epic futuro aparte. |
|
| 29 |
+
| **Q3** | **A** — no artear el look 3D; canvas héroe reusa engine sin “pixelizar” puntos/ribbons. |
|
| 30 |
+
| **Q4** | **A** — self-host Topaz (`.ttf` / `.woff2` en repo bajo `amiga/` o `src/amiga/assets/`). Sin CDN GitHub raw. |
|
| 31 |
+
| **Q5** | **A** — CSS estático (tokens + clase raíz). **No** inyección dinámica de `<style>` por JS. |
|
| 32 |
+
| **Q6** | Paleta **Magic Workbench** (8 colores canónicos + tokens semánticos). |
|
| 33 |
+
| **Q7** | Desktop **y** mobile desde el inicio. Si mobile se complica (legibilidad Topaz / density) → **parar y preguntar** con propuesta, no inventar. |
|
| 34 |
+
| **Q8** | Cero relación con `gui-art` / v25. Separación total. Lesson obligatoria. SemVer **2.4.x**. |
|
| 35 |
+
| **Q9** | HF Space público = **último slice** del epic (§7). |
|
| 36 |
+
| **Q10** | Primer slice demoable = shell `/amiga/` con Topaz + serrucho + fondo/paleta MagicWB. |
|
| 37 |
+
|
| 38 |
+
### Fuera de alcance (este epic)
|
| 39 |
+
|
| 40 |
+
- Editar `src/v25/**`, `v25/index.html`, tokens/skin v25.
|
| 41 |
+
- Contaminar `src/style.css` / `src/main.js` legado “para que amiga funcione”.
|
| 42 |
+
- Cosplay Q2-C (icon packs, drawer metaphors, Amiga pointer custom) — doc futuro.
|
| 43 |
+
- Cambiar contratos `/api/*` / SAE math / Galaxy.
|
| 44 |
+
- Publicar HF Space antes del slice final.
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
## 2. Arquitectura bandera blanca `/amiga/`
|
| 49 |
+
|
| 50 |
+
### 2.1 Layout de archivos (objetivo)
|
| 51 |
+
|
| 52 |
+
```text
|
| 53 |
+
amiga/index.html # entry MPA → /amiga/
|
| 54 |
+
src/amiga/
|
| 55 |
+
main.js # bootstrap solo amiga
|
| 56 |
+
style.css # importa tokens + skin
|
| 57 |
+
tokens.css # :root MagicWB + Topaz
|
| 58 |
+
assets/fonts/ # TopazNew self-hosted (+ LICENSE)
|
| 59 |
+
ui/ # shell / paneles (crecer por slice)
|
| 60 |
+
… # copy-adapt desde legado si hace falta; NO editar legado para “arreglar” amiga
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
Legado intacto: `index.html`, `src/main.js`, `src/ui/*`, `src/style.css`.
|
| 64 |
+
v25 intacto: `v25/**`, `src/v25/**`.
|
| 65 |
+
|
| 66 |
+
### 2.2 Vite / serve
|
| 67 |
+
|
| 68 |
+
Extender el MPA existente (mismo patrón que v25):
|
| 69 |
+
|
| 70 |
+
- `getViteInputs` → agregar `amiga: …/amiga/index.html`.
|
| 71 |
+
- `mpaTrailingSlashRedirect` / `mpaTrailingSlashLocation` → incluir `'/amiga'`.
|
| 72 |
+
- Dev: `http://127.0.0.1:5173/amiga/` (con slash).
|
| 73 |
+
- Build: `dist/amiga/index.html`; FastAPI `resolve_dist_file` ya cubre `path/index.html`.
|
| 74 |
+
- Proxy `/api` sin cambios.
|
| 75 |
+
|
| 76 |
+
### 2.3 Reglas de convivencia
|
| 77 |
+
|
| 78 |
+
| Árbol | Política en este epic |
|
| 79 |
+
| :--- | :--- |
|
| 80 |
+
| `/` legado | **No modificar** salvo smoke check de no-regresión (idealmente `git diff` vacío en paths legado). |
|
| 81 |
+
| `/v25/` | **PROHIBIDO**. Verificación: `git diff -- src/v25 v25` vacío antes de approval. |
|
| 82 |
+
| `/amiga/` | Único lugar de trabajo nuevo. |
|
| 83 |
+
| `src/engine/*`, `src/visualizer/*`, `src/core/*` | Reusar por import en slices de wire (como v25); **no** fork de math/shaders. |
|
| 84 |
+
| Helpers puros compartidos | Preferir copy-adapt en `src/amiga/` al inicio; extraer a módulo neutro solo con test + OK humano. |
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## 3. Norte visual (MagicWB + Topaz)
|
| 89 |
+
|
| 90 |
+
### 3.1 Tipografía
|
| 91 |
+
|
| 92 |
+
- Familia: **TopazNew** (o Topaz equivalente self-hosted).
|
| 93 |
+
- Tamaños en **múltiplos de 8** (base **16px**; line-height tip. **20px**).
|
| 94 |
+
- Sin antialiasing / efecto serrucho:
|
| 95 |
+
|
| 96 |
+
```css
|
| 97 |
+
-webkit-font-smoothing: none;
|
| 98 |
+
-moz-osx-font-smoothing: grayscale; /* o unset según plataforma; documentar en lesson si diverge */
|
| 99 |
+
text-rendering: optimizeSpeed;
|
| 100 |
+
font-smooth: never; /* non-standard; harmless where ignored */
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
- Imágenes UI (si hay): `image-rendering: pixelated` / `crisp-edges`.
|
| 104 |
+
|
| 105 |
+
### 3.2 Paleta Magic Workbench (8 pens)
|
| 106 |
+
|
| 107 |
+
Fuente canónica (Wikipedia / MagicWB docs), RGB → hex:
|
| 108 |
+
|
| 109 |
+
| Idx | Hex | Rol sugerido (token) |
|
| 110 |
+
| :---: | :--- | :--- |
|
| 111 |
+
| 0 | `#959595` | `--mwb-gray` fondo escritorio / paneles |
|
| 112 |
+
| 1 | `#000000` | `--mwb-black` texto |
|
| 113 |
+
| 2 | `#FFFFFF` | `--mwb-white` highlight / shine |
|
| 114 |
+
| 3 | `#3B67A2` | `--mwb-blue` acento / titlebar-ish |
|
| 115 |
+
| 4 | `#7B7B7B` | `--mwb-halfshadow` |
|
| 116 |
+
| 5 | `#AFAFAF` | `--mwb-halfshine` |
|
| 117 |
+
| 6 | `#AA907C` | `--mwb-tan` |
|
| 118 |
+
| 7 | `#FFA997` | `--mwb-peach` |
|
| 119 |
+
|
| 120 |
+
Tokens semánticos mínimos (Slice 1+):
|
| 121 |
+
|
| 122 |
+
```text
|
| 123 |
+
--amiga-bg: var(--mwb-gray);
|
| 124 |
+
--amiga-fg: var(--mwb-black);
|
| 125 |
+
--amiga-accent: var(--mwb-blue);
|
| 126 |
+
--amiga-font: 'TopazNew', monospace;
|
| 127 |
+
--amiga-font-size: 16px;
|
| 128 |
+
--amiga-line-height: 20px;
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
> Nota: el gris clásico WB `#aaaaaa` / azul titlebar `#0055aa` del snippet original **ceden** a MagicWB (§Q6). Si hace falta un azul más “WB 3.0 puro” para titlebars en un slice de chrome, preguntar antes de desviarse.
|
| 132 |
+
|
| 133 |
+
### 3.3 Mobile
|
| 134 |
+
|
| 135 |
+
- Mismos tokens; tipografía 16px puede ser densa → medir portrait + landscape corto (`MOBILE_MQ` del legado como referencia).
|
| 136 |
+
- Si Topaz ilegible o docks imposibles: **diseñar 1–2 opciones y preguntar** (no bajar a 14px “porque sí”; múltiplos de 8 o pregunta).
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## 4. SemVer / docs
|
| 141 |
+
|
| 142 |
+
| Entrega | SemVer | Docs |
|
| 143 |
+
| :--- | :--- | :--- |
|
| 144 |
+
| Slice 1: scaffold `/amiga/` + Topaz + tokens MagicWB visibles | **MINOR `2.4.0`** | CHANGELOG + CONTEXT término `amiga` + lessons §6.0e |
|
| 145 |
+
| Slices siguientes (shell, wire producto) | **PATCH `2.4.x`** | CHANGELOG Unreleased / sección 2.4.x |
|
| 146 |
+
| Cosplay Q2-C (otro roadmap) | a decidir | — |
|
| 147 |
+
| HF Space cutover / demo pública | Slice final (§7) | README/HF docs según `hf-space-cpu-demo.md` |
|
| 148 |
+
|
| 149 |
+
Versión producto en Navbar/manifest: **solo** cuando el slice de amiga lo pida en su DoD; **no** sync cortesía a v25 (§6.0d + §6.0e).
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## 5. Invariantes que NO romper
|
| 154 |
+
|
| 155 |
+
- English-only product copy.
|
| 156 |
+
- Contratos API `/api/*`.
|
| 157 |
+
- Lecciones WebGL existentes si se cablea canvas (frustum, fog, etc.).
|
| 158 |
+
- `git diff -- src/v25 v25` vacío siempre en este epic.
|
| 159 |
+
- Preferir `git diff` vacío en legado `/` (`index.html`, `src/main.js`, `src/style.css`, `src/ui/*`) salvo cambios MPA compartidos inevitables (`vite.mpa.js`, `vite.config.js`, tests MPA).
|
| 160 |
+
|
| 161 |
+
Cambios MPA compartidos permitidos (mínimos):
|
| 162 |
+
|
| 163 |
+
- `vite.mpa.js` / `vite.config.js` — agregar entry + redirect `/amiga`.
|
| 164 |
+
- `tests/viteMpaInputs.test.js` (o equivalente) — cubrir `amiga`.
|
| 165 |
+
- FastAPI solo si hace falta test de `resolve_dist_file` con `/amiga/` (probablemente ya genérico).
|
| 166 |
+
|
| 167 |
+
---
|
| 168 |
+
|
| 169 |
+
## 6. Slices secuenciales
|
| 170 |
+
|
| 171 |
+
> **No en paralelo.** Un slice / sesión. Approval gate → OK → merge antes del siguiente.
|
| 172 |
+
|
| 173 |
+
| Slice | Nombre | DoD (testeable) | Dep | Status |
|
| 174 |
+
| :---: | :--- | :--- | :---: | :--- |
|
| 175 |
+
| **0** | Plan + prompt | Este doc + `PROMPT-amiga-workbench.md` + README roadmap + lesson §6.0e | — | ✅ 2026-08-08 |
|
| 176 |
+
| **1** | Scaffold MPA + Topaz + tokens | `/amiga/` carga; Topaz self-host; serrucho; fondo MagicWB; `/` y `/v25/` intactos; bump **2.4.0**; tests MPA verdes | 0 | ✅ 2026-08-08 (branch `feat/amiga-scaffold`) |
|
| 177 |
+
| **2** | Shell layout desktop+mobile | Zonas mínimas (header / left / canvas-host / right o equivalente) con tokens; responsive OK o pregunta si falla | 1 |
|
| 178 |
+
| **3** | Chrome 2D mínimo | Paneles legibles “WB-ish” **sin** cosplay Q2-C (bordes/contraste MagicWB, no icon packs) | 2 |
|
| 179 |
+
| **4** | Wire Arithmetic (o panel primario legado) | CTA real vía `/api`; lista resultados; canvas aún stub OK | 3 |
|
| 180 |
+
| **5** | Canvas host + engine reuse | Montar scene existente en host amiga; POINTS startup; **sin** retocar shaders/look 3D | 4 |
|
| 181 |
+
| **6** | Compare / SAE / viz (según prioridad humana) | Port vertical de superficies críticas; mobile no roto | 5 |
|
| 182 |
+
| **7** | Polish + HF Space | Smoke desktop+mobile; CONTEXT/CHANGELOG; **publicar Space** con OK explícito | 6 |
|
| 183 |
+
|
| 184 |
+
### Hand-off (plantilla)
|
| 185 |
+
|
| 186 |
+
```markdown
|
| 187 |
+
### Hand-off — Slice N
|
| 188 |
+
- **Branch / commit:** …
|
| 189 |
+
- **Cómo probar:** `/amiga/` … ; smoke `/` y `/v25/` sin cambios visuales
|
| 190 |
+
- **Tests:** …
|
| 191 |
+
- **Hecho:** …
|
| 192 |
+
- **NO hecho:** …
|
| 193 |
+
- **Verificación aislamiento:** `git diff -- src/v25 v25` vacío; legado solo MPA shared si aplica
|
| 194 |
+
- **Riesgos / preguntas mobile:** …
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## 7. HF Space (último)
|
| 200 |
+
|
| 201 |
+
Solo cuando Slices 1–6 cumplan DoD y el humano diga OK:
|
| 202 |
+
|
| 203 |
+
1. Confirmar qué URL es default en Space (`/` vs `/amiga/` vs link). **Preguntar** si no está cerrado.
|
| 204 |
+
2. Seguir proceso existente de publish (`hf-space-cpu-demo.md` / force-push Space).
|
| 205 |
+
3. Documentar en CHANGELOG + README cómo abrir el skin Amiga.
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## 8. Epic futuro (no este doc)
|
| 210 |
+
|
| 211 |
+
**`amiga-workbench-cosplay.md`** (crear cuando se pida): Q2-C — iconos MagicWB/NewIcons, drawers, pointer, patterns de escritorio, gadgets pressable full. Depende de `/amiga` usable.
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
## 9. Smoke checklist (acumulativa)
|
| 216 |
+
|
| 217 |
+
1. `http://127.0.0.1:5173/` — legado igual.
|
| 218 |
+
2. `http://127.0.0.1:5173/v25/` — v25 igual (no tocado).
|
| 219 |
+
3. `http://127.0.0.1:5173/amiga/` — Topaz + MagicWB visibles.
|
| 220 |
+
4. `/amiga` sin slash → redirect a `/amiga/`.
|
| 221 |
+
5. Phone portrait + landscape corto: texto legible o issue abierto al humano.
|
| 222 |
+
6. Tras wire: Arithmetic/Compare no rompen contratos Top-10 / cosine.
|
| 223 |
+
|
| 224 |
+
---
|
| 225 |
+
|
| 226 |
+
## 10. Slice 0 — hecho al crear este plan
|
| 227 |
+
|
| 228 |
+
- [x] Decisiones Q1–Q10 cerradas
|
| 229 |
+
- [x] Este roadmap
|
| 230 |
+
- [x] Prompt agente
|
| 231 |
+
- [x] Entrada README roadmap
|
| 232 |
+
- [x] Lesson §6.0e aislamiento `/amiga` + v25 off-limits en este epic
|
| 233 |
+
|
| 234 |
+
### Hand-off — Slice 1
|
| 235 |
+
- **Branch / commit:** `feat/amiga-scaffold` (local; approval gate)
|
| 236 |
+
- **Cómo probar:** `npm run dev` → `http://127.0.0.1:5173/amiga/` (Topaz + gris MagicWB + titlebar azul + 8 swatches); smoke `/` y `/v25/` sin cambios de skin; `/amiga` sin slash → `/amiga/`
|
| 237 |
+
- **Tests:** `npm test` (incl. `amigaTokens`, `viteMpaInputs`)
|
| 238 |
+
- **Hecho:** MPA entry, tokens MagicWB, Topaz self-host GPL-FE, SemVer **2.4.0**, CONTEXT término Amiga Skin, lessons §6.0/§6.0e
|
| 239 |
+
- **NO hecho:** shell 5 zonas (Slice 2), wire API/canvas, HF Space
|
| 240 |
+
- **Verificación aislamiento:** `git diff -- src/v25 v25` vacío; legado solo version-tag Navbar + MPA shared
|
| 241 |
+
- **Riesgos / preguntas mobile:** Topaz 16px en phone portrait — revisar visualmente; si ilegible, preguntar (no bajar de múltiplos de 8 a ciegas)
|
src/amiga/assets/fonts/LICENSE.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Topaz (TrueType remake) — Multi Platform Amiga Fonts
|
| 2 |
+
Source: https://github.com/rewtnull/amigafonts (ttf/Topaz_a1200_v1.0.ttf)
|
| 3 |
+
|
| 4 |
+
License: GPL with Font Exception (GPL-FE)
|
| 5 |
+
https://www.gnu.org/licenses/gpl-faq.html#FontException
|
| 6 |
+
|
| 7 |
+
Topaz is © AmigaInc.
|
| 8 |
+
TrueType version © dMG/t!s^dS! (Trueschool / Divine Stylers), 2009.
|
| 9 |
+
|
| 10 |
+
Vendored here for the VHectorLab `/amiga` MagicWB skin (self-host; no CDN).
|
src/amiga/assets/fonts/Topaz_a1200_v1.0.ttf
ADDED
|
Binary file (24.3 kB). View file
|
|
|
src/amiga/main.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import './style.css';
|
| 2 |
+
import { PRODUCT_NAME, PRODUCT_VERSION } from './version.js';
|
| 3 |
+
|
| 4 |
+
document.body.classList.add('amiga-workbench');
|
| 5 |
+
|
| 6 |
+
const app = document.getElementById('app');
|
| 7 |
+
if (app) {
|
| 8 |
+
app.innerHTML = `
|
| 9 |
+
<div class="amiga-shell" data-amiga-shell>
|
| 10 |
+
<header class="amiga-titlebar">
|
| 11 |
+
<h1 class="amiga-titlebar__brand">${PRODUCT_NAME}</h1>
|
| 12 |
+
<span class="amiga-titlebar__meta">Amiga / MagicWB · v${PRODUCT_VERSION}</span>
|
| 13 |
+
</header>
|
| 14 |
+
<section class="amiga-panel" aria-label="Workbench scaffold">
|
| 15 |
+
<p>Topaz + Magic Workbench palette scaffold.</p>
|
| 16 |
+
<p>Parallel skin at /amiga/ — legacy / and /v25/ untouched.</p>
|
| 17 |
+
<ul class="amiga-swatches" aria-label="MagicWB pens">
|
| 18 |
+
<li data-pen="0" title="0 gray"></li>
|
| 19 |
+
<li data-pen="1" title="1 black"></li>
|
| 20 |
+
<li data-pen="2" title="2 white"></li>
|
| 21 |
+
<li data-pen="3" title="3 blue"></li>
|
| 22 |
+
<li data-pen="4" title="4 halfshadow"></li>
|
| 23 |
+
<li data-pen="5" title="5 halfshine"></li>
|
| 24 |
+
<li data-pen="6" title="6 tan"></li>
|
| 25 |
+
<li data-pen="7" title="7 peach"></li>
|
| 26 |
+
</ul>
|
| 27 |
+
</section>
|
| 28 |
+
</div>
|
| 29 |
+
`;
|
| 30 |
+
}
|
src/amiga/style.css
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import './tokens.css';
|
| 2 |
+
|
| 3 |
+
*,
|
| 4 |
+
*::before,
|
| 5 |
+
*::after {
|
| 6 |
+
box-sizing: border-box;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
html,
|
| 10 |
+
body {
|
| 11 |
+
margin: 0;
|
| 12 |
+
min-height: 100%;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
body.amiga-workbench {
|
| 16 |
+
font-family: var(--amiga-font);
|
| 17 |
+
font-size: var(--amiga-font-size);
|
| 18 |
+
line-height: var(--amiga-line-height);
|
| 19 |
+
letter-spacing: 0.5px;
|
| 20 |
+
color: var(--amiga-fg);
|
| 21 |
+
background-color: var(--amiga-bg);
|
| 22 |
+
|
| 23 |
+
/* Pixel / sawtooth — disable font smoothing */
|
| 24 |
+
-webkit-font-smoothing: none;
|
| 25 |
+
-moz-osx-font-smoothing: grayscale;
|
| 26 |
+
text-rendering: optimizeSpeed;
|
| 27 |
+
font-smooth: never;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
body.amiga-workbench img {
|
| 31 |
+
image-rendering: pixelated;
|
| 32 |
+
image-rendering: crisp-edges;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
#app {
|
| 36 |
+
min-height: 100vh;
|
| 37 |
+
min-height: 100dvh;
|
| 38 |
+
padding: 16px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.amiga-shell {
|
| 42 |
+
max-width: 960px;
|
| 43 |
+
margin: 0 auto;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.amiga-titlebar {
|
| 47 |
+
display: flex;
|
| 48 |
+
align-items: center;
|
| 49 |
+
justify-content: space-between;
|
| 50 |
+
gap: 16px;
|
| 51 |
+
background: var(--amiga-accent);
|
| 52 |
+
color: var(--mwb-white);
|
| 53 |
+
padding: 4px 8px;
|
| 54 |
+
margin-bottom: 0;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.amiga-titlebar__brand {
|
| 58 |
+
margin: 0;
|
| 59 |
+
font-size: 16px;
|
| 60 |
+
line-height: 20px;
|
| 61 |
+
font-weight: normal;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.amiga-titlebar__meta {
|
| 65 |
+
font-size: 16px;
|
| 66 |
+
line-height: 20px;
|
| 67 |
+
white-space: nowrap;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.amiga-panel {
|
| 71 |
+
background: var(--amiga-bg);
|
| 72 |
+
border: 2px solid var(--mwb-black);
|
| 73 |
+
border-top: none;
|
| 74 |
+
padding: 16px;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.amiga-panel p {
|
| 78 |
+
margin: 0 0 16px;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.amiga-panel p:last-child {
|
| 82 |
+
margin-bottom: 0;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.amiga-swatches {
|
| 86 |
+
display: flex;
|
| 87 |
+
flex-wrap: wrap;
|
| 88 |
+
gap: 8px;
|
| 89 |
+
margin: 16px 0 0;
|
| 90 |
+
padding: 0;
|
| 91 |
+
list-style: none;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.amiga-swatches li {
|
| 95 |
+
width: 32px;
|
| 96 |
+
height: 32px;
|
| 97 |
+
border: 2px solid var(--mwb-black);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.amiga-swatches li[data-pen='0'] { background: var(--mwb-gray); }
|
| 101 |
+
.amiga-swatches li[data-pen='1'] { background: var(--mwb-black); }
|
| 102 |
+
.amiga-swatches li[data-pen='2'] { background: var(--mwb-white); }
|
| 103 |
+
.amiga-swatches li[data-pen='3'] { background: var(--mwb-blue); }
|
| 104 |
+
.amiga-swatches li[data-pen='4'] { background: var(--mwb-halfshadow); }
|
| 105 |
+
.amiga-swatches li[data-pen='5'] { background: var(--mwb-halfshine); }
|
| 106 |
+
.amiga-swatches li[data-pen='6'] { background: var(--mwb-tan); }
|
| 107 |
+
.amiga-swatches li[data-pen='7'] { background: var(--mwb-peach); }
|
| 108 |
+
|
| 109 |
+
@media (max-width: 640px), ((max-height: 500px) and (orientation: landscape)) {
|
| 110 |
+
#app {
|
| 111 |
+
padding: 8px;
|
| 112 |
+
}
|
| 113 |
+
}
|
src/amiga/tokenValues.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Magic Workbench 8-pen palette + semantic aliases.
|
| 3 |
+
* Keep hex in sync with `tokens.css`.
|
| 4 |
+
* Source: MagicWB canonical RGB (Wikipedia / MagicWB docs).
|
| 5 |
+
*/
|
| 6 |
+
export const MWB_PENS = Object.freeze({
|
| 7 |
+
0: '#959595',
|
| 8 |
+
1: '#000000',
|
| 9 |
+
2: '#FFFFFF',
|
| 10 |
+
3: '#3B67A2',
|
| 11 |
+
4: '#7B7B7B',
|
| 12 |
+
5: '#AFAFAF',
|
| 13 |
+
6: '#AA907C',
|
| 14 |
+
7: '#FFA997',
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
export const AMIGA_TOKEN_HEX = Object.freeze({
|
| 18 |
+
bg: MWB_PENS[0],
|
| 19 |
+
fg: MWB_PENS[1],
|
| 20 |
+
white: MWB_PENS[2],
|
| 21 |
+
accent: MWB_PENS[3],
|
| 22 |
+
halfshadow: MWB_PENS[4],
|
| 23 |
+
halfshine: MWB_PENS[5],
|
| 24 |
+
tan: MWB_PENS[6],
|
| 25 |
+
peach: MWB_PENS[7],
|
| 26 |
+
fontSizePx: 16,
|
| 27 |
+
lineHeightPx: 20,
|
| 28 |
+
});
|
src/amiga/tokens.css
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Magic Workbench tokens — source of truth for `/amiga` chrome.
|
| 3 |
+
* Hex mirrored in `tokenValues.js` for tests.
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
@font-face {
|
| 7 |
+
font-family: 'TopazNew';
|
| 8 |
+
src: url('./assets/fonts/Topaz_a1200_v1.0.ttf') format('truetype');
|
| 9 |
+
font-weight: normal;
|
| 10 |
+
font-style: normal;
|
| 11 |
+
font-display: block;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
:root {
|
| 15 |
+
--mwb-gray: #959595;
|
| 16 |
+
--mwb-black: #000000;
|
| 17 |
+
--mwb-white: #ffffff;
|
| 18 |
+
--mwb-blue: #3b67a2;
|
| 19 |
+
--mwb-halfshadow: #7b7b7b;
|
| 20 |
+
--mwb-halfshine: #afafaf;
|
| 21 |
+
--mwb-tan: #aa907c;
|
| 22 |
+
--mwb-peach: #ffa997;
|
| 23 |
+
|
| 24 |
+
--amiga-bg: var(--mwb-gray);
|
| 25 |
+
--amiga-fg: var(--mwb-black);
|
| 26 |
+
--amiga-accent: var(--mwb-blue);
|
| 27 |
+
--amiga-font: 'TopazNew', monospace;
|
| 28 |
+
--amiga-font-size: 16px;
|
| 29 |
+
--amiga-line-height: 20px;
|
| 30 |
+
}
|
src/amiga/version.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** Product version for the `/amiga` MPA shell (sync with manifest on bump). */
|
| 2 |
+
export const PRODUCT_NAME = 'VHectorLab-3D';
|
| 3 |
+
export const PRODUCT_VERSION = '2.4.0';
|
src/ui/Navbar.js
CHANGED
|
@@ -53,7 +53,7 @@ export class Navbar {
|
|
| 53 |
<div class="logo-icon">🌐</div>
|
| 54 |
<div class="title-group">
|
| 55 |
<h1>VHectorLab <span class="accent-3d">3D</span></h1>
|
| 56 |
-
<span class="version-tag">v2.
|
| 57 |
</div>
|
| 58 |
</div>
|
| 59 |
|
|
|
|
| 53 |
<div class="logo-icon">🌐</div>
|
| 54 |
<div class="title-group">
|
| 55 |
<h1>VHectorLab <span class="accent-3d">3D</span></h1>
|
| 56 |
+
<span class="version-tag">v2.4.0</span>
|
| 57 |
</div>
|
| 58 |
</div>
|
| 59 |
|
tests/amigaTokens.test.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import { AMIGA_TOKEN_HEX, MWB_PENS } from '../src/amiga/tokenValues.js';
|
| 3 |
+
import { PRODUCT_VERSION } from '../src/amiga/version.js';
|
| 4 |
+
|
| 5 |
+
describe('MagicWB pens', () => {
|
| 6 |
+
it('exposes the canonical 8-pen palette', () => {
|
| 7 |
+
expect(MWB_PENS).toEqual({
|
| 8 |
+
0: '#959595',
|
| 9 |
+
1: '#000000',
|
| 10 |
+
2: '#FFFFFF',
|
| 11 |
+
3: '#3B67A2',
|
| 12 |
+
4: '#7B7B7B',
|
| 13 |
+
5: '#AFAFAF',
|
| 14 |
+
6: '#AA907C',
|
| 15 |
+
7: '#FFA997',
|
| 16 |
+
});
|
| 17 |
+
});
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
describe('amiga semantic tokens', () => {
|
| 21 |
+
it('maps bg/fg/accent to MagicWB pens', () => {
|
| 22 |
+
expect(AMIGA_TOKEN_HEX.bg).toBe(MWB_PENS[0]);
|
| 23 |
+
expect(AMIGA_TOKEN_HEX.fg).toBe(MWB_PENS[1]);
|
| 24 |
+
expect(AMIGA_TOKEN_HEX.accent).toBe(MWB_PENS[3]);
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
it('uses pixel-perfect 16/20 typography sizes', () => {
|
| 28 |
+
expect(AMIGA_TOKEN_HEX.fontSizePx).toBe(16);
|
| 29 |
+
expect(AMIGA_TOKEN_HEX.lineHeightPx).toBe(20);
|
| 30 |
+
expect(AMIGA_TOKEN_HEX.fontSizePx % 8).toBe(0);
|
| 31 |
+
});
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
describe('amiga product version', () => {
|
| 35 |
+
it('starts the 2.4.x line', () => {
|
| 36 |
+
expect(PRODUCT_VERSION).toMatch(/^2\.4\.\d+$/);
|
| 37 |
+
});
|
| 38 |
+
});
|
tests/viteMpaInputs.test.js
CHANGED
|
@@ -3,12 +3,13 @@ import { describe, expect, it } from 'vitest';
|
|
| 3 |
import { getViteInputs, mpaTrailingSlashLocation } from '../vite.mpa.js';
|
| 4 |
|
| 5 |
describe('getViteInputs', () => {
|
| 6 |
-
it('includes legacy root and
|
| 7 |
const root = path.resolve('/tmp/vhectorlab-fixture');
|
| 8 |
const inputs = getViteInputs(root);
|
| 9 |
expect(inputs).toEqual({
|
| 10 |
main: path.resolve(root, 'index.html'),
|
| 11 |
v25: path.resolve(root, 'v25/index.html'),
|
|
|
|
| 12 |
});
|
| 13 |
});
|
| 14 |
});
|
|
@@ -18,13 +19,20 @@ describe('mpaTrailingSlashLocation', () => {
|
|
| 18 |
expect(mpaTrailingSlashLocation('/v25')).toBe('/v25/');
|
| 19 |
});
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
it('preserves query string', () => {
|
| 22 |
expect(mpaTrailingSlashLocation('/v25?x=1')).toBe('/v25/?x=1');
|
|
|
|
| 23 |
});
|
| 24 |
|
| 25 |
-
it('leaves
|
| 26 |
expect(mpaTrailingSlashLocation('/v25/')).toBeNull();
|
|
|
|
| 27 |
expect(mpaTrailingSlashLocation('/')).toBeNull();
|
| 28 |
expect(mpaTrailingSlashLocation('/v25/index.html')).toBeNull();
|
|
|
|
| 29 |
});
|
| 30 |
});
|
|
|
|
| 3 |
import { getViteInputs, mpaTrailingSlashLocation } from '../vite.mpa.js';
|
| 4 |
|
| 5 |
describe('getViteInputs', () => {
|
| 6 |
+
it('includes legacy root, v25, and amiga MPA HTML entries', () => {
|
| 7 |
const root = path.resolve('/tmp/vhectorlab-fixture');
|
| 8 |
const inputs = getViteInputs(root);
|
| 9 |
expect(inputs).toEqual({
|
| 10 |
main: path.resolve(root, 'index.html'),
|
| 11 |
v25: path.resolve(root, 'v25/index.html'),
|
| 12 |
+
amiga: path.resolve(root, 'amiga/index.html'),
|
| 13 |
});
|
| 14 |
});
|
| 15 |
});
|
|
|
|
| 19 |
expect(mpaTrailingSlashLocation('/v25')).toBe('/v25/');
|
| 20 |
});
|
| 21 |
|
| 22 |
+
it('redirects bare /amiga to /amiga/', () => {
|
| 23 |
+
expect(mpaTrailingSlashLocation('/amiga')).toBe('/amiga/');
|
| 24 |
+
});
|
| 25 |
+
|
| 26 |
it('preserves query string', () => {
|
| 27 |
expect(mpaTrailingSlashLocation('/v25?x=1')).toBe('/v25/?x=1');
|
| 28 |
+
expect(mpaTrailingSlashLocation('/amiga?x=1')).toBe('/amiga/?x=1');
|
| 29 |
});
|
| 30 |
|
| 31 |
+
it('leaves trailing-slash MPA paths and legacy / alone', () => {
|
| 32 |
expect(mpaTrailingSlashLocation('/v25/')).toBeNull();
|
| 33 |
+
expect(mpaTrailingSlashLocation('/amiga/')).toBeNull();
|
| 34 |
expect(mpaTrailingSlashLocation('/')).toBeNull();
|
| 35 |
expect(mpaTrailingSlashLocation('/v25/index.html')).toBeNull();
|
| 36 |
+
expect(mpaTrailingSlashLocation('/amiga/index.html')).toBeNull();
|
| 37 |
});
|
| 38 |
});
|
vite.mpa.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
| 1 |
import path from 'node:path';
|
| 2 |
|
| 3 |
/**
|
| 4 |
-
* Vite multi-page inputs: legacy `/` + parallel
|
| 5 |
-
* Keep keys stable — build emits `dist/
|
| 6 |
*
|
| 7 |
* @param {string} rootDir Absolute project root (repo cwd).
|
| 8 |
-
* @returns {{ main: string, v25: string }}
|
| 9 |
*/
|
| 10 |
export function getViteInputs(rootDir) {
|
| 11 |
return {
|
| 12 |
main: path.resolve(rootDir, 'index.html'),
|
| 13 |
v25: path.resolve(rootDir, 'v25/index.html'),
|
|
|
|
| 14 |
};
|
| 15 |
}
|
| 16 |
|
| 17 |
/**
|
| 18 |
-
* Vite SPA fallback treats
|
| 19 |
* Redirect bare MPA dirs to their trailing-slash URL.
|
| 20 |
*
|
| 21 |
* @param {string} urlPath `req.url` (path + optional query)
|
| 22 |
* @param {string[]} mpaDirs Absolute path prefixes without trailing slash
|
| 23 |
* @returns {string | null} Location header value, or null if no redirect
|
| 24 |
*/
|
| 25 |
-
export function mpaTrailingSlashLocation(urlPath, mpaDirs = ['/v25']) {
|
| 26 |
const qIndex = urlPath.indexOf('?');
|
| 27 |
const pathname = qIndex === -1 ? urlPath : urlPath.slice(0, qIndex);
|
| 28 |
const query = qIndex === -1 ? '' : urlPath.slice(qIndex);
|
|
@@ -32,8 +33,8 @@ export function mpaTrailingSlashLocation(urlPath, mpaDirs = ['/v25']) {
|
|
| 32 |
return null;
|
| 33 |
}
|
| 34 |
|
| 35 |
-
/** Vite plugin: 302
|
| 36 |
-
export function mpaTrailingSlashRedirect(mpaDirs = ['/v25']) {
|
| 37 |
const attach = (middlewares) => {
|
| 38 |
middlewares.use((req, res, next) => {
|
| 39 |
const location = mpaTrailingSlashLocation(req.url || '', mpaDirs);
|
|
|
|
| 1 |
import path from 'node:path';
|
| 2 |
|
| 3 |
/**
|
| 4 |
+
* Vite multi-page inputs: legacy `/` + parallel skins `/v25/` + `/amiga/`.
|
| 5 |
+
* Keep keys stable — build emits matching `dist/.../index.html` paths.
|
| 6 |
*
|
| 7 |
* @param {string} rootDir Absolute project root (repo cwd).
|
| 8 |
+
* @returns {{ main: string, v25: string, amiga: string }}
|
| 9 |
*/
|
| 10 |
export function getViteInputs(rootDir) {
|
| 11 |
return {
|
| 12 |
main: path.resolve(rootDir, 'index.html'),
|
| 13 |
v25: path.resolve(rootDir, 'v25/index.html'),
|
| 14 |
+
amiga: path.resolve(rootDir, 'amiga/index.html'),
|
| 15 |
};
|
| 16 |
}
|
| 17 |
|
| 18 |
/**
|
| 19 |
+
* Vite SPA fallback treats bare MPA dirs (no slash) as unknown → serves legacy `index.html`.
|
| 20 |
* Redirect bare MPA dirs to their trailing-slash URL.
|
| 21 |
*
|
| 22 |
* @param {string} urlPath `req.url` (path + optional query)
|
| 23 |
* @param {string[]} mpaDirs Absolute path prefixes without trailing slash
|
| 24 |
* @returns {string | null} Location header value, or null if no redirect
|
| 25 |
*/
|
| 26 |
+
export function mpaTrailingSlashLocation(urlPath, mpaDirs = ['/v25', '/amiga']) {
|
| 27 |
const qIndex = urlPath.indexOf('?');
|
| 28 |
const pathname = qIndex === -1 ? urlPath : urlPath.slice(0, qIndex);
|
| 29 |
const query = qIndex === -1 ? '' : urlPath.slice(qIndex);
|
|
|
|
| 33 |
return null;
|
| 34 |
}
|
| 35 |
|
| 36 |
+
/** Vite plugin: 302 bare MPA dirs → trailing slash (dev + preview). */
|
| 37 |
+
export function mpaTrailingSlashRedirect(mpaDirs = ['/v25', '/amiga']) {
|
| 38 |
const attach = (middlewares) => {
|
| 39 |
middlewares.use((req, res, next) => {
|
| 40 |
const location = mpaTrailingSlashLocation(req.url || '', mpaDirs);
|