feat(domain,application,infrastructure,backend,web-server,app-tauri,frontend): bibliothèque d'icônes projet personnalisées importées par l'utilisateur — #358 (QA verte)

domain: variante ProjectIconSet::Custom, métadonnées CustomProjectIconAsset (id opaque, media type png/jpeg/webp, byte_size, dimensions décodées, sha256, nom de fichier original sans chemin, created_at), CustomProjectIconContent et port CustomProjectIconAssetStore (list/read/exists/create/delete) avec CustomProjectIconStoreError dédiée.
application: use case project_icon_assets — import validé png/jpeg/webp avec rejet du contenu SVG, dimensionnement et digest sha256, bibliothèque partagée à l'instance indépendante de tout projet, suppression refusée tant que l'asset est référencé par un projet.
infrastructure: store fichier store/project_icon_asset.rs — index canonique + octets, création atomique sous extension validée.
backend/web-server: contrat HTTP /api/project-icon-assets (GET liste, POST import, GET lecture, GET /usage, DELETE) avec prévol OPTIONS et tests contrat incluant le rejet SVG.
app-tauri: commandes import/list/read/get_usage/delete_custom_project_icon_asset exposées au frontend.
frontend: port ProjectIconAssetGateway (list/import/delete/getUrl sans exposer de chemin filesystem), gateways http+mock avec tests, DI, section Icônes personnalisées du ProjectIconPicker (import fichier accept png/jpeg/webp, sélection role=option, suppression avec retrait de sélection, erreurs lisibles i18n FR/EN), tests projectIcons étendus.

QA verte: contrats HTTP et Tauri identiques, suites cargo et npm ciblées vertes.
This commit is contained in:
2026-09-15 07:57:51 +02:00
parent a082b55c75
commit 0f01d1f9b1
32 changed files with 2002 additions and 151 deletions

View File

@ -270,8 +270,9 @@ pub use scheduled_task::{
};
pub use settings::{
DefaultAgentTerminalInterface, IdeaLanguage, IdeaUserSettings, ProjectIconRef, ProjectIconSet,
ProjectSettings, ThemeColor, MAX_PROJECT_ICON_NAME_LEN,
CustomProjectIconAsset, CustomProjectIconContent, DefaultAgentTerminalInterface, IdeaLanguage,
IdeaUserSettings, ProjectIconRef, ProjectIconSet, ProjectSettings, ThemeColor,
MAX_PROJECT_ICON_NAME_LEN,
};
pub use orchestrator::{
@ -283,19 +284,20 @@ pub use ports::{
AssistantContextProvider, BackgroundCompletionStream, BackgroundTaskCompletion,
BackgroundTaskHandle, BackgroundTaskPortError, BackgroundTaskRunner, BackgroundTaskSpec,
BackgroundTaskStore, CliVersionReader, Clock, CompatibilityMatrixSource, ContextInjectionPlan,
DirEntry, Embedder, EmbedderEnvInspector, EmbedderEnvReport, EmbedderError,
EmbedderProfileStore, EmbedderPromptDismissal, EmbedderPromptStore, EventBus, EventStream,
ExitStatus, FileSystem, FsError, GitCommitInfo, GitError, GitFileStatus, GitPort, GraphCommit,
IdGenerator, IssueAttachmentContent, IssueNumberAllocator, IssueStore, IssueStoreError,
LiveStateStore, LocalPath, McpToolPermissionStore, MemoryError, MemoryQuery, MemoryRecall,
MemoryStore, ModelArtifactCancel, ModelArtifactDownloader, ModelArtifactProgress,
ModelArtifactResolution, Output, OutputStream, PermissionStore, PluginManifestBytes,
PluginManifestError, PluginManifestValidator, PluginMcpError, PluginMcpSupervisor,
PluginPackageStore, PluginRegistryError, PluginRegistryStore, PluginStorageError,
PluginStorageStore, PluginStoreError, PluginWorkspaceExecAuditSink, PreparedContext,
ProcessError, ProcessSpawner, ProfileStore, ProjectPluginSettingsStore, ProjectSettingsStore,
ProjectStore, ProviderModelCatalogue, PtyError, PtyHandle, PtyPort, RemoteError, RemoteHost,
RemotePath, RuntimeError, RuntimePermissionProbe, ScheduledProjectTaskStore, ScheduledTask,
CustomProjectIconAssetStore, CustomProjectIconStoreError, DirEntry, Embedder,
EmbedderEnvInspector, EmbedderEnvReport, EmbedderError, EmbedderProfileStore,
EmbedderPromptDismissal, EmbedderPromptStore, EventBus, EventStream, ExitStatus, FileSystem,
FsError, GitCommitInfo, GitError, GitFileStatus, GitPort, GraphCommit, IdGenerator,
IssueAttachmentContent, IssueNumberAllocator, IssueStore, IssueStoreError, LiveStateStore,
LocalPath, McpToolPermissionStore, MemoryError, MemoryQuery, MemoryRecall, MemoryStore,
ModelArtifactCancel, ModelArtifactDownloader, ModelArtifactProgress, ModelArtifactResolution,
Output, OutputStream, PermissionStore, PluginManifestBytes, PluginManifestError,
PluginManifestValidator, PluginMcpError, PluginMcpSupervisor, PluginPackageStore,
PluginRegistryError, PluginRegistryStore, PluginStorageError, PluginStorageStore,
PluginStoreError, PluginWorkspaceExecAuditSink, PreparedContext, ProcessError, ProcessSpawner,
ProfileStore, ProjectPluginSettingsStore, ProjectSettingsStore, ProjectStore,
ProviderModelCatalogue, PtyError, PtyHandle, PtyPort, RemoteError, RemoteHost, RemotePath,
RuntimeError, RuntimePermissionProbe, ScheduledProjectTaskStore, ScheduledTask,
ScheduledTaskStoreError, Scheduler, SpawnSpec, SprintStore, SprintStoreError, StoreError,
StructuredSessionEnvironment, StructuredSessionEnvironmentPreparer, SystemPermissionStore,
TemplateStore, UserSettingsStore, WindowStateStore, WorkspaceToolExecError,

View File

@ -66,6 +66,7 @@ use crate::plugin::{
use crate::profile::{AgentProfile, EmbedderProfile, StructuredAdapter};
use crate::project::{Project, ProjectPath};
use crate::remote::RemoteKind;
use crate::settings::{CustomProjectIconAsset, CustomProjectIconContent};
use crate::skill::{Skill, SkillScope};
use crate::sprint::{Sprint, SprintIndexEntry, SprintVersion};
use crate::system_permissions::{ProjectSystemPermissions, RuntimePermissionSnapshot};
@ -564,6 +565,43 @@ pub trait ProjectSettingsStore: Send + Sync {
) -> Result<(), StoreError>;
}
/// Failures from the instance-local custom project icon library.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum CustomProjectIconStoreError {
/// Asset id is absent from the canonical index.
#[error("custom project icon asset not found")]
NotFound,
/// Persisted data is malformed.
#[error("custom project icon asset store format error: {0}")]
Format(String),
/// Filesystem operation failed.
#[error("custom project icon asset store I/O error: {0}")]
Io(String),
}
/// Persistence for the workspace-wide user icon library.
#[async_trait]
pub trait CustomProjectIconAssetStore: Send + Sync {
/// Lists persisted metadata in stable creation order.
async fn list(&self) -> Result<Vec<CustomProjectIconAsset>, CustomProjectIconStoreError>;
/// Reads metadata and bytes for one id.
async fn read(
&self,
asset_id: &str,
) -> Result<CustomProjectIconContent, CustomProjectIconStoreError>;
/// Returns whether an id exists in the canonical index.
async fn exists(&self, asset_id: &str) -> Result<bool, CustomProjectIconStoreError>;
/// Atomically adds metadata and bytes under the validated extension.
async fn create(
&self,
asset: &CustomProjectIconAsset,
extension: &str,
bytes: &[u8],
) -> Result<(), CustomProjectIconStoreError>;
/// Deletes one unused asset.
async fn delete(&self, asset_id: &str) -> Result<(), CustomProjectIconStoreError>;
}
/// Manifest parser and validator.
pub trait PluginManifestValidator: Send + Sync {
/// Validates raw manifest bytes.

View File

@ -38,6 +38,8 @@ pub struct IdeaUserSettings {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProjectIconSet {
/// User-imported icon from the instance-local shared library.
Custom,
/// Unicode emoji catalogue.
Emoji,
/// Devicon language and platform catalogue.
@ -50,6 +52,37 @@ pub enum ProjectIconSet {
Idea,
}
/// Metadata for one user-imported project icon.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomProjectIconAsset {
/// Opaque stable ASCII id used by [`ProjectIconRef`].
pub id: String,
/// Validated image media type.
pub media_type: String,
/// Stored byte count.
pub byte_size: u64,
/// Decoded pixel width.
pub width: u32,
/// Decoded pixel height.
pub height: u32,
/// Lowercase SHA-256 digest of the stored bytes.
pub sha256: String,
/// Basename supplied by the user; never a local path.
pub original_filename: String,
/// Creation time as epoch milliseconds.
pub created_at: i64,
}
/// Stored custom icon together with its validated image bytes.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomProjectIconContent {
/// Persisted metadata.
pub asset: CustomProjectIconAsset,
/// Exact raster bytes.
pub bytes: Vec<u8>,
}
/// Maximum byte length of a stable project icon name.
pub const MAX_PROJECT_ICON_NAME_LEN: usize = 64;