- carnet.md: enregistre la livraison CORS et la décision Git du 2026-08-01 - issue.md + index.json: mise à jour version/timestamp - plugins.test.tsx: tests d'intégration frontend liés - Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#120"
|
issueRef: "#120"
|
||||||
|
version: 9
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
|
||||||
|
updatedAt: 1785600025153
|
||||||
|
---
|
||||||
|
---
|
||||||
|
issueRef: "#120"
|
||||||
version: 8
|
version: 8
|
||||||
updatedBy: {"kind":"agent","agent_id":"dce19c75-9669-4e45-b8de-9950025157da"}
|
|
||||||
updatedAt: 1785596006134
|
|
||||||
---
|
|
||||||
---
|
|
||||||
issueRef: "#120"
|
|
||||||
version: 7
|
|
||||||
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
|
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
|
||||||
updatedAt: 1785595113109
|
updatedAt: 1785595113109
|
||||||
---
|
---
|
||||||
@ -188,3 +188,77 @@ Le commit du correctif (`5a30ec8`, isolation `usePluginMenus`/`ProjectsView`) da
|
|||||||
|
|
||||||
- Ne pas rouvrir de chantier de code avant d'avoir confirme que l'utilisateur reproduit sur le binaire effectivement a jour.
|
- Ne pas rouvrir de chantier de code avant d'avoir confirme que l'utilisateur reproduit sur le binaire effectivement a jour.
|
||||||
- Remplacement de binaire + retest = action Git/deploiement, a executer avant toute nouvelle investigation frontend.
|
- Remplacement de binaire + retest = action Git/deploiement, a executer avant toute nouvelle investigation frontend.
|
||||||
|
|
||||||
|
## Requalification Architect du 2026-08-01 (nouveau symptome CORS apres installation)
|
||||||
|
|
||||||
|
### Nouveau fait utilisateur
|
||||||
|
|
||||||
|
Le symptome visible n'est plus seulement un ecran noir: dans la vue Plugins, IdeA affiche apres installation de `hello-plugin` un bandeau explicite:
|
||||||
|
- `Certains plugins installes n'ont pas pu etre charges.`
|
||||||
|
- `com.example.hello-plugin : Cross-origin script load denied by Cross-Origin Resource Sharing policy.`
|
||||||
|
|
||||||
|
Cette observation invalide la piste `LayoutTabs -> PluginLayoutSelectorSection` comme cause racine de ce symptome precis. Le durcissement frontend precedent reste utile: il a empeche le black screen et laisse remonter l'erreur exploitable.
|
||||||
|
|
||||||
|
### Cause racine retenue
|
||||||
|
|
||||||
|
- Le protocole custom `idea-plugin://` repondait sans headers CORS dans `crates/app-tauri/src/plugins.rs` (`plugin_asset_response`).
|
||||||
|
- Le frontend charge le bundle plugin via `import()` dynamique depuis `frontend/src/plugins/runtime/loader.ts`; ce chargement est un fetch CORS.
|
||||||
|
- L'origine du document (`tauri://localhost` / `http://tauri.localhost` en prod, `http://localhost:5173` en dev) est distincte de `idea-plugin://...`.
|
||||||
|
- Faute de `Access-Control-Allow-Origin`, WebKitGTK bloque le module avec exactement le message observe.
|
||||||
|
|
||||||
|
### Couche proprietaire et perimetre de correction
|
||||||
|
|
||||||
|
- Couche proprietaire: backend/infrastructure `app-tauri`, pas frontend produit, pas packaging plugin.
|
||||||
|
- Correctif attendu:
|
||||||
|
- ajouter `Access-Control-Allow-Origin` sur les reponses du protocole plugin
|
||||||
|
- ajouter `Access-Control-Allow-Methods: GET`
|
||||||
|
- conserver intact le confinement `asset_allowed`
|
||||||
|
- ajouter un test backend verrouillant ces headers sur `plugin_asset_response`
|
||||||
|
|
||||||
|
## Livraison DevBackend du 2026-08-01
|
||||||
|
|
||||||
|
- Branche de travail dediee: `feature/ticket120-plugin-asset-cors-headers`
|
||||||
|
- Correctif implemente dans `crates/app-tauri/src/plugins.rs`
|
||||||
|
- Headers ajoutes sur les reponses du protocole `idea-plugin://`:
|
||||||
|
- `Access-Control-Allow-Origin: *`
|
||||||
|
- `Access-Control-Allow-Methods: GET`
|
||||||
|
- Factorisation via un builder de reponse commun aux chemins succes/erreur du protocole
|
||||||
|
- Test ajoute: `plugin_asset_response_includes_cors_headers_for_dynamic_import`
|
||||||
|
|
||||||
|
### Commandes executees par DevBackend
|
||||||
|
- `cargo fmt -p app-tauri` : OK
|
||||||
|
- `cargo test -p app-tauri plugin_asset_response_includes_cors_headers_for_dynamic_import` : OK
|
||||||
|
- `cargo test -p app-tauri plugins::tests` : OK
|
||||||
|
- `cargo test -p app-tauri` : OK (242 passed, 0 failed, 5 ignored)
|
||||||
|
|
||||||
|
## Validation QA du 2026-08-01 (fix CORS)
|
||||||
|
|
||||||
|
### Verdict
|
||||||
|
- PASS avec reserve explicite.
|
||||||
|
|
||||||
|
### Validation reelle obtenue
|
||||||
|
- `cargo test -p app-tauri plugin_asset_response_includes_cors_headers_for_dynamic_import -- --nocapture` : OK
|
||||||
|
- `cargo test -p app-tauri` : OK
|
||||||
|
- `cargo test -p infrastructure --test plugin_install_load installs_sdk_hello_plugin_and_loads_runtime_catalog -- --nocapture` : OK
|
||||||
|
- `cargo test -p infrastructure --test plugin_install_load installs_reference_fixture_and_loads_runtime_catalog -- --nocapture` : OK
|
||||||
|
- `npm --prefix /home/anthony/Documents/Projects/IdeA/sdk/IdeaSDK run package:hello-plugin` : OK
|
||||||
|
- `cd /home/anthony/Documents/Projects/IdeA/frontend && npx vitest run src/plugins/runtime/loader.test.ts src/features/plugins/plugins.test.tsx src/features/plugins/menus.test.ts` : OK (27 tests)
|
||||||
|
|
||||||
|
### Reserve QA restante
|
||||||
|
- Pas de preuve visuelle AppImage/UI de bout en bout dans cet environnement.
|
||||||
|
- Tentative de build AppImage realisee, mais l'artefact final n'etait pas disponible ensuite dans `target/release/bundle/appimage/`.
|
||||||
|
- Tentative d'execution d'une AppImage existante bloquee par l'environnement (`No suitable fusermount binary found on the $PATH`).
|
||||||
|
- Risque residuel exact: le fix est prouve au niveau handler backend, catalogue runtime et chargeur frontend, mais pas sur l'enchainement visuel complet `archive installee -> redemarrage UI reel -> absence du bandeau CORS`.
|
||||||
|
|
||||||
|
## Decision Git du 2026-08-01
|
||||||
|
|
||||||
|
- Commit local realise sur la branche dediee: `fbe69de`
|
||||||
|
- Merge local `--no-ff` dans `develop`: `fd2ab4a`
|
||||||
|
- Motivation: QA a rendu un verdict PASS; la reserve porte sur une limite d'environnement de preuve AppImage/FUSE, pas sur un test rouge.
|
||||||
|
- La branche `feature/ticket120-plugin-asset-cors-headers` a ete supprimee apres merge.
|
||||||
|
|
||||||
|
## Etat reel a la fin de cette relance
|
||||||
|
|
||||||
|
- Le correctif CORS est livre dans `develop`.
|
||||||
|
- Le ticket **reste a considerer ouvert fonctionnellement** tant qu'une validation visuelle reelle sur AppImage/redemarrage n'a pas confirme la disparition du bandeau `Cross-origin script load denied by Cross-Origin Resource Sharing policy` et l'activation effective des contributions `hello-plugin`.
|
||||||
|
- Prochaine preuve attendue hors environnement QA courant: lancer le binaire AppImage reellement utilise par l'utilisateur, installer l'archive `hello-plugin`, redemarrer IdeA, verifier visuellement l'absence du bandeau CORS et la presence des contributions plugin actives.
|
||||||
|
|||||||
@ -9,10 +9,10 @@ links: [{"target":"#116","kind":"relatesTo"},{"target":"#43","kind":"relatesTo"}
|
|||||||
agentRefs: []
|
agentRefs: []
|
||||||
attachments: []
|
attachments: []
|
||||||
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
|
createdBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"dce19c75-9669-4e45-b8de-9950025157da"}
|
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
|
||||||
createdAt: 1785534496259
|
createdAt: 1785534496259
|
||||||
updatedAt: 1785596006134
|
updatedAt: 1785600025153
|
||||||
version: 8
|
version: 9
|
||||||
---
|
---
|
||||||
## Constat utilisateur
|
## Constat utilisateur
|
||||||
|
|
||||||
|
|||||||
@ -1564,7 +1564,7 @@
|
|||||||
"kind": "agent",
|
"kind": "agent",
|
||||||
"agent_id": "a6ced819-b893-4213-b003-9e9dc79b9641"
|
"agent_id": "a6ced819-b893-4213-b003-9e9dc79b9641"
|
||||||
},
|
},
|
||||||
"updatedAt": 1785596006134
|
"updatedAt": 1785600025153
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#121",
|
"issueRef": "#121",
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import { describe, it, expect } from "vitest";
|
|||||||
import { render, screen, waitFor, fireEvent, within } from "@testing-library/react";
|
import { render, screen, waitFor, fireEvent, within } from "@testing-library/react";
|
||||||
|
|
||||||
import { MockPluginGateway, MockSystemGateway } from "@/adapters/mock";
|
import { MockPluginGateway, MockSystemGateway } from "@/adapters/mock";
|
||||||
import type { PluginInstallResult, PluginRuntimeContributionCatalog } from "@/domain";
|
import type { PluginInstallResult, PluginReview, PluginRuntimeContributionCatalog } from "@/domain";
|
||||||
import type { Gateways } from "@/ports";
|
import type { Gateways, ReviewPluginPackageInput } from "@/ports";
|
||||||
import { DIProvider } from "@/app/di";
|
import { DIProvider } from "@/app/di";
|
||||||
import { PluginRuntimeRegistry } from "@/plugins/runtime";
|
import { PluginRuntimeRegistry } from "@/plugins/runtime";
|
||||||
import { PluginsPanel } from "./PluginsPanel";
|
import { PluginsPanel } from "./PluginsPanel";
|
||||||
@ -77,6 +77,25 @@ class FailingRuntimeCatalogPluginGateway extends MockPluginGateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BackendShapedReviewPluginGateway extends MockPluginGateway {
|
||||||
|
async reviewPackage(input: ReviewPluginPackageInput): Promise<PluginReview> {
|
||||||
|
const label = input.path.split("/").pop() ?? input.path;
|
||||||
|
return {
|
||||||
|
id: "dev.acme.gitgraph",
|
||||||
|
displayName: label.replace(/\.(ideaplug|zip|vsix)$/i, ""),
|
||||||
|
publisher: "Acme",
|
||||||
|
version: "1.2.3",
|
||||||
|
description: "Graph",
|
||||||
|
sourceKind: input.sourceKind,
|
||||||
|
sourceLabel: input.path,
|
||||||
|
contentHash: "abc",
|
||||||
|
trustLevel: "full",
|
||||||
|
contributionSummary: { topLevelMenus: 1, menuItems: 2, layouts: 3, mcpServers: 4 },
|
||||||
|
contributes: { menus: [], menuItems: [], layouts: [], mcpServers: [] },
|
||||||
|
} as unknown as PluginReview;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("PluginsPanel", () => {
|
describe("PluginsPanel", () => {
|
||||||
it("shows an empty state with no plugins installed", async () => {
|
it("shows an empty state with no plugins installed", async () => {
|
||||||
renderPanel();
|
renderPanel();
|
||||||
@ -122,6 +141,30 @@ describe("PluginsPanel", () => {
|
|||||||
expect(screen.getByText("Activé")).toBeTruthy();
|
expect(screen.getByText("Activé")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps install, uninstall and reinstall stable with backend-shaped reviews", async () => {
|
||||||
|
renderPanel(new BackendShapedReviewPluginGateway());
|
||||||
|
await screen.findByText("Aucun plugin installé.");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Installer depuis une archive…" }));
|
||||||
|
let dialog = await screen.findByRole("dialog");
|
||||||
|
expect(within(dialog).getByText(/full-trust/)).toBeTruthy();
|
||||||
|
fireEvent.click(within(dialog).getByRole("button", { name: "Installer" }));
|
||||||
|
expect(await screen.findByText("mock-plugin")).toBeTruthy();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Désinstaller" }));
|
||||||
|
dialog = await screen.findByRole("dialog");
|
||||||
|
fireEvent.click(within(dialog).getByRole("button", { name: "Désinstaller" }));
|
||||||
|
expect(await screen.findByText("Aucun plugin installé.")).toBeTruthy();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Installer depuis une archive…" }));
|
||||||
|
dialog = await screen.findByRole("dialog");
|
||||||
|
expect(within(dialog).getByRole("button", { name: "Installer" })).toBeTruthy();
|
||||||
|
fireEvent.click(within(dialog).getByRole("button", { name: "Installer" }));
|
||||||
|
|
||||||
|
expect(await screen.findByText("mock-plugin")).toBeTruthy();
|
||||||
|
expect(screen.queryByText("INTERFACE INTERROMPUE")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("renders an invalid plugin returned after install as isolated, without replacing the panel", async () => {
|
it("renders an invalid plugin returned after install as isolated, without replacing the panel", async () => {
|
||||||
renderPanel(new InvalidInstallPluginGateway());
|
renderPanel(new InvalidInstallPluginGateway());
|
||||||
await screen.findByText("Aucun plugin installé.");
|
await screen.findByText("Aucun plugin installé.");
|
||||||
|
|||||||
Reference in New Issue
Block a user