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

@ -3358,6 +3358,88 @@ pub struct ProjectIconRefDto {
pub name: String,
}
/// Metadata for one user-imported icon, including live project usage.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomProjectIconAssetDto {
/// Opaque stable asset id.
pub id: String,
/// Validated raster media type.
pub media_type: String,
/// Stored encoded byte count.
pub byte_size: u64,
/// Pixel width.
pub width: u32,
/// Pixel height.
pub height: u32,
/// SHA-256 digest.
pub sha256: String,
/// Original user-facing basename.
pub original_filename: String,
/// Creation time as epoch milliseconds.
pub created_at: i64,
/// Current number of registered projects using the asset.
pub used_by_project_count: u64,
}
impl From<application::CustomProjectIconAssetView> for CustomProjectIconAssetDto {
fn from(value: application::CustomProjectIconAssetView) -> Self {
Self {
id: value.asset.id,
media_type: value.asset.media_type,
byte_size: value.asset.byte_size,
width: value.asset.width,
height: value.asset.height,
sha256: value.asset.sha256,
original_filename: value.asset.original_filename,
created_at: value.asset.created_at,
used_by_project_count: value.used_by_project_count,
}
}
}
/// Import payload shared by Tauri and HTTP JSON clients.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ImportCustomProjectIconAssetRequestDto {
/// User-facing source basename (paths are stripped server-side).
pub original_filename: String,
/// Claimed media type, verified against file bytes.
pub media_type: String,
/// Standard base64-encoded image bytes.
pub data_base64: String,
}
/// Tauri-safe read response; HTTP serves these bytes directly instead.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomProjectIconAssetContentDto {
/// Metadata and usage.
#[serde(flatten)]
pub metadata: CustomProjectIconAssetDto,
/// Standard base64-encoded image bytes.
pub data_base64: String,
}
/// Current usage for one custom icon.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomProjectIconUsageDto {
/// Stable asset id.
pub asset_id: String,
/// Number of registered projects using it.
pub used_by_project_count: u64,
}
impl From<application::CustomProjectIconUsage> for CustomProjectIconUsageDto {
fn from(value: application::CustomProjectIconUsage) -> Self {
Self {
asset_id: value.asset_id,
used_by_project_count: value.used_by_project_count,
}
}
}
impl From<domain::ProjectIconRef> for ProjectIconRefDto {
fn from(value: domain::ProjectIconRef) -> Self {
Self {

View File

@ -21,30 +21,33 @@ use application::{
ClearChatDraft, CloneOpenCodeProfileFromSeed, CloneProfileFromSeed, CloseProject, CloseTab,
CloseTerminal, CloseTicketAssistant, ConfigureProfiles, ContextGuardUseCases,
CreateAgentFromScratch, CreateAgentFromTemplate, CreateIssue, CreateLayout, CreateMemory,
CreateProject, CreateSkill, CreateSprint, CreateTemplate, DeleteAgent, DeleteEmbedderProfile,
DeleteIssue, DeleteLayout, DeleteMemory, DeleteModelArtifact, DeleteModelServer, DeleteProfile,
DeleteSkill, DeleteSprint, DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift,
DetectProfiles, DismissEmbedderSuggestion, EnsureLocalModelServer, ExecuteSlashCommand,
FirstRunState, GcManagedTemp, GetAppExitWorkGuardState, GetLiveStateLean, GetMemory,
GetProjectPermissions, GetProjectSystemPermissions, GetProjectWorkState, GitBranches,
GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage,
HarvestMemoryFromTurn, HealthUseCase, ImportChatAttachments, InspectConversation,
InstallPluginFromArchive, InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent,
LaunchAgentInput, LinkIssues, ListAgents, ListAgentsInput, ListClaudeModels, ListCodexModels,
ListDevices, ListEmbedderProfiles, ListIssues, ListLayouts, ListMemories, ListModelServers,
ListOpenCodeProviders, ListPluginRuntimeContributions, ListPlugins, ListProfiles,
ListProjectFiles, ListProjectPluginSettings, ListProjects, ListResumableAgents, ListSkills,
ListSlashCommands, ListSprints, ListTemplates, LiveAgentRegistry, LiveSessions,
LiveStateLeanProvider, LiveStateProvider, LiveStateReadProvider, LoadLayout,
MarkIssueAttachmentSummarized, McpRuntime, McpToolPermissionCatalogue, MoveTabToNewWindow,
MutateAgentPluginSkill, MutateAgentPluginTool, MutateLayout, OnnxModelView,
OpenPluginLayoutWindow, OpenProject, OpenTerminal, OpenTicketAssistant, OrchestratorService,
PairAttemptLimiter, PairDevice, PermissionProjectorRegistry, PluginCommandTasks,
PluginConfigDocuments, PluginEventSubscriptions, PluginStorageAccess,
PluginToolchainDiagnostics, PluginWorkspaceAccess, PluginWorkspaceExecTool,
ProjectPluginResolver, ProposeContext, PurgeManagedTemp, QueryProjectStructure,
ReadAgentContext, ReadChatDraft, ReadContext, ReadConversationPage, ReadIssue,
ReadIssueAttachment, ReadIssueCarnet, ReadMcpToolPermissions, ReadMemory, ReadMemoryIndex,
CreateProject, CreateSkill, CreateSprint, CreateTemplate, DeleteAgent,
DeleteCustomProjectIconAsset, DeleteEmbedderProfile, DeleteIssue, DeleteLayout, DeleteMemory,
DeleteModelArtifact, DeleteModelServer, DeleteProfile, DeleteSkill, DeleteSprint,
DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles,
DismissEmbedderSuggestion, EnsureLocalModelServer, ExecuteSlashCommand, FirstRunState,
GcManagedTemp, GetAppExitWorkGuardState, GetCustomProjectIconAssetUsage, GetLiveStateLean,
GetMemory, GetProjectPermissions, GetProjectSystemPermissions, GetProjectWorkState,
GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus,
GitUnstage, HarvestMemoryFromTurn, HealthUseCase, ImportChatAttachments,
ImportCustomProjectIconAsset, InspectConversation, InstallPluginFromArchive,
InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput,
LinkIssues, ListAgents, ListAgentsInput, ListClaudeModels, ListCodexModels,
ListCustomProjectIconAssets, ListDevices, ListEmbedderProfiles, ListIssues, ListLayouts,
ListMemories, ListModelServers, ListOpenCodeProviders, ListPluginRuntimeContributions,
ListPlugins, ListProfiles, ListProjectFiles, ListProjectPluginSettings, ListProjects,
ListResumableAgents, ListSkills, ListSlashCommands, ListSprints, ListTemplates,
LiveAgentRegistry, LiveSessions, LiveStateLeanProvider, LiveStateProvider,
LiveStateReadProvider, LoadLayout, MarkIssueAttachmentSummarized, McpRuntime,
McpToolPermissionCatalogue, MoveTabToNewWindow, MutateAgentPluginSkill, MutateAgentPluginTool,
MutateLayout, OnnxModelView, OpenPluginLayoutWindow, OpenProject, OpenTerminal,
OpenTicketAssistant, OrchestratorService, PairAttemptLimiter, PairDevice,
PermissionProjectorRegistry, PluginCommandTasks, PluginConfigDocuments,
PluginEventSubscriptions, PluginStorageAccess, PluginToolchainDiagnostics,
PluginWorkspaceAccess, PluginWorkspaceExecTool, ProjectPluginResolver, ProposeContext,
PurgeManagedTemp, QueryProjectStructure, ReadAgentContext, ReadChatDraft, ReadContext,
ReadConversationPage, ReadCustomProjectIconAsset, ReadIssue, ReadIssueAttachment,
ReadIssueCarnet, ReadMcpToolPermissions, ReadMemory, ReadMemoryIndex,
ReadOpenProjectRestoreState, ReadProjectContext, ReadProjectSettings, ReadSkill, ReadTemplate,
ReadUserSettings, RecallMemory, ReconcileLayouts, ReconcileLiveState, ReconcileLiveStateInput,
ReconcilePluginMcpServers, RecordProjectClosedForRestore, RecordProjectOpenedForRestore,
@ -74,16 +77,16 @@ use domain::events::AgentRestartPhase;
use domain::ports::{
AgentContextStore, AgentRuntime, AgentSession, AgentSessionFactory, AgentToolPolicyStore,
AgentWakePort, AssistantContextProvider, BackgroundTaskPortError, BackgroundTaskRunner,
BackgroundTaskStore, ChatAttachmentStore, ChatDraftStore, Clock, DeviceSessionStore, Embedder,
EmbedderEnvInspector, EmbedderProfileStore, EmbedderPromptStore, EnvironmentReader, EventBus,
FileSystem, GitPort, IdGenerator, IssueNumberAllocator, IssueStore, ManagedTempStore,
McpToolPermissionStore, MemoryRecall, MemoryStore, ModelArtifactDownloader,
OpenProjectRestoreStore, PermissionStore, PluginManifestValidator, PluginMcpSupervisor,
PluginPackageStore, PluginRegistryStore, PluginStorageStore, ProcessSpawner, ProfileStore,
ProjectPluginSettingsStore, ProjectSettingsStore, ProjectStore, PtyHandle, PtyPort,
RuntimePermissionProbe, ScheduledTask, Scheduler, SecretStore, SkillStore, SprintStore,
StructuredSessionEnvironmentPreparer, SystemPermissionStore, TemplateStore, ToolInvoker,
UserSettingsStore, WakeError, WakeReason, WindowStateStore,
BackgroundTaskStore, ChatAttachmentStore, ChatDraftStore, Clock, CustomProjectIconAssetStore,
DeviceSessionStore, Embedder, EmbedderEnvInspector, EmbedderProfileStore, EmbedderPromptStore,
EnvironmentReader, EventBus, FileSystem, GitPort, IdGenerator, IssueNumberAllocator,
IssueStore, ManagedTempStore, McpToolPermissionStore, MemoryRecall, MemoryStore,
ModelArtifactDownloader, OpenProjectRestoreStore, PermissionStore, PluginManifestValidator,
PluginMcpSupervisor, PluginPackageStore, PluginRegistryStore, PluginStorageStore,
ProcessSpawner, ProfileStore, ProjectPluginSettingsStore, ProjectSettingsStore, ProjectStore,
PtyHandle, PtyPort, RuntimePermissionProbe, ScheduledTask, Scheduler, SecretStore, SkillStore,
SprintStore, StructuredSessionEnvironmentPreparer, SystemPermissionStore, TemplateStore,
ToolInvoker, UserSettingsStore, WakeError, WakeReason, WindowStateStore,
};
use domain::profile::{
AgentProfile, ContextInjection, McpConfigStrategy, McpTransport, StructuredAdapter,
@ -104,22 +107,22 @@ use infrastructure::{
CliAgentRuntime, CodexPermissionProjector, CommandBackgroundRunner,
DiagnosticPluginWorkspaceExecAudit, EmbeddedCompatibilityMatrix, EmbedderEnvProbe,
ExternalMcpPluginSupervisor, FsAssistantContextStore, FsBackgroundTaskStore,
FsChatAttachmentStore, FsChatDraftStore, FsConversationLog, FsDeviceSessionStore,
FsEmbedderProfileStore, FsEmbedderPromptStore, FsHandoffStore, FsIssueNumberAllocator,
FsIssueStore, FsLiveStateStore, FsManagedTempStore, FsMcpToolPermissionStore, FsMemoryStore,
FsModelServerRegistry, FsOpenProjectRestoreStore, FsOrchestratorWatcher, FsPermissionStore,
FsPluginPackageStore, FsPluginRegistryStore, FsPluginStorageStore, FsProfileStore,
FsProjectPluginSettingsStore, FsProjectSettingsStore, FsProjectStore, FsProviderSessionStore,
FsScheduledProjectTaskStore, FsSecretStore, FsSkillStore, FsSprintStore,
FsSystemPermissionStore, FsTemplateStore, FsUserSettingsStore, FsWindowStateStore,
FsWorkspaceToolProvider, Git2Repository, HeuristicHandoffSummarizer, HfModelArtifactDownloader,
HttpOpenAiCompatibleProbe, HttpProviderModelCatalogue, IdeaiContextStore,
InMemoryConversationRegistry, InMemoryMailbox, InMemoryPairAttemptLimiter, LlamaCppRuntime,
LocalEnvironmentReader, LocalFileSystem, LocalManagedProcess, LocalProcessSpawner,
LocalWorkspaceToolRunner, ManagedTempToolProvider, McpServer, MediatedInbox, NaiveMemoryRecall,
OrchestratorWatchHandle, PortablePtyAdapter, ProcessCliVersionReader,
ReadOnlyRuntimePermissionProbe, RwFileGuard, StructuredSessionFactory, SystemClock,
SystemMillisClock, TemplateToolProvider, TicketAssistantEnvironmentPreparer,
FsChatAttachmentStore, FsChatDraftStore, FsConversationLog, FsCustomProjectIconAssetStore,
FsDeviceSessionStore, FsEmbedderProfileStore, FsEmbedderPromptStore, FsHandoffStore,
FsIssueNumberAllocator, FsIssueStore, FsLiveStateStore, FsManagedTempStore,
FsMcpToolPermissionStore, FsMemoryStore, FsModelServerRegistry, FsOpenProjectRestoreStore,
FsOrchestratorWatcher, FsPermissionStore, FsPluginPackageStore, FsPluginRegistryStore,
FsPluginStorageStore, FsProfileStore, FsProjectPluginSettingsStore, FsProjectSettingsStore,
FsProjectStore, FsProviderSessionStore, FsScheduledProjectTaskStore, FsSecretStore,
FsSkillStore, FsSprintStore, FsSystemPermissionStore, FsTemplateStore, FsUserSettingsStore,
FsWindowStateStore, FsWorkspaceToolProvider, Git2Repository, HeuristicHandoffSummarizer,
HfModelArtifactDownloader, HttpOpenAiCompatibleProbe, HttpProviderModelCatalogue,
IdeaiContextStore, InMemoryConversationRegistry, InMemoryMailbox, InMemoryPairAttemptLimiter,
LlamaCppRuntime, LocalEnvironmentReader, LocalFileSystem, LocalManagedProcess,
LocalProcessSpawner, LocalWorkspaceToolRunner, ManagedTempToolProvider, McpServer,
MediatedInbox, NaiveMemoryRecall, OrchestratorWatchHandle, PortablePtyAdapter,
ProcessCliVersionReader, ReadOnlyRuntimePermissionProbe, RwFileGuard, StructuredSessionFactory,
SystemClock, SystemMillisClock, TemplateToolProvider, TicketAssistantEnvironmentPreparer,
TicketToolProvider, TokioBroadcastEventBus, TokioScheduler, ToolPolicyRegistry, UuidGenerator,
VectorMemoryRecall, WorkspaceToolProvider, DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR,
RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
@ -2267,6 +2270,16 @@ pub struct BackendCore {
pub read_project_settings: Arc<ReadProjectSettings>,
/// Updates settings owned by the active project.
pub update_project_settings: Arc<UpdateProjectSettings>,
/// Imports into the shared custom icon library.
pub import_custom_project_icon_asset: Arc<ImportCustomProjectIconAsset>,
/// Lists shared custom icons with live usage.
pub list_custom_project_icon_assets: Arc<ListCustomProjectIconAssets>,
/// Reads one custom icon and exact bytes.
pub read_custom_project_icon_asset: Arc<ReadCustomProjectIconAsset>,
/// Gets current project usage for one custom icon.
pub get_custom_project_icon_asset_usage: Arc<GetCustomProjectIconAssetUsage>,
/// Deletes an unused custom icon.
pub delete_custom_project_icon_asset: Arc<DeleteCustomProjectIconAsset>,
/// Read the project permission document.
pub get_project_permissions: Arc<GetProjectPermissions>,
/// Update project-level default permissions.
@ -2635,6 +2648,10 @@ impl BackendCore {
let project_settings_store = Arc::new(FsProjectSettingsStore::new(Arc::clone(&fs_port)));
let project_settings_store_port =
Arc::clone(&project_settings_store) as Arc<dyn ProjectSettingsStore>;
let custom_project_icon_asset_store =
Arc::new(FsCustomProjectIconAssetStore::new(&app_data_dir));
let custom_project_icon_asset_store_port =
Arc::clone(&custom_project_icon_asset_store) as Arc<dyn CustomProjectIconAssetStore>;
let read_user_settings =
Arc::new(ReadUserSettings::new(Arc::clone(&user_settings_store_port)));
let update_user_settings = Arc::new(UpdateUserSettings::new(Arc::clone(
@ -2643,9 +2660,35 @@ impl BackendCore {
let read_project_settings = Arc::new(ReadProjectSettings::new(Arc::clone(
&project_settings_store_port,
)));
let update_project_settings = Arc::new(UpdateProjectSettings::new(Arc::clone(
&project_settings_store_port,
)));
let update_project_settings = Arc::new(UpdateProjectSettings::new(
Arc::clone(&project_settings_store_port),
Arc::clone(&custom_project_icon_asset_store_port),
));
let import_custom_project_icon_asset = Arc::new(ImportCustomProjectIconAsset::new(
Arc::clone(&custom_project_icon_asset_store_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&clock) as Arc<dyn Clock>,
));
let list_custom_project_icon_assets = Arc::new(ListCustomProjectIconAssets::new(
Arc::clone(&custom_project_icon_asset_store_port),
Arc::clone(&store_port),
Arc::clone(&project_settings_store_port),
));
let read_custom_project_icon_asset = Arc::new(ReadCustomProjectIconAsset::new(
Arc::clone(&custom_project_icon_asset_store_port),
Arc::clone(&store_port),
Arc::clone(&project_settings_store_port),
));
let get_custom_project_icon_asset_usage = Arc::new(GetCustomProjectIconAssetUsage::new(
Arc::clone(&custom_project_icon_asset_store_port),
Arc::clone(&store_port),
Arc::clone(&project_settings_store_port),
));
let delete_custom_project_icon_asset = Arc::new(DeleteCustomProjectIconAsset::new(
custom_project_icon_asset_store_port,
Arc::clone(&store_port),
Arc::clone(&project_settings_store_port),
));
let open_project_restore_store_port =
Arc::clone(&open_project_restore_store) as Arc<dyn OpenProjectRestoreStore>;
let window_state_port = Arc::clone(&window_state_store) as Arc<dyn WindowStateStore>;
@ -4514,6 +4557,11 @@ impl BackendCore {
update_user_settings,
read_project_settings,
update_project_settings,
import_custom_project_icon_asset,
list_custom_project_icon_assets,
read_custom_project_icon_asset,
get_custom_project_icon_asset_usage,
delete_custom_project_icon_asset,
get_project_permissions,
update_project_permissions,
update_agent_permissions,