diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 11e3435..a6127ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -57,7 +57,7 @@ Requise pour toute modification de documentation : Requise si la PR ne concerne pas uniquement la documentation : - [ ] `npm ci --no-audit --no-fund` réussit sans modifier le lockfile. -- [ ] `npm run check` réussit. +- [ ] `npm run check` réussit, y compris le build et les tests du GUI. - [ ] Aucun changement involontaire n'apparaît dans les profils générés. - [ ] Le GUI a été testé sans erreur ni avertissement inattendu dans la console, s'il est affecté. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index b49b647..1ff0067 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,6 +22,9 @@ jobs: with: node-version: 22 cache: npm + cache-dependency-path: | + package-lock.json + prototype/package-lock.json - name: Install locked dependencies run: npm ci --no-audit --no-fund - name: Validate manifests, documentation and tests diff --git a/.gitignore b/.gitignore index a4f6a31..df31c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,13 @@ /work/ node_modules/ *.log + +# Local GUI builds and design-review artifacts +/prototype/dist/ +/prototype/audit-ux-ui/ +/prototype/design-reference/ +/prototype/qa/ +/prototype/design-qa.md +/prototype/public/assets/ai-controller-generated*.png +/prototype/public/assets/ai-controller-glass-v3.png +/prototype/public/assets/glass-background-v1.png diff --git a/README.md b/README.md index 78eec6d..576c07f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Le dépôt contient désormais : - un manifeste portable V1 et des schémas réutilisables ; - un mapping physique Claude avec couleur, cadran, joystick et AppSense ; - une représentation SVG originale du Codex Micro ; +- un configurateur graphique local lancé par `npm run configure` ; - un outil Node.js de diagnostic, inventaire, sauvegarde, dry-run, sanitation et rollback ; - un générateur local de profile Input `0.17.3` qui préserve le layer natif et @@ -44,9 +45,11 @@ L'analyse du package officiel confirme des commandes **Import layer** et attendue. Le vrai fichier Claude n'est volontairement pas fabriqué : ses objets internes doivent provenir d'un export réel du Codex Micro. -Le preset est `hardware-observed` : le générateur a été validé sur un export -Input `0.17.3`, mais le round-trip d'un artefact `*-layer.json` et la checklist -matérielle complète restent ouverts. +Le manifeste V1 est `hardware-observed` : le générateur a été validé sur un +export Input `0.17.3`, mais le round-trip d'un artefact `*-layer.json` et la +checklist matérielle complète restent ouverts. Le fichier logique +`macos.example.json` reste séparément `proposal-not-applied` : ce n'est pas un +preset universel à importer. ## Mapping Claude proposé @@ -69,6 +72,10 @@ AppSense et `Auto detect`. ![Mapping physique Claude](profiles/claude-shortcuts/assets/layout.svg) +Les raccourcis globaux de Claude restent hors du layer AppSense : double appui +sur Option pour la saisie rapide et Verr. Maj. pour la dictée globale. Ils +doivent rester disponibles quand une autre application est au premier plan. + ## Ce qui est interdit par défaut - Retour/Entrée et envoi de message ; @@ -93,6 +100,23 @@ actif. Voir la [matrice de compatibilité](docs/compatibility.md) pour distinguer les faits, tests de fixture et validations matérielles manquantes. +## Configurateur graphique + +```sh +npm run configure +``` + +La première ouverture peut installer les dépendances verrouillées du GUI, puis +lance l'interface uniquement sur `127.0.0.1` et ouvre le navigateur. + +Le parcours demande un export officiel Work Louder Input qui contient +exactement un layer `Claude`, hors index `0`, déjà lié à Claude Desktop avec +AppSense. L'utilisateur peut vérifier et personnaliser les quatre touches, le +cadran et le joystick, puis télécharger son propre +`Claude-macOS-profile.json`. Le fichier source, le layer natif, les autres +layers et les autres liens AppSense sont conservés. Le JSON logique public +n'est jamais présenté comme directement importable. + ## Démarrage sans modification Prérequis : Node.js 18 ou version ultérieure. Les dépendances de validation @@ -168,13 +192,17 @@ profiles/ assets/layout.svg aperçu original artifacts/ futur export officiel assaini scripts/ + configure.mjs lancement local du configurateur graphique + prepare-gui.mjs préparation verrouillée des dépendances du GUI build-input-profile.mjs génération locale du profile importable input-layer.mjs diagnostic, sauvegarde et installation guidée + lib/ fonctions de validation et de preset + validate-profile.mjs contrat logique Claude historique + validate-presets.mjs invariants de la bibliothèque +prototype/ + src/ interface locale du configurateur shared/ input-profile.mjs transformation canonique partagée avec le GUI - lib/ fonctions testables - validate-profile.mjs contrat Claude historique - validate-presets.mjs invariants de la bibliothèque tests/ input-layer.test.mjs sauvegarde, rollback, sanitation et idempotence docs/ diff --git a/SECURITY.md b/SECURITY.md index 0c3943c..67fa8e1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -55,13 +55,17 @@ Tant que la piste n'est pas auditée : ## Dépendances et scripts Le projet ne transmet pas les profils ou exports Work Louder à un service -distant. Le générateur et les validateurs les traitent localement. +distant. Le configurateur, le générateur et les validateurs les traitent +localement, sans télémétrie applicative. Une connexion réseau peut toutefois être utilisée par `npm ci --no-audit --no-fund` pour télécharger depuis le registre configuré les -versions verrouillées dans `package-lock.json`. Toute nouvelle dépendance ou -communication distante doit être justifiée, verrouillée, documentée et auditée -avant publication. +versions verrouillées dans `package-lock.json`. Lors de leur première +exécution, `npm run configure` et `npm run check` peuvent de même lancer +`npm ci --ignore-scripts` dans `prototype/` si les dépendances du GUI sont +absentes. Une fois installées, le traitement des profils reste local. Toute +nouvelle dépendance ou communication distante doit être justifiée, verrouillée, +documentée et auditée avant publication. ## Sauvegarde et restauration diff --git a/docs/codex-micro/configuration.md b/docs/codex-micro/configuration.md index e6a8114..319e5f7 100644 --- a/docs/codex-micro/configuration.md +++ b/docs/codex-micro/configuration.md @@ -15,6 +15,11 @@ Cette intégration utilise les raccourcis HID et AppSense. Elle ne dépend pas du protocole expérimental Hardware Buddy. +Le manifeste et le mapping V1 décrivent l'état observé du générateur. Le fichier +logique `macos.example.json` reste `proposal-not-applied` et ne doit pas être +importé tel quel. `npm run configure` fabrique uniquement un profile personnel +à partir de l'export officiel de l'utilisateur. + ## Préservation obligatoire Le preset et l'outil imposent les règles suivantes : @@ -83,10 +88,12 @@ obligatoire : il ne doit pas être supposé à partir de la seule documentation. - `git push` ; - déploiement ; - terminal, shell ou commande destructive ; -- raccourci global Saisie rapide. +- raccourcis globaux Saisie rapide et Dictée. Les raccourcis globaux sont volontairement hors du layer AppSense : ils doivent -rester utilisables quand une autre application est au premier plan. +rester utilisables quand une autre application est au premier plan. Sur la +configuration documentée, il s'agit du double appui sur Option pour la saisie +rapide et de Verr. Maj. pour la dictée globale. ## Partage officiel observé diff --git a/docs/installation.md b/docs/installation.md index 20b788e..cf08eb8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -8,6 +8,29 @@ layer dans une copie et produit un nouveau `*-profile.json`. Il ne clique jamais à votre place, ne lance pas `Reset settings`, ne flashe aucun firmware et ne modifie aucun raccourci système. +## Parcours GUI recommandé + +Depuis la racine du dépôt : + +```sh +npm run configure +``` + +La première ouverture peut préparer les dépendances verrouillées de +`prototype/`. Le configurateur s'ouvre ensuite localement dans le navigateur. + +1. exporter le profile actif depuis Work Louder Input ; +2. déposer ce `*-profile.json` dans le configurateur ; +3. vérifier l'unique layer `Claude`, son lien AppSense et l'index natif `0` ; +4. personnaliser les quatre touches, le cadran et le joystick ; +5. télécharger `Claude-macOS-profile.json` ; +6. l'importer avec **Add New**, sans remplacer le profile source. + +La génération refuse un mauvais appareil, un layer Claude absent ou dupliqué, +une cible à l'index `0` et un lien AppSense manquant. Elle conserve le layer +natif, les autres layers, les autres profils et les autres liens AppSense. Le +reste de ce guide décrit le même parcours avec les contrôles CLI détaillés. + ## 1. Vérifier le dépôt Depuis la copie locale : diff --git a/package.json b/package.json index 8625869..2327152 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,14 @@ "node": ">=18" }, "scripts": { + "configure": "node scripts/configure.mjs", "build:profile": "node scripts/build-input-profile.mjs", "validate": "node scripts/validate-profile.mjs && node scripts/validate-presets.mjs && node scripts/check-doc-links.mjs", - "test": "node --test", - "check": "npm run validate && npm test" + "test": "node --test tests/*.test.mjs", + "prepare:gui": "node scripts/prepare-gui.mjs", + "build:gui": "npm run prepare:gui && npm --prefix prototype run build", + "test:gui": "npm --prefix prototype run test:sites", + "check": "npm run validate && npm test && npm run build:gui && npm run test:gui" }, "devDependencies": { "ajv": "8.17.1" diff --git a/profiles/claude-shortcuts/README.md b/profiles/claude-shortcuts/README.md index c356915..d15bce8 100644 --- a/profiles/claude-shortcuts/README.md +++ b/profiles/claude-shortcuts/README.md @@ -16,16 +16,18 @@ lié avec AppSense et distinct du layer Codex natif protégé à l'index `0`. - validation complète de toutes les commandes, de la perte de focus et du rollback matériel : **encore requise**. -Le statut reste `hardware-observed`. Aucun artefact layer ne doit être présenté -comme prêt à l'emploi avant le round-trip matériel et la vérification de son -SHA-256. +Le manifeste V1 reste `hardware-observed`. Le contrat logique +`macos.example.json` reste séparément `proposal-not-applied`. Aucun des deux ne +doit être présenté comme un fichier universel prêt à importer, et aucun +artefact layer ne doit être publié avant le round-trip matériel et la +vérification de son SHA-256. ## Fichiers ```text manifest.json identité, compatibilité, preuve et installation mapping.json mapping physique et règles de sécurité -macos.example.json contrat logique historique, aligné sur la V1 +macos.example.json proposition logique V0 non importable schema.json schéma du contrat historique assets/layout.svg représentation originale du clavier artifacts/README.md porte d'entrée du futur export officiel @@ -64,6 +66,20 @@ com.anthropic.claudefordesktop La politique exige exactement un layer Claude et conserve son `linkedAppId`. L'outil ne crée pas de second lien et ne modifie jamais les autres liens. +## Configurateur local + +```sh +npm run configure +``` + +Le GUI charge un export officiel appartenant à l'utilisateur, permet de +personnaliser les contrôles sûrs et génère un nouveau +`Claude-macOS-profile.json`. Seul ce fichier personnel est destiné au flux +**Add New** d'Input ; le contrat logique du dépôt ne l'est pas. + +Le double appui sur Option pour la saisie rapide et Verr. Maj. pour la dictée +globale restent hors du layer AppSense. + ## Sécurité Le validateur interdit dans les contrôles actifs : Retour/Entrée, envoi, diff --git a/profiles/claude-shortcuts/macos.example.json b/profiles/claude-shortcuts/macos.example.json index 321c1c7..73b7931 100644 --- a/profiles/claude-shortcuts/macos.example.json +++ b/profiles/claude-shortcuts/macos.example.json @@ -1,7 +1,7 @@ { "$schema": "./schema.json", - "id": "claude-desktop-macos-codex-micro-v1", - "status": "hardware-observed", + "id": "claude-desktop-macos-codex-micro-v0", + "status": "proposal-not-applied", "target": { "platform": "macOS", "appBundleId": "com.anthropic.claudefordesktop", @@ -14,7 +14,7 @@ "preservation": { "mode": "update-only-existing-layer", "protectedLayerIndexes": [0], - "unlistedControls": "no-action-in-new-layer", + "unlistedControls": "no-action-in-target-layer", "touchLayerControl": "reserved", "otherLayers": "unchanged", "otherProfiles": "unchanged", @@ -128,6 +128,11 @@ "action": "quick-entry", "documentedShortcut": "DoublePressOption", "reason": "Must remain reachable while another application is in the foreground." + }, + { + "action": "voice-dictation", + "documentedShortcut": "CapsLock", + "reason": "Claude global dictation must remain independent from the AppSense-linked layer." } ], "excludedByDefault": [ diff --git a/profiles/claude-shortcuts/schema.json b/profiles/claude-shortcuts/schema.json index aabc02c..704de63 100644 --- a/profiles/claude-shortcuts/schema.json +++ b/profiles/claude-shortcuts/schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/thannous/claude-codex-micro/raw/main/profiles/claude-shortcuts/schema.json", - "title": "Codex Micro Claude shortcut profile v1", + "title": "Codex Micro Claude logical shortcut proposal v0", "type": "object", "additionalProperties": false, "required": [ @@ -19,8 +19,8 @@ ], "properties": { "$schema": { "type": "string" }, - "id": { "const": "claude-desktop-macos-codex-micro-v1" }, - "status": { "const": "hardware-observed" }, + "id": { "const": "claude-desktop-macos-codex-micro-v0" }, + "status": { "const": "proposal-not-applied" }, "target": { "type": "object", "additionalProperties": false, @@ -68,7 +68,7 @@ "minContains": 1, "uniqueItems": true }, - "unlistedControls": { "const": "no-action-in-new-layer" }, + "unlistedControls": { "const": "no-action-in-target-layer" }, "touchLayerControl": { "const": "reserved" }, "otherLayers": { "const": "unchanged" }, "otherProfiles": { "const": "unchanged" }, @@ -168,16 +168,56 @@ }, "outsideAppLinkedLayer": { "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["action", "documentedShortcut", "reason"], - "properties": { - "action": { "const": "quick-entry" }, - "documentedShortcut": { "const": "DoublePressOption" }, - "reason": { "type": "string", "minLength": 1 } + "minItems": 2, + "maxItems": 2, + "uniqueItems": true, + "allOf": [ + { + "contains": { + "type": "object", + "required": ["action"], + "properties": { + "action": { "const": "quick-entry" } + } + }, + "minContains": 1, + "maxContains": 1 + }, + { + "contains": { + "type": "object", + "required": ["action"], + "properties": { + "action": { "const": "voice-dictation" } + } + }, + "minContains": 1, + "maxContains": 1 } + ], + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["action", "documentedShortcut", "reason"], + "properties": { + "action": { "const": "quick-entry" }, + "documentedShortcut": { "const": "DoublePressOption" }, + "reason": { "type": "string", "minLength": 1 } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["action", "documentedShortcut", "reason"], + "properties": { + "action": { "const": "voice-dictation" }, + "documentedShortcut": { "const": "CapsLock" }, + "reason": { "type": "string", "minLength": 1 } + } + } + ] } }, "excludedByDefault": { diff --git a/prototype/.npmrc b/prototype/.npmrc new file mode 100644 index 0000000..1223b5c --- /dev/null +++ b/prototype/.npmrc @@ -0,0 +1,2 @@ +fund=false +audit=false diff --git a/prototype/.openai/hosting.json b/prototype/.openai/hosting.json new file mode 100644 index 0000000..47c28cb --- /dev/null +++ b/prototype/.openai/hosting.json @@ -0,0 +1,4 @@ +{ + "d1": null, + "r2": null +} diff --git a/prototype/AGENTS.md b/prototype/AGENTS.md new file mode 100644 index 0000000..f42028c --- /dev/null +++ b/prototype/AGENTS.md @@ -0,0 +1,17 @@ +# Prototype Instructions + +Run the local server yourself and open the preview in the browser available to this environment. Do not give the user server-start instructions when you can run it. + +Before making substantial visual changes, use the Product Design plugin's `get-context` skill when the visual source is unclear or no longer matches the current goal. When the user gives durable prototype-specific design feedback, preferences, or decisions, record them in `AGENTS.md`. + +When implementing from a selected generated mock, treat that image as the source of truth for layout, component anatomy, density, spacing, color, typography, visible content, and hierarchy. + +Build app UI in `src/`. Keep `.openai/hosting.json`, `worker/index.js`, `scripts/prepare-sites-build.mjs`, and `tests/sites-worker.test.mjs` intact so the same local prototype can be handed to Sites. Before a Sites handoff, run `npm run build` and `npm run test:sites`; the build must leave `dist/client/index.html`, `dist/server/index.js`, and `dist/.openai/hosting.json`. + +The repository-level command `npm run configure` is the canonical entry point for end users. It must prepare missing GUI dependencies, start the local prototype, and open it in the browser. + +The default Claude mapping uses `Cmd+N` for a new session, user-confirmed `Cmd+D` for in-app voice, `Cmd+Shift+D` for the diff pane, and `Esc` to stop a response. Quick Entry (`double Option`) and its global dictation shortcut (`Caps Lock`) remain separate from the AppSense layer. + +The logical Claude V0 JSON remains `proposal-not-applied`. The GUI may generate a genuinely importable, personal Work Louder Input profile only from the user's own official Codex Micro backup. Validate the backup, preserve the native layer, other layers, and AppSense link, and create a separate `Claude macOS` profile. Never present that generated file as a universal public preset or a live agent integration. + +Keep the GUI focused on the user's primary job: select a physical control, assign a Claude action, review the mapping, and export it. Preserve the warm glass visual direction of the original single-screen configurator. Safety and proof-level caveats should stay concise and contextual; do not turn the interface into a compliance journey or a documentation dashboard. diff --git a/prototype/index.html b/prototype/index.html new file mode 100644 index 0000000..51af54b --- /dev/null +++ b/prototype/index.html @@ -0,0 +1,14 @@ + + + + + + + + Configurer Claude | Codex Micro + + +
+ + + diff --git a/prototype/package-lock.json b/prototype/package-lock.json new file mode 100644 index 0000000..610df70 --- /dev/null +++ b/prototype/package-lock.json @@ -0,0 +1,1677 @@ +{ + "name": "prototype", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prototype", + "version": "0.0.0", + "dependencies": { + "@vitejs/plugin-react": "5.0.4", + "lucide-react": "^1.27.0", + "react": "19.2.0", + "react-dom": "19.2.0", + "vite": "6.4.2" + }, + "devDependencies": {} + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", + "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", + "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.38", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.42", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", + "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001800", + "electron-to-chromium": "^1.5.387", + "node-releases": "^2.0.50", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001803", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", + "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.27.0.tgz", + "integrity": "sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + } + } +} diff --git a/prototype/package.json b/prototype/package.json new file mode 100644 index 0000000..d5914c9 --- /dev/null +++ b/prototype/package.json @@ -0,0 +1,19 @@ +{ + "name": "prototype", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build && node scripts/prepare-sites-build.mjs", + "preview": "vite preview", + "test:sites": "node --test tests/sites-worker.test.mjs" + }, + "dependencies": { + "@vitejs/plugin-react": "5.0.4", + "lucide-react": "^1.27.0", + "react": "19.2.0", + "react-dom": "19.2.0", + "vite": "6.4.2" + } +} diff --git a/prototype/public/assets/ai-controller-claude-v1.png b/prototype/public/assets/ai-controller-claude-v1.png new file mode 100644 index 0000000..21cd151 Binary files /dev/null and b/prototype/public/assets/ai-controller-claude-v1.png differ diff --git a/prototype/public/assets/claude-glass-background-v1.png b/prototype/public/assets/claude-glass-background-v1.png new file mode 100644 index 0000000..12348a2 Binary files /dev/null and b/prototype/public/assets/claude-glass-background-v1.png differ diff --git a/prototype/scripts/prepare-sites-build.mjs b/prototype/scripts/prepare-sites-build.mjs new file mode 100644 index 0000000..86a9e8c --- /dev/null +++ b/prototype/scripts/prepare-sites-build.mjs @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const dist = path.join(root, "dist"); +const index = path.join(dist, "client", "index.html"); +const worker = path.join(root, "worker", "index.js"); +const hosting = path.join(root, ".openai", "hosting.json"); + +for (const file of [index, worker, hosting]) { + if (!existsSync(file)) throw new Error("Missing Sites build input: " + file); +} + +mkdirSync(path.join(dist, "server"), { recursive: true }); +mkdirSync(path.join(dist, ".openai"), { recursive: true }); +copyFileSync(worker, path.join(dist, "server", "index.js")); +copyFileSync(hosting, path.join(dist, ".openai", "hosting.json")); + +console.log("Prepared Sites build: dist/server/index.js and dist/.openai/hosting.json"); diff --git a/prototype/src/App.jsx b/prototype/src/App.jsx new file mode 100644 index 0000000..92a6de4 --- /dev/null +++ b/prototype/src/App.jsx @@ -0,0 +1,542 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { + ArrowDownToLine, + Check, + ChevronRight, + Command, + Diff, + FileJson, + Mic, + MousePointer2, + Move, + RotateCcw, + ShieldCheck, + SlidersHorizontal, + Square, + X, +} from "lucide-react"; +import { + buildInputProfile, + inspectInputProfile, +} from "../../shared/input-profile.mjs"; + +const CONTROLS = [ + { + id: "joystick", + label: "Joystick", + shortLabel: "NAV", + type: "joystick", + x: 22.3, + y: 19.7, + w: 13.3, + h: 13.6, + }, + { + id: "wheel", + label: "Molette", + shortLabel: "SCROLL", + type: "dial", + x: 65.7, + y: 20.1, + w: 12.2, + h: 13.2, + }, + { + id: "key-1", + label: "Touche 1", + shortLabel: "K1", + type: "key", + x: 21.9, + y: 49.4, + w: 13.7, + h: 12.7, + }, + { + id: "key-2", + label: "Touche 2", + shortLabel: "K2", + type: "key", + x: 35.5, + y: 49.4, + w: 13.7, + h: 12.7, + }, + { + id: "key-3", + label: "Touche 3", + shortLabel: "K3", + type: "key", + x: 50.6, + y: 49.4, + w: 13.7, + h: 12.7, + }, + { + id: "key-4", + label: "Touche 4", + shortLabel: "K4", + type: "key", + x: 64.9, + y: 49.4, + w: 13.7, + h: 12.7, + }, +]; + +const ACTIONS = { + navigation: { + id: "navigation", + label: "Navigation", + description: "Se déplacer avec les flèches", + shortcut: "↑ → ↓ ←", + icon: Move, + controlTypes: ["joystick"], + exportLabel: "NAV", + action: { + type: "directional-keys", + up: "ArrowUp", + right: "ArrowRight", + down: "ArrowDown", + left: "ArrowLeft", + }, + }, + scroll: { + id: "scroll", + label: "Défilement", + description: "Faire défiler la conversation page par page", + shortcut: "Page ↑ Page ↓", + icon: MousePointer2, + controlTypes: ["dial"], + exportLabel: "SCROLL", + action: { type: "page-keys", counterClockwise: "PageUp", clockwise: "PageDown" }, + }, + newSession: { + id: "newSession", + label: "Nouvelle session", + description: "Ouvrir une nouvelle session Claude", + shortcut: "⌘ N", + icon: Command, + controlTypes: ["key"], + exportLabel: "NEW", + action: { type: "shortcut", keys: ["Command", "N"] }, + }, + voice: { + id: "voice", + label: "Mode vocal", + description: "Activer la conversation vocale", + shortcut: "⌘ D", + icon: Mic, + controlTypes: ["key"], + exportLabel: "VOICE", + action: { type: "shortcut", keys: ["Command", "D"] }, + }, + diff: { + id: "diff", + label: "Afficher le diff", + description: "Ouvrir ou masquer le panneau diff", + shortcut: "⌘ ⇧ D", + icon: Diff, + controlTypes: ["key"], + exportLabel: "DIFF", + action: { type: "shortcut", keys: ["Command", "Shift", "D"] }, + }, + stop: { + id: "stop", + label: "Arrêter la réponse", + description: "Interrompre la réponse en cours", + shortcut: "Esc", + icon: Square, + controlTypes: ["key"], + exportLabel: "ESC", + action: { type: "shortcut", keys: ["Escape"] }, + }, + none: { + id: "none", + label: "Aucune action", + description: "Laisser ce contrôle libre", + shortcut: "Non assigné", + icon: X, + controlTypes: ["key", "dial", "joystick"], + exportLabel: "NONE", + action: { type: "none" }, + }, +}; + +const DEFAULT_MAPPING = { + joystick: "navigation", + wheel: "scroll", + "key-1": "newSession", + "key-2": "voice", + "key-3": "diff", + "key-4": "stop", +}; + +export function App() { + const [mapping, setMapping] = useState(DEFAULT_MAPPING); + const [selectedControlId, setSelectedControlId] = useState("key-1"); + const [panelOpen, setPanelOpen] = useState(false); + const [toast, setToast] = useState(""); + const [sourceProfile, setSourceProfile] = useState(null); + const [sourceFileName, setSourceFileName] = useState(""); + const [profileInfo, setProfileInfo] = useState(null); + const [profileError, setProfileError] = useState(""); + const dialogRef = useRef(null); + const closeButtonRef = useRef(null); + const returnFocusRef = useRef(null); + const profileInputRef = useRef(null); + + const selectedControl = useMemo( + () => CONTROLS.find((control) => control.id === selectedControlId) ?? CONTROLS[0], + [selectedControlId], + ); + const selectedAction = ACTIONS[mapping[selectedControl.id]]; + const availableActions = Object.values(ACTIONS).filter((action) => + action.controlTypes.includes(selectedControl.type), + ); + + const openConfigurator = (controlId = selectedControlId) => { + returnFocusRef.current = document.activeElement; + setSelectedControlId(controlId); + setPanelOpen(true); + }; + + const closeConfigurator = () => setPanelOpen(false); + + const assignAction = (actionId) => { + setMapping((current) => ({ ...current, [selectedControl.id]: actionId })); + }; + + const resetMapping = () => { + setMapping(DEFAULT_MAPPING); + setToast("Mapping Claude restauré."); + }; + + const loadSourceProfile = async (event) => { + const file = event.target.files?.[0]; + event.target.value = ""; + if (!file) return; + + try { + const parsed = JSON.parse(await file.text()); + const inspection = inspectInputProfile(parsed); + setSourceProfile(parsed); + setSourceFileName(file.name); + setProfileInfo(inspection); + setProfileError(""); + setToast("Sauvegarde Input reconnue. Le profil peut être généré."); + } catch (error) { + setSourceProfile(null); + setSourceFileName(""); + setProfileInfo(null); + setProfileError(error instanceof Error ? error.message : "Fichier Input invalide."); + } + }; + + const exportMapping = () => { + if (!sourceProfile) { + profileInputRef.current?.click(); + setToast("Chargez d’abord la sauvegarde officielle de votre profil Input."); + return; + } + + try { + const { profile } = buildInputProfile(sourceProfile, mapping); + const blob = new Blob([`${JSON.stringify(profile, null, 2)}\n`], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = "Claude-macOS-profile.json"; + document.body.append(anchor); + anchor.click(); + anchor.remove(); + window.setTimeout(() => URL.revokeObjectURL(url), 1000); + setToast("Profil Input créé. Importez-le avec « Add New » dans Input."); + } catch (error) { + setProfileError(error instanceof Error ? error.message : "Génération impossible."); + } + }; + + useEffect(() => { + if (!toast) return undefined; + const timeout = window.setTimeout(() => setToast(""), 3000); + return () => window.clearTimeout(timeout); + }, [toast]); + + useEffect(() => { + if (!panelOpen) { + if (returnFocusRef.current) { + window.requestAnimationFrame(() => returnFocusRef.current?.focus()); + } + return undefined; + } + + window.requestAnimationFrame(() => closeButtonRef.current?.focus()); + + const handleKeyDown = (event) => { + if (event.key === "Escape") { + event.preventDefault(); + closeConfigurator(); + return; + } + + if (event.key !== "Tab" || !dialogRef.current) return; + + const focusable = Array.from( + dialogRef.current.querySelectorAll( + 'button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])', + ), + ).filter((element) => !element.hasAttribute("hidden")); + if (!focusable.length) return; + + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [panelOpen]); + + return ( +
+
+
+ + C + Codex Micro + + + +
+ +
+
+ Contrôleur pour Claude Desktop +

Vos raccourcis Claude, sous la main.

+

+ Choisissez une touche du Codex Micro et affectez-lui l’action Claude + que vous utilisez le plus. +

+
+ +
+
+ Codex Micro translucide avec quatre touches, un joystick et une molette + {CONTROLS.map((control) => { + const action = ACTIONS[mapping[control.id]]; + return ( + + ); + })} +
+ + +
+ + + +
+ {CONTROLS.map((control) => { + const action = ACTIONS[mapping[control.id]]; + return ( + + ); + })} +
+ +

+ Votre sauvegarde reste dans ce navigateur. Le profil généré s’importe + ensuite avec « Add New » dans Work Louder Input. +

+
+
+ + + + {panelOpen && ( +
+ ); +} diff --git a/prototype/src/main.jsx b/prototype/src/main.jsx new file mode 100644 index 0000000..78c0e05 --- /dev/null +++ b/prototype/src/main.jsx @@ -0,0 +1,10 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.jsx"; +import "./styles.css"; + +createRoot(document.getElementById("root")).render( + + + , +); diff --git a/prototype/src/styles.css b/prototype/src/styles.css new file mode 100644 index 0000000..17c8f06 --- /dev/null +++ b/prototype/src/styles.css @@ -0,0 +1,872 @@ +:root { + font-family: "Avenir Next", Avenir, "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #453a36; + background: #f8eee2; + font-synthesis: none; + --ink: #453a36; + --muted: #786d68; + --soft: #9f9189; + --accent: #c95f43; + --accent-dark: #a94831; + --accent-soft: #f7ded4; + --panel: rgba(255, 251, 247, 0.94); + --line: rgba(84, 65, 56, 0.14); + --line-strong: rgba(84, 65, 56, 0.25); + --shadow: rgba(126, 73, 52, 0.2); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + min-width: 320px; + min-height: 100vh; + margin: 0; + background: #f8eee2; + color: var(--ink); + -webkit-font-smoothing: antialiased; +} + +button, +a { + color: inherit; + font: inherit; +} + +button { + cursor: pointer; +} + +button:focus-visible, +a:focus-visible { + outline: 3px solid rgba(201, 95, 67, 0.42); + outline-offset: 3px; +} + +.app-shell { + position: relative; + min-height: 100vh; + overflow: hidden; + background: + linear-gradient(rgba(255, 249, 241, 0.22), rgba(255, 244, 232, 0.28)), + url("/assets/claude-glass-background-v1.png") center / cover fixed; +} + +.app-shell::after { + position: fixed; + z-index: 0; + inset: 0; + background: radial-gradient(circle at 50% 44%, transparent 0 28%, rgba(255, 247, 238, 0.22) 65%); + content: ""; + pointer-events: none; +} + +.page-content { + position: relative; + z-index: 1; + min-height: 100vh; +} + +.topbar { + position: absolute; + z-index: 3; + top: 0; + right: 0; + left: 0; + display: flex; + max-width: 1180px; + align-items: center; + justify-content: space-between; + margin: 0 auto; + padding: 24px 28px; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 10px; + color: var(--ink); + font-size: 14px; + font-weight: 700; + text-decoration: none; +} + +.brand-mark { + display: grid; + width: 30px; + height: 30px; + place-items: center; + border: 1px solid rgba(255, 255, 255, 0.7); + border-radius: 9px; + background: rgba(255, 250, 246, 0.62); + box-shadow: 0 8px 22px rgba(112, 70, 51, 0.12); + color: var(--accent); + font-size: 13px; +} + +.local-status { + display: inline-flex; + align-items: center; + gap: 7px; + border: 1px solid rgba(255, 255, 255, 0.66); + border-radius: 999px; + background: rgba(255, 250, 246, 0.54); + padding: 7px 11px; + color: var(--muted); + font-size: 12px; + font-weight: 650; + backdrop-filter: blur(14px); +} + +.local-status > span { + width: 6px; + height: 6px; + border-radius: 50%; + background: #65a87f; + box-shadow: 0 0 0 4px rgba(101, 168, 127, 0.13); +} + +.hero { + display: flex; + min-height: 100dvh; + align-items: center; + flex-direction: column; + justify-content: center; + padding: 72px 24px 94px; + text-align: center; +} + +.hero-copy { + position: relative; + z-index: 2; + max-width: 760px; +} + +.eyebrow { + color: var(--accent); + font-size: 12px; + font-weight: 750; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.hero h1 { + max-width: 760px; + margin: 14px auto 0; + color: #7c3f30; + font-size: clamp(46px, 5.5vw, 72px); + font-weight: 520; + letter-spacing: -0.06em; + line-height: 0.98; + text-wrap: balance; +} + +.hero-copy p { + max-width: 590px; + margin: 20px auto 0; + color: var(--muted); + font-size: clamp(16px, 1.5vw, 19px); + letter-spacing: -0.015em; + line-height: 1.5; + text-wrap: pretty; +} + +.device-stage { + position: relative; + width: min(510px, 72vh, 88vw); + margin-top: 20px; +} + +.device-wrap { + position: relative; + width: 100%; + aspect-ratio: 1; +} + +.device-image { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 30px 40px rgba(119, 70, 50, 0.16)); + user-select: none; +} + +.key-hotspot { + position: absolute; + z-index: 2; + display: grid; + place-items: center; + border: 1px solid transparent; + border-radius: 12px; + background: transparent; + padding: 0; + transition: + background 160ms ease, + border-color 160ms ease, + box-shadow 160ms ease, + transform 160ms ease; +} + +.key-hotspot--dial, +.key-hotspot--joystick { + border-radius: 50%; +} + +.key-hotspot:hover, +.key-hotspot:focus-visible { + border-color: rgba(201, 95, 67, 0.74); + background: rgba(255, 255, 255, 0.2); + box-shadow: 0 0 0 5px rgba(201, 95, 67, 0.18); + outline: none; + transform: translateY(-1px); +} + +.key-hotspot > span { + position: absolute; + top: calc(100% + 7px); + left: 50%; + width: max-content; + transform: translate(-50%, 4px); + border-radius: 7px; + background: rgba(69, 58, 54, 0.92); + padding: 5px 7px; + color: #fffaf6; + font: 750 9px/1 "SFMono-Regular", Consolas, monospace; + opacity: 0; + pointer-events: none; + transition: opacity 140ms ease, transform 140ms ease; +} + +.key-hotspot:hover > span, +.key-hotspot:focus-visible > span { + opacity: 1; + transform: translate(-50%, 0); +} + +.configure-button { + position: absolute; + right: -62px; + bottom: 62px; + display: inline-flex; + width: min(240px, calc(100vw - 32px)); + min-height: 50px; + align-items: center; + justify-content: center; + gap: 9px; + border: 1px solid rgba(255, 255, 255, 0.78); + border-radius: 999px; + background: rgba(255, 251, 247, 0.82); + padding: 0 17px; + color: var(--ink); + box-shadow: + 0 16px 42px rgba(112, 70, 51, 0.16), + inset 0 1px 0 rgba(255, 255, 255, 0.92); + font-size: 13px; + font-weight: 720; + backdrop-filter: blur(18px); + transition: background 160ms ease, box-shadow 160ms ease, transform 160ms ease; +} + +.configure-button:hover { + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 20px 48px rgba(112, 70, 51, 0.22); + transform: translateY(-2px); +} + +.configure-button:active { + transform: translateY(0); +} + +.generate-button { + position: relative; + z-index: 3; + display: inline-flex; + width: min(240px, calc(100vw - 32px)); + min-height: 50px; + align-items: center; + justify-content: center; + gap: 8px; + margin-top: -44px; + border: 1px solid var(--accent); + border-radius: 999px; + background: var(--accent); + padding: 0 20px; + color: #fffaf6; + box-shadow: 0 14px 32px rgba(169, 72, 49, 0.24); + font-size: 13px; + font-weight: 750; + transition: background 160ms ease, box-shadow 160ms ease, transform 160ms ease; +} + +.generate-button:hover { + background: var(--accent-dark); + box-shadow: 0 18px 38px rgba(169, 72, 49, 0.3); + transform: translateY(-2px); +} + +.generate-button:active { + transform: translateY(0); +} + +.mapping-preview { + display: flex; + max-width: calc(100vw - 32px); + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 7px; + margin-top: 12px; +} + +.mapping-preview button { + display: inline-flex; + min-height: 34px; + align-items: center; + gap: 7px; + border: 1px solid rgba(255, 255, 255, 0.68); + border-radius: 999px; + background: rgba(255, 250, 246, 0.52); + padding: 0 11px; + color: var(--muted); + backdrop-filter: blur(12px); + transition: background 150ms ease, transform 150ms ease; +} + +.mapping-preview button:hover { + background: rgba(255, 255, 255, 0.88); + transform: translateY(-1px); +} + +.mapping-preview span { + color: var(--soft); + font: 700 9px/1 "SFMono-Regular", Consolas, monospace; +} + +.mapping-preview strong { + color: var(--ink); + font-size: 10px; +} + +.quiet-note { + margin: 15px 0 0; + color: rgba(84, 65, 56, 0.66); + font-size: 11px; +} + +.mapping-dialog { + position: fixed; + z-index: 30; + top: 12px; + right: 12px; + bottom: 12px; + display: flex; + width: min(470px, calc(100vw - 24px)); + flex-direction: column; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.82); + border-radius: 28px; + background: var(--panel); + box-shadow: 0 32px 110px rgba(67, 51, 44, 0.28); + opacity: 0; + transform: translateX(calc(100% + 24px)); + pointer-events: none; + transition: + opacity 180ms ease, + transform 280ms cubic-bezier(0.2, 0.9, 0.25, 1); + backdrop-filter: blur(28px) saturate(1.08); +} + +.mapping-dialog.is-open { + opacity: 1; + transform: translateX(0); + pointer-events: auto; +} + +.dialog-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + border-bottom: 1px solid var(--line); + padding: 25px 25px 20px; +} + +.dialog-header > div > span { + color: var(--accent); + font-size: 11px; + font-weight: 750; + letter-spacing: 0.11em; + text-transform: uppercase; +} + +.dialog-header h2 { + margin: 6px 0 0; + font-size: 30px; + font-weight: 560; + letter-spacing: -0.045em; +} + +.icon-button { + display: grid; + width: 44px; + height: 44px; + flex: none; + place-items: center; + border: 1px solid var(--line); + border-radius: 50%; + background: rgba(255, 255, 255, 0.58); + transition: background 150ms ease, transform 150ms ease; +} + +.icon-button:hover { + background: #fff; + transform: rotate(4deg); +} + +.dialog-scroll { + flex: 1; + overflow: auto; + padding: 22px 25px 28px; +} + +.control-tabs { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 7px; +} + +.control-tabs button { + display: grid; + min-height: 60px; + align-content: center; + gap: 4px; + border: 1px solid var(--line); + border-radius: 14px; + background: rgba(255, 255, 255, 0.42); + padding: 9px; + text-align: left; + transition: background 150ms ease, border-color 150ms ease, transform 150ms ease; +} + +.control-tabs button:hover { + border-color: var(--line-strong); + background: rgba(255, 255, 255, 0.78); + transform: translateY(-1px); +} + +.control-tabs button.is-active { + border-color: rgba(201, 95, 67, 0.58); + background: var(--accent-soft); +} + +.control-tabs span { + color: var(--soft); + font: 750 9px/1 "SFMono-Regular", Consolas, monospace; +} + +.control-tabs strong { + overflow: hidden; + color: var(--ink); + font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.action-picker { + margin-top: 30px; +} + +.section-label { + display: flex; + align-items: center; + justify-content: space-between; +} + +.section-label h3 { + margin: 0; + font-size: 13px; + font-weight: 750; +} + +kbd { + border-radius: 7px; + background: rgba(91, 72, 64, 0.08); + padding: 6px 8px; + color: var(--muted); + font: 700 11px/1 "SFMono-Regular", Consolas, monospace; +} + +.action-list { + display: grid; + gap: 7px; + margin-top: 12px; +} + +.action-list button { + display: grid; + min-height: 67px; + align-items: center; + grid-template-columns: 38px minmax(0, 1fr) auto 20px; + gap: 10px; + border: 1px solid transparent; + border-radius: 16px; + background: rgba(255, 255, 255, 0.36); + padding: 10px 12px; + text-align: left; + transition: background 150ms ease, border-color 150ms ease, transform 150ms ease; +} + +.action-list button:hover { + border-color: var(--line); + background: rgba(255, 255, 255, 0.78); + transform: translateX(2px); +} + +.action-list button.is-active { + border-color: rgba(201, 95, 67, 0.34); + background: var(--accent-soft); +} + +.action-icon { + display: grid; + width: 38px; + height: 38px; + place-items: center; + border-radius: 11px; + background: rgba(255, 255, 255, 0.66); + color: var(--accent); +} + +.action-copy { + display: grid; + gap: 3px; +} + +.action-copy strong { + font-size: 13px; +} + +.action-copy small { + overflow: hidden; + color: var(--muted); + font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.check-slot { + display: grid; + color: var(--accent); + place-items: center; +} + +.panel-note { + margin: 24px 0 0; + border-top: 1px solid var(--line); + padding-top: 17px; + color: var(--muted); + font-size: 12px; + line-height: 1.5; +} + +.profile-loader { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 12px; + margin-top: 24px; + border: 1px solid var(--line); + border-radius: 17px; + background: rgba(255, 255, 255, 0.44); + padding: 13px; +} + +.profile-loader-copy { + display: flex; + min-width: 0; + align-items: center; + gap: 10px; + text-align: left; +} + +.profile-loader-copy > span:last-child { + display: grid; + min-width: 0; + gap: 3px; +} + +.profile-loader-copy strong { + font-size: 12px; +} + +.profile-loader-copy small { + overflow: hidden; + color: var(--muted); + font-size: 10px; + line-height: 1.4; + text-overflow: ellipsis; + white-space: nowrap; +} + +.profile-loader-icon { + display: grid; + width: 36px; + height: 36px; + flex: none; + place-items: center; + border-radius: 11px; + background: var(--accent-soft); + color: var(--accent); +} + +.profile-load-button { + min-height: 36px; + border: 1px solid var(--line-strong); + border-radius: 999px; + background: rgba(255, 255, 255, 0.72); + padding: 0 12px; + color: var(--ink); + font-size: 10px; + font-weight: 750; + white-space: nowrap; +} + +.profile-load-button:hover { + background: #fff; +} + +.profile-error { + grid-column: 1 / -1; + margin: 0; + border-radius: 10px; + background: rgba(169, 72, 49, 0.09); + padding: 9px 10px; + color: var(--accent-dark); + font-size: 10px; + line-height: 1.4; +} + +.dialog-footer { + display: grid; + grid-template-columns: 0.8fr 1.35fr; + gap: 8px; + border-top: 1px solid var(--line); + padding: 16px 25px 20px; +} + +.dialog-footer button { + display: flex; + min-height: 48px; + align-items: center; + justify-content: center; + gap: 7px; + border-radius: 999px; + padding: 0 15px; + font-size: 12px; + font-weight: 750; + white-space: nowrap; + transition: background 150ms ease, box-shadow 150ms ease, transform 150ms ease; +} + +.reset-button { + border: 1px solid var(--line-strong); + background: rgba(255, 255, 255, 0.5); +} + +.reset-button:hover { + background: #fff; +} + +.export-button { + border: 1px solid var(--accent); + background: var(--accent); + color: #fffaf6; + box-shadow: 0 12px 28px rgba(169, 72, 49, 0.22); +} + +.export-button:hover { + background: var(--accent-dark); + box-shadow: 0 16px 32px rgba(169, 72, 49, 0.28); + transform: translateY(-1px); +} + +.dialog-footer button:active { + transform: translateY(1px); +} + +.dialog-scrim { + position: fixed; + z-index: 20; + inset: 0; + border: 0; + background: rgba(81, 68, 63, 0.24); + backdrop-filter: blur(5px); +} + +.toast { + position: fixed; + z-index: 40; + bottom: 22px; + left: 50%; + display: flex; + max-width: min(520px, calc(100vw - 32px)); + align-items: center; + gap: 8px; + transform: translateX(-50%); + border-radius: 14px; + background: #453a36; + padding: 13px 16px; + color: #fffaf6; + box-shadow: 0 18px 50px rgba(62, 46, 40, 0.28); + font-size: 13px; +} + +@media (max-width: 760px) { + .topbar { + padding: 18px; + } + + .local-status { + padding: 7px 9px; + } + + .hero { + min-height: 100dvh; + padding: 78px 14px 54px; + } + + .hero h1 { + font-size: clamp(40px, 11vw, 54px); + } + + .hero-copy p { + font-size: 15px; + } + + .device-stage { + width: min(460px, 96vw); + margin-top: 10px; + } + + .configure-button { + position: static; + min-height: 50px; + margin: -34px auto 0; + } + + .generate-button { + margin-top: 10px; + } + + .mapping-preview { + margin-top: 15px; + } + + .quiet-note { + max-width: 310px; + line-height: 1.4; + } + + .mapping-dialog { + top: auto; + right: 8px; + bottom: 8px; + left: 8px; + width: auto; + max-height: 91dvh; + border-radius: 24px; + transform: translateY(calc(100% + 16px)); + } + + .mapping-dialog.is-open { + transform: translateY(0); + } + + .dialog-header { + padding: 20px 18px 16px; + } + + .dialog-scroll { + padding: 18px 18px 22px; + } + + .dialog-footer { + padding: 13px 18px 16px; + } + + .profile-loader { + grid-template-columns: 1fr; + } + + .profile-load-button { + justify-self: start; + } +} + +@media (max-width: 420px) { + .brand > span:last-child { + display: none; + } + + .hero { + justify-content: flex-start; + } + + .hero h1 { + font-size: 39px; + } + + .device-stage { + width: min(400px, 108vw); + } + + .mapping-preview button { + min-height: 31px; + padding: 0 9px; + } + + .control-tabs { + grid-template-columns: repeat(3, 1fr); + } + + .action-list button { + grid-template-columns: 36px minmax(0, 1fr) auto 18px; + gap: 8px; + padding: 9px; + } + + .action-copy small { + max-width: 170px; + } + + .dialog-footer { + grid-template-columns: 0.72fr 1.28fr; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + transition-duration: 0.01ms !important; + } +} + +@media (prefers-reduced-transparency: reduce) { + .local-status, + .configure-button, + .mapping-preview button, + .mapping-dialog { + background: #fffaf6; + backdrop-filter: none; + } +} diff --git a/prototype/tests/sites-worker.test.mjs b/prototype/tests/sites-worker.test.mjs new file mode 100644 index 0000000..64f277e --- /dev/null +++ b/prototype/tests/sites-worker.test.mjs @@ -0,0 +1,68 @@ +import assert from "node:assert/strict"; +import { access } from "node:fs/promises"; +import test from "node:test"; +import worker from "../worker/index.js"; + +test("serves existing static assets without a fallback", async () => { + const calls = []; + const response = await worker.fetch(new Request("https://example.test/assets/app.js"), { + ASSETS: { + fetch: async (request) => { + calls.push(new URL(request.url).pathname); + return new Response("asset", { status: 200 }); + }, + }, + }); + + assert.equal(response.status, 200); + assert.deepEqual(calls, ["/assets/app.js"]); +}); + +test("falls back to index.html for an unknown app route", async () => { + const calls = []; + const response = await worker.fetch( + new Request("https://example.test/flow/step-two?source=share", { + headers: { accept: "text/html" }, + }), + { + ASSETS: { + fetch: async (request) => { + const url = new URL(request.url); + calls.push(url.pathname + url.search); + return new Response(url.pathname === "/index.html" ? "app" : "missing", { + status: url.pathname === "/index.html" ? 200 : 404, + }); + }, + }, + }, + ); + + assert.equal(response.status, 200); + assert.deepEqual(calls, ["/flow/step-two?source=share", "/index.html"]); +}); + +test("does not turn missing API or write requests into the app shell", async () => { + for (const request of [ + new Request("https://example.test/api/missing", { headers: { accept: "application/json" } }), + new Request("https://example.test/flow", { method: "POST", headers: { accept: "text/html" } }), + ]) { + let calls = 0; + const response = await worker.fetch(request, { + ASSETS: { + fetch: async () => { + calls += 1; + return new Response("missing", { status: 404 }); + }, + }, + }); + + assert.equal(response.status, 404); + assert.equal(calls, 1); + } +}); + +test("emits the files required by Sites packaging", async () => { + await access(new URL("../dist/client/index.html", import.meta.url)); + await access(new URL("../dist/server/index.js", import.meta.url)); + await access(new URL("../dist/.openai/hosting.json", import.meta.url)); +}); diff --git a/prototype/vite.config.mjs b/prototype/vite.config.mjs new file mode 100644 index 0000000..888d6c6 --- /dev/null +++ b/prototype/vite.config.mjs @@ -0,0 +1,19 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + build: { + outDir: "dist/client", + }, + optimizeDeps: { + include: ["react", "react-dom/client"], + }, + server: { + host: "0.0.0.0", + allowedHosts: ["terminal.local"], + warmup: { + clientFiles: ["./src/main.jsx"], + }, + }, + plugins: [react()], +}); diff --git a/prototype/worker/index.js b/prototype/worker/index.js new file mode 100644 index 0000000..5f35412 --- /dev/null +++ b/prototype/worker/index.js @@ -0,0 +1,15 @@ +export default { + async fetch(request, env) { + const response = await env.ASSETS.fetch(request); + const acceptsHtml = request.headers.get("accept")?.includes("text/html"); + + if (response.status !== 404 || !acceptsHtml || !["GET", "HEAD"].includes(request.method)) { + return response; + } + + const indexUrl = new URL(request.url); + indexUrl.pathname = "/index.html"; + indexUrl.search = ""; + return env.ASSETS.fetch(new Request(indexUrl, request)); + }, +}; diff --git a/scripts/configure.mjs b/scripts/configure.mjs new file mode 100644 index 0000000..484fcf0 --- /dev/null +++ b/scripts/configure.mjs @@ -0,0 +1,47 @@ +import { spawn } from "node:child_process"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { ensureGuiDependencies } from "./lib/gui-dependencies.mjs"; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const guiDirectory = resolve(repositoryRoot, "prototype"); +const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"; + +try { + ensureGuiDependencies(guiDirectory); +} catch (error) { + console.error(error.message); + process.exit(error.exitCode ?? 1); +} + +console.log("Ouverture du configurateur Codex Micro…"); + +const gui = spawn( + npmCommand, + [ + "run", + "dev", + "--", + "--host", + "127.0.0.1", + "--open", + ...process.argv.slice(2), + ], + { + cwd: guiDirectory, + stdio: "inherit", + }, +); + +gui.on("error", (error) => { + console.error(`Impossible de lancer l’interface : ${error.message}`); + process.exit(1); +}); + +gui.on("exit", (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + return; + } + process.exit(code ?? 0); +}); diff --git a/scripts/lib/gui-dependencies.mjs b/scripts/lib/gui-dependencies.mjs new file mode 100644 index 0000000..99e4857 --- /dev/null +++ b/scripts/lib/gui-dependencies.mjs @@ -0,0 +1,45 @@ +import { existsSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { resolve } from "node:path"; + +const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"; + +export function ensureGuiDependencies(guiDirectory) { + const packageJson = resolve(guiDirectory, "package.json"); + const viteExecutable = resolve( + guiDirectory, + "node_modules", + ".bin", + process.platform === "win32" ? "vite.cmd" : "vite", + ); + + if (!existsSync(packageJson)) { + throw new Error("Interface introuvable : prototype/package.json est absent."); + } + + if (existsSync(viteExecutable)) { + return { installed: false }; + } + + console.log("Préparation des dépendances verrouillées de l’interface…"); + const install = spawnSync( + npmCommand, + ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], + { + cwd: guiDirectory, + stdio: "inherit", + }, + ); + + if (install.error) { + throw new Error(`Impossible de préparer l’interface : ${install.error.message}`); + } + + if (install.status !== 0) { + const error = new Error(`La préparation de l’interface a échoué avec le code ${install.status ?? 1}.`); + error.exitCode = install.status ?? 1; + throw error; + } + + return { installed: true }; +} diff --git a/scripts/prepare-gui.mjs b/scripts/prepare-gui.mjs new file mode 100644 index 0000000..13ee590 --- /dev/null +++ b/scripts/prepare-gui.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { ensureGuiDependencies } from "./lib/gui-dependencies.mjs"; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const guiDirectory = resolve(repositoryRoot, "prototype"); + +try { + const result = ensureGuiDependencies(guiDirectory); + console.log(result.installed ? "OK: dépendances GUI installées" : "OK: dépendances GUI déjà disponibles"); +} catch (error) { + console.error(error.message); + process.exitCode = error.exitCode ?? 1; +} diff --git a/scripts/validate-profile.mjs b/scripts/validate-profile.mjs index d1eef95..70820db 100755 --- a/scripts/validate-profile.mjs +++ b/scripts/validate-profile.mjs @@ -15,7 +15,8 @@ function sameAction(actual, expected) { return isDeepStrictEqual(actual, expected); } -if (profile.status !== "hardware-observed") errors.push("status must remain hardware-observed until the full hardware checklist passes"); +if (profile.id !== "claude-desktop-macos-codex-micro-v0") errors.push("logical profile id must remain the V0 proposal"); +if (profile.status !== "proposal-not-applied") errors.push("logical profile status must remain proposal-not-applied"); if (profile.target?.appBundleId !== "com.anthropic.claudefordesktop") errors.push("unexpected Claude bundle identifier"); if (profile.target?.observedInputVersion !== "0.17.3") errors.push("observed Input version must be 0.17.3"); if (!profile.target?.supportedInputVersions?.includes("0.17.3")) { @@ -26,7 +27,7 @@ if (profile.target?.observedFirmwareVersion !== "v0.4.1") errors.push("observed if ( profile.preservation?.mode !== "update-only-existing-layer" || !profile.preservation?.protectedLayerIndexes?.includes(0) || - profile.preservation?.unlistedControls !== "no-action-in-new-layer" || + profile.preservation?.unlistedControls !== "no-action-in-target-layer" || profile.preservation?.touchLayerControl !== "reserved" || profile.preservation?.otherLayers !== "unchanged" || profile.preservation?.otherProfiles !== "unchanged" || @@ -115,7 +116,7 @@ if (!profile.unusedControls?.length || profile.unusedControls.some((entry) => ![ errors.push("unused controls must be explicitly safe"); } -for (const action of ["quick-entry"]) { +for (const action of ["quick-entry", "voice-dictation"]) { if (!profile.outsideAppLinkedLayer?.some((entry) => entry.action === action)) errors.push(`${action} must remain outside the AppSense layer`); } for (const action of ["send-message", "permission-approve-or-deny", "delete", "git-push", "deploy", "destructive-command"]) { @@ -126,5 +127,5 @@ if (errors.length) { for (const error of errors) console.error(`ERROR: ${error}`); process.exitCode = 1; } else { - console.log("OK: Claude V1 logical profile validated; layer 0 protected and sensitive actions excluded"); + console.log("OK: Claude logical proposal validated; layer 0 protected and sensitive actions excluded"); }