From a958052bbf6518b07c89c576bec93e4f8b99bf70 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 19 Mar 2026 09:16:11 +0000 Subject: [PATCH] asset details (WIP) --- .../p/packer/models/PackerFileCacheEntry.java | 5 +- .../FileSystemPackerCacheRepository.java | 54 ++++- .../PackerAbstractBankWalker.java | 5 +- .../PackerRuntimeAssetMaterializer.java | 3 +- .../FileSystemPackerWorkspaceService.java | 34 +++ .../services/PackerAssetDetailsService.java | 13 ++ .../FileSystemPackerCacheRepositoryTest.java | 33 ++- .../PackerAbstractBankWalkerTest.java | 18 +- .../FileSystemPackerWorkspaceServiceTest.java | 86 ++++++++ .../PackerAssetDetailsServiceTest.java | 27 +++ .../java/p/studio/utilities/i18n/I18n.java | 6 + .../assets/details/AssetDetailsControl.java | 34 ++- .../dialogs/AssetDiagnosticsDialog.java | 138 ++++++++++++ .../messages/AssetWorkspaceAssetDetails.java | 5 +- .../main/resources/i18n/messages.properties | 6 + .../resources/themes/default-prometeu.css | 13 ++ test-projects/main/.studio/activities.json | 196 +++++++++--------- .../main/assets/.prometeu/cache.json | 44 ++-- .../main/assets/recovered/atlas2/asset.json | 10 +- .../recovered/atlas2/confirm.palette.json | 4 +- .../assets/recovered/atlas2/confirm.tile.json | 4 +- .../main/assets/ui/atlas2/confirm.tile.json | 4 +- .../main/assets/ui/atlas2/wrong-palette.png | Bin 0 -> 248 bytes 23 files changed, 599 insertions(+), 143 deletions(-) create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java create mode 100644 test-projects/main/assets/ui/atlas2/wrong-palette.png diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerFileCacheEntry.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerFileCacheEntry.java index 0d88f22b..8a8b9d64 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerFileCacheEntry.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerFileCacheEntry.java @@ -1,6 +1,7 @@ package p.packer.models; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -10,7 +11,8 @@ public record PackerFileCacheEntry( long size, long lastModified, String fingerprint, - Map metadata) { + Map metadata, + List diagnostics) { public PackerFileCacheEntry { relativePath = normalizeRelativePath(relativePath); @@ -23,6 +25,7 @@ public record PackerFileCacheEntry( } fingerprint = normalizeOptional(fingerprint); metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata"))); + diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } private static String normalizeRelativePath(String relativePath) { diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/FileSystemPackerCacheRepository.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/FileSystemPackerCacheRepository.java index f5bbf715..a60c293a 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/FileSystemPackerCacheRepository.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/FileSystemPackerCacheRepository.java @@ -7,8 +7,11 @@ import p.packer.PackerWorkspacePaths; import p.packer.exceptions.PackerRegistryException; import p.packer.messages.PackerProjectContext; import p.packer.models.PackerAssetCacheEntry; +import p.packer.models.PackerDiagnostic; import p.packer.models.PackerFileCacheEntry; import p.packer.models.PackerWorkspaceCacheState; +import p.packer.messages.diagnostics.PackerDiagnosticCategory; +import p.packer.messages.diagnostics.PackerDiagnosticSeverity; import java.io.IOException; import java.nio.file.Files; @@ -54,7 +57,8 @@ public final class FileSystemPackerCacheRepository { file.size, file.lastModified, file.fingerprint, - file.metadata == null ? Map.of() : file.metadata)); + file.metadata == null ? Map.of() : file.metadata, + loadDiagnostics(file.diagnostics))); } } validateAssetId(asset.assetId); @@ -92,7 +96,8 @@ public final class FileSystemPackerCacheRepository { file.size(), file.lastModified(), file.fingerprint(), - file.metadata())) + file.metadata(), + saveDiagnostics(file.diagnostics()))) .toList())) .toList(); mapper.writerWithDefaultPrettyPrinter().writeValue(cachePath.toFile(), document); @@ -101,6 +106,39 @@ public final class FileSystemPackerCacheRepository { } } + private List loadDiagnostics(List diagnostics) { + if (diagnostics == null || diagnostics.isEmpty()) { + return List.of(); + } + final List loaded = new ArrayList<>(); + for (CacheDiagnosticDocument diagnostic : diagnostics) { + if (diagnostic == null || diagnostic.severity == null || diagnostic.category == null || diagnostic.message == null) { + continue; + } + loaded.add(new PackerDiagnostic( + diagnostic.severity, + diagnostic.category, + diagnostic.message, + diagnostic.evidencePath == null || diagnostic.evidencePath.isBlank() ? null : Path.of(diagnostic.evidencePath), + diagnostic.blocking)); + } + return List.copyOf(loaded); + } + + private List saveDiagnostics(List diagnostics) { + if (diagnostics == null || diagnostics.isEmpty()) { + return List.of(); + } + return diagnostics.stream() + .map(diagnostic -> new CacheDiagnosticDocument( + diagnostic.severity(), + diagnostic.category(), + diagnostic.message(), + diagnostic.evidencePath() == null ? null : diagnostic.evidencePath().toString(), + diagnostic.blocking())) + .toList(); + } + private void validateDuplicateAssetIds(List assets) { final Set assetIds = new HashSet<>(); for (PackerAssetCacheEntry asset : assets) { @@ -162,6 +200,16 @@ public final class FileSystemPackerCacheRepository { @JsonProperty("size") long size, @JsonProperty("last_modified") long lastModified, @JsonProperty("fingerprint") String fingerprint, - @JsonProperty("metadata") Map metadata) { + @JsonProperty("metadata") Map metadata, + @JsonProperty("diagnostics") List diagnostics) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private record CacheDiagnosticDocument( + @JsonProperty("severity") PackerDiagnosticSeverity severity, + @JsonProperty("category") PackerDiagnosticCategory category, + @JsonProperty("message") String message, + @JsonProperty("evidence_path") String evidencePath, + @JsonProperty("blocking") boolean blocking) { } } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java index 7136783e..e9cc43ec 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java @@ -73,7 +73,10 @@ public abstract class PackerAbstractBankWalker { final var fileProbe = fileProbeMaybe.get(); final var cachedEntry = resolvePriorFileCache(assetRoot, fileProbe.path(), priorAssetCache); if (cachedEntry.isPresent() && canReuseMetadata(fileProbe, cachedEntry.get())) { - probeResults.add(new PackerProbeResult(fileProbe, cachedEntry.get().metadata(), List.of())); + probeResults.add(new PackerProbeResult( + fileProbe, + cachedEntry.get().metadata(), + cachedEntry.get().diagnostics())); continue; } probeResults.add(processFileProbe(fileProbe, requirements)); diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java index 7f0f40cf..f82e91e7 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java @@ -74,7 +74,8 @@ public final class PackerRuntimeAssetMaterializer { file.size(), file.lastModified(), file.fingerprint(), - file.metadata())) + file.metadata(), + file.diagnostics())) .toList()); return new PackerRuntimeAssetMaterialization(runtimeAsset, Optional.of(assetCacheEntry)); } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java index ed698a5f..ece5cd0f 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java @@ -14,6 +14,7 @@ import p.packer.messages.diagnostics.PackerDiagnosticCategory; import p.packer.messages.diagnostics.PackerDiagnosticSeverity; import p.packer.models.*; import p.packer.repositories.FileSystemPackerCacheRepository; +import p.packer.repositories.PackerContractFingerprint; import p.packer.repositories.PackerRuntimeRegistry; import java.io.IOException; @@ -646,7 +647,11 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe } try { + final String previousContractFingerprint = contractFingerprint(manifest); patchManifestContract(manifest, request); + if (!Objects.equals(previousContractFingerprint, contractFingerprint(manifest))) { + clearManifestBankComposition(manifest); + } mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); final var runtime = runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterUpdateAssetContract( currentSnapshot, @@ -770,6 +775,35 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe } } + private String contractFingerprint(ObjectNode manifest) { + final JsonNode outputNode = manifest.path("output"); + if (!(outputNode instanceof ObjectNode outputObject)) { + return PackerContractFingerprint.metadataFingerprint(Map.of()); + } + final JsonNode metadataNode = outputObject.path("metadata"); + if (!(metadataNode instanceof ObjectNode metadataObject)) { + return PackerContractFingerprint.metadataFingerprint(Map.of()); + } + final Map metadata = new LinkedHashMap<>(); + metadataObject.fields().forEachRemaining(entry -> { + if (entry.getKey() == null || entry.getKey().isBlank()) { + return; + } + final JsonNode valueNode = entry.getValue(); + if (valueNode == null || valueNode.isContainerNode()) { + return; + } + metadata.put(entry.getKey().trim(), valueNode.isNull() ? "" : valueNode.asText("")); + }); + return PackerContractFingerprint.metadataFingerprint(metadata); + } + + private void clearManifestBankComposition(ObjectNode manifest) { + final ObjectNode inputsNode = mutableObject(manifest, "inputs"); + inputsNode.removeAll(); + manifest.putArray("artifacts"); + } + private boolean isTrustedRelativePath(String value) { final Path path = Path.of(value).normalize(); return !path.isAbsolute() && !path.startsWith(".."); diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java index 19150bfe..4f210b0b 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java @@ -44,6 +44,7 @@ public final class PackerAssetDetailsService { final var parsed = runtimeAsset.parsedDeclaration(); diagnostics.addAll(parsed.diagnostics()); diagnostics.addAll(runtimeAsset.walkDiagnostics()); + appendWalkFileDiagnostics(runtimeAsset, diagnostics); if (!parsed.valid()) { return failureResult(project, request.assetReference(), resolved, diagnostics); } @@ -253,6 +254,18 @@ public final class PackerAssetDetailsService { true)); } + private void appendWalkFileDiagnostics( + PackerRuntimeAsset runtimeAsset, + List diagnostics) { + for (PackerRuntimeWalkFile file : runtimeAsset.walkProjection().buildCandidateFiles()) { + for (PackerDiagnostic diagnostic : file.diagnostics()) { + if (!diagnostics.contains(diagnostic)) { + diagnostics.add(diagnostic); + } + } + } + } + private AssetReference canonicalReference( final PackerProjectContext project, final Path assetRoot, diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/FileSystemPackerCacheRepositoryTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/FileSystemPackerCacheRepositoryTest.java index fb00cbf7..a0147a8f 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/FileSystemPackerCacheRepositoryTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/FileSystemPackerCacheRepositoryTest.java @@ -7,8 +7,11 @@ import p.packer.PackerWorkspacePaths; import p.packer.exceptions.PackerRegistryException; import p.packer.messages.PackerProjectContext; import p.packer.models.PackerAssetCacheEntry; +import p.packer.models.PackerDiagnostic; import p.packer.models.PackerFileCacheEntry; import p.packer.models.PackerWorkspaceCacheState; +import p.packer.messages.diagnostics.PackerDiagnosticCategory; +import p.packer.messages.diagnostics.PackerDiagnosticSeverity; import java.nio.file.Files; import java.nio.file.Path; @@ -46,14 +49,21 @@ final class FileSystemPackerCacheRepositoryTest { 128L, 42L, "abc123", - Map.of("tile_count", 4)), + Map.of("tile_count", 4), + List.of(new PackerDiagnostic( + PackerDiagnosticSeverity.WARNING, + PackerDiagnosticCategory.HYGIENE, + "cached warning", + Path.of("/tmp/ui.png"), + false))), new PackerFileCacheEntry( "tiles/ui-2.png", "image/png", 256L, 84L, "def456", - Map.of()))), + Map.of(), + List.of()))), new PackerAssetCacheEntry(1, "contract-1", List.of( new PackerFileCacheEntry( "audio/click.wav", @@ -61,7 +71,8 @@ final class FileSystemPackerCacheRepositoryTest { 512L, 21L, null, - Map.of("sample_count", 8)))))); + Map.of("sample_count", 8), + List.of()))))); repository.save(project, state); final var loaded = repository.load(project); @@ -70,6 +81,8 @@ final class FileSystemPackerCacheRepositoryTest { assertEquals("contract-3", loaded.findAsset(3).orElseThrow().contractFingerprint()); assertTrue(loaded.findAsset(3).flatMap(asset -> asset.findFile("tiles/ui.png")).isPresent()); assertEquals(128L, loaded.findAsset(3).orElseThrow().findFile("tiles/ui.png").orElseThrow().size()); + assertEquals(1, loaded.findAsset(3).orElseThrow().findFile("tiles/ui.png").orElseThrow().diagnostics().size()); + assertEquals("cached warning", loaded.findAsset(3).orElseThrow().findFile("tiles/ui.png").orElseThrow().diagnostics().getFirst().message()); } @Test @@ -114,7 +127,7 @@ final class FileSystemPackerCacheRepositoryTest { } @Test - void doesNotSerializeDiagnostics() throws Exception { + void serializesDiagnosticsWhenPresent() throws Exception { final var repository = new FileSystemPackerCacheRepository(MAPPER); final var project = project(tempDir.resolve("project")); final var state = new PackerWorkspaceCacheState( @@ -126,13 +139,19 @@ final class FileSystemPackerCacheRepositoryTest { 128L, 42L, "abc123", - Map.of("warning_count", 2)))))); + Map.of("warning_count", 2), + List.of(new PackerDiagnostic( + PackerDiagnosticSeverity.WARNING, + PackerDiagnosticCategory.HYGIENE, + "cached diagnostic", + Path.of("/tmp/ui.png"), + false))))))); repository.save(project, state); final String json = Files.readString(PackerWorkspacePaths.cachePath(project)); - assertFalse(json.contains("diagnostic")); - assertFalse(json.contains("diagnostics")); + assertTrue(json.contains("\"diagnostics\"")); + assertTrue(json.contains("\"cached diagnostic\"")); assertTrue(json.contains("\"asset_id\"")); assertTrue(json.contains("\"contract_fingerprint\"")); assertTrue(json.contains("\"metadata\"")); diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerAbstractBankWalkerTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerAbstractBankWalkerTest.java index 89cef6f3..98de3c8c 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerAbstractBankWalkerTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerAbstractBankWalkerTest.java @@ -29,7 +29,7 @@ final class PackerAbstractBankWalkerTest { final var filePath = writeText(assetRoot.resolve("entry.txt"), "hello world"); final long lastModified = Files.getLastModifiedTime(filePath).toMillis(); final var priorCache = new PackerAssetCacheEntry(1, "contract", List.of( - new PackerFileCacheEntry("entry.txt", "text/plain", 1L, lastModified, "cached", Map.of("source", "cache")))); + new PackerFileCacheEntry("entry.txt", "text/plain", 1L, lastModified, "cached", Map.of("source", "cache"), List.of()))); final var result = walker.walk(assetRoot, "requirements", java.util.Optional.of(priorCache)); @@ -51,7 +51,8 @@ final class PackerAbstractBankWalkerTest { Files.size(filePath), currentLastModified - 1L, "cached", - Map.of("source", "cache")))); + Map.of("source", "cache"), + List.of()))); final var result = walker.walk(assetRoot, "requirements", java.util.Optional.of(priorCache)); @@ -74,13 +75,21 @@ final class PackerAbstractBankWalkerTest { Files.size(filePath), lastModified, fingerprint, - Map.of("source", "cache")))); + Map.of("source", "cache"), + List.of(new PackerDiagnostic( + p.packer.messages.diagnostics.PackerDiagnosticSeverity.WARNING, + p.packer.messages.diagnostics.PackerDiagnosticCategory.HYGIENE, + "cached file-scoped diagnostic", + filePath, + false))))); final var result = walker.walk(assetRoot, "requirements", java.util.Optional.of(priorCache)); assertEquals(0, walker.processCount()); assertEquals(1, walker.fingerprintCount()); assertEquals("cache", result.probeResults().getFirst().metadata().get("source")); + assertEquals(1, result.probeResults().getFirst().diagnostics().size()); + assertTrue(result.probeResults().getFirst().diagnostics().getFirst().message().contains("cached")); } @Test @@ -96,7 +105,8 @@ final class PackerAbstractBankWalkerTest { Files.size(filePath), lastModified, null, - Map.of("source", "cache")))); + Map.of("source", "cache"), + List.of()))); final var result = walker.walk(assetRoot, "requirements", java.util.Optional.of(priorCache)); diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java index e796bb3a..4fb2f948 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java @@ -15,6 +15,8 @@ import p.packer.repositories.PackerRuntimeRegistry; import p.packer.repositories.PackerRuntimeSnapshotLoader; import p.packer.testing.PackerFixtureLocator; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; @@ -245,6 +247,41 @@ final class FileSystemPackerWorkspaceServiceTest { assertEquals(1, loader.loadCount()); } + @Test + void updateAssetContractDropsPersistedBankCompositionWhenContractFingerprintChanges() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("update-contract-drops-bank")); + final Path assetRoot = projectRoot.resolve("assets/ui/atlas"); + writeTilePng(assetRoot.resolve("confirm.png"), 16); + writeTilePng(assetRoot.resolve("cancel.png"), 16); + final FileSystemPackerWorkspaceService service = service(); + + final var applyResult = service.applyBankComposition(new ApplyBankCompositionRequest( + project(projectRoot), + AssetReference.forAssetId(1), + List.of("cancel.png", "confirm.png"))); + assertTrue(applyResult.success()); + + final var updateResult = service.updateAssetContract(new UpdateAssetContractRequest( + project(projectRoot), + AssetReference.forAssetId(1), + true, + OutputCodecCatalog.NONE, + Map.of(), + Map.of("tile_size", "32x32"))); + assertTrue(updateResult.success()); + + final var manifest = MAPPER.readTree(assetRoot.resolve("asset.json").toFile()); + assertTrue(manifest.path("inputs").isObject()); + assertTrue(manifest.path("inputs").isEmpty()); + assertTrue(manifest.path("artifacts").isArray()); + assertTrue(manifest.path("artifacts").isEmpty()); + + final var detailsResult = service.getAssetDetails(new GetAssetDetailsRequest( + project(projectRoot), + AssetReference.forAssetId(1))); + assertTrue(detailsResult.details().bankComposition().selectedFiles().isEmpty()); + } + @Test void applyBankCompositionWritesArtifactsAndRefreshesSnapshotWithoutWholeProjectReload() throws Exception { final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("apply-bank-composition")); @@ -284,6 +321,44 @@ final class FileSystemPackerWorkspaceServiceTest { assertEquals(1, loader.loadCount()); } + @Test + void deepSyncPreservesContractDrivenBankInvalidationAfterPatchUpdate() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("deep-sync-bank-composition")); + final Path assetRoot = projectRoot.resolve("assets/ui/atlas"); + writeTilePng(assetRoot.resolve("confirm.png"), 16); + writeTilePng(assetRoot.resolve("cancel.png"), 16); + final FileSystemPackerWorkspaceService service = service(); + + final var applyResult = service.applyBankComposition(new ApplyBankCompositionRequest( + project(projectRoot), + AssetReference.forAssetId(1), + List.of("cancel.png", "confirm.png"))); + assertTrue(applyResult.success()); + + final var updateResult = service.updateAssetContract(new UpdateAssetContractRequest( + project(projectRoot), + AssetReference.forAssetId(1), + true, + OutputCodecCatalog.NONE, + Map.of(), + Map.of("tile_size", "32x32"))); + assertTrue(updateResult.success()); + + final var patchedDetails = service.getAssetDetails(new GetAssetDetailsRequest( + project(projectRoot), + AssetReference.forAssetId(1))); + assertTrue(patchedDetails.details().bankComposition().availableFiles().isEmpty()); + assertTrue(patchedDetails.details().bankComposition().selectedFiles().isEmpty()); + + service.listAssets(new ListAssetsRequest(project(projectRoot), true)); + + final var refreshedDetails = service.getAssetDetails(new GetAssetDetailsRequest( + project(projectRoot), + AssetReference.forAssetId(1))); + assertTrue(refreshedDetails.details().bankComposition().availableFiles().isEmpty()); + assertTrue(refreshedDetails.details().bankComposition().selectedFiles().isEmpty()); + } + @Test void returnsFailureWhenAssetManifestIsMissingOnContractUpdate() throws Exception { final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("update-contract-missing-manifest")); @@ -786,4 +861,15 @@ final class FileSystemPackerWorkspaceServiceTest { } return targetRoot; } + + private void writeTilePng(Path path, int tileSize) throws Exception { + Files.createDirectories(path.getParent()); + final BufferedImage image = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < tileSize; y += 1) { + for (int x = 0; x < tileSize; x += 1) { + image.setRGB(x, y, 0xFFFF0000); + } + } + ImageIO.write(image, "png", path.toFile()); + } } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java index b27455bb..1087c92a 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java @@ -107,6 +107,22 @@ final class PackerAssetDetailsServiceTest { result.details().bankComposition().selectedFiles().stream().map(file -> file.path()).toList()); } + @Test + void includesFileScopedDiagnosticsInAssetDetailsDiagnostics() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed-file-diagnostics")); + final Path assetRoot = projectRoot.resolve("assets/ui/atlas"); + writeTilePng(assetRoot.resolve("wrong-palette.png"), 32); + + final PackerAssetDetailsService service = service(); + final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forAssetId(1))); + + assertEquals(PackerOperationStatus.PARTIAL, result.status()); + assertTrue(result.diagnostics().stream() + .anyMatch(diagnostic -> diagnostic.message().contains("Invalid tile dimensions for wrong-palette.png"))); + assertTrue(result.details().diagnostics().stream() + .anyMatch(diagnostic -> diagnostic.message().contains("Invalid tile dimensions for wrong-palette.png"))); + } + @Test void returnsUnregisteredDetailsForValidUnregisteredRootReference() throws Exception { final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("orphan")); @@ -224,4 +240,15 @@ final class PackerAssetDetailsServiceTest { } return targetRoot; } + + private void writeTilePng(Path path, int tileSize) throws Exception { + Files.createDirectories(path.getParent()); + final BufferedImage image = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < tileSize; y += 1) { + for (int x = 0; x < tileSize; x += 1) { + image.setRGB(x, y, 0xFFFF0000); + } + } + ImageIO.write(image, "png", path.toFile()); + } } diff --git a/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java index b9befc19..5192e36c 100644 --- a/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java +++ b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java @@ -104,6 +104,7 @@ public enum I18n { ASSETS_SECTION_ACTIONS("assets.section.actions"), ASSETS_ACTIONS_EMPTY("assets.actions.empty"), ASSETS_ACTION_REGISTER("assets.action.register"), + ASSETS_ACTION_ANALYSE("assets.action.analyse"), ASSETS_ACTION_DELETE("assets.action.delete"), ASSETS_ACTION_INCLUDE_IN_BUILD("assets.action.includeInBuild"), ASSETS_ACTION_EXCLUDE_FROM_BUILD("assets.action.excludeFromBuild"), @@ -162,6 +163,11 @@ public enum I18n { ASSETS_LOGS_TITLE("assets.logs.title"), ASSETS_INPUTS_EMPTY("assets.inputs.empty"), ASSETS_DIAGNOSTICS_EMPTY("assets.diagnostics.empty"), + ASSETS_DIAGNOSTICS_DIALOG_TITLE("assets.diagnostics.dialog.title"), + ASSETS_DIAGNOSTICS_DIALOG_SUMMARY("assets.diagnostics.dialog.summary"), + ASSETS_DIAGNOSTICS_LABEL_CATEGORY("assets.diagnostics.label.category"), + ASSETS_DIAGNOSTICS_LABEL_EVIDENCE("assets.diagnostics.label.evidence"), + ASSETS_DIAGNOSTICS_LABEL_BLOCKING("assets.diagnostics.label.blocking"), ASSETS_PREVIEW_EMPTY("assets.preview.empty"), ASSETS_PREVIEW_ZOOM("assets.preview.zoom"), ASSETS_PREVIEW_TEXT_ERROR("assets.preview.textError"), diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java index c54177d2..409907b7 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java @@ -10,6 +10,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import p.packer.dtos.PackerAssetActionAvailabilityDTO; import p.packer.dtos.PackerAssetDetailsDTO; +import p.packer.dtos.PackerDiagnosticDTO; import p.packer.messages.*; import p.studio.Container; import p.studio.controls.forms.StudioFormEditScopeChangedEvent; @@ -17,6 +18,7 @@ import p.studio.controls.forms.StudioSection; import p.studio.events.StudioWorkspaceEventBus; import p.studio.projects.ProjectReference; import p.studio.utilities.i18n.I18n; +import p.studio.workspaces.assets.dialogs.AssetDiagnosticsDialog; import p.studio.workspaces.assets.details.bank.AssetDetailsBankCompositionControl; import p.studio.workspaces.assets.details.contract.AssetDetailsContractControl; import p.studio.workspaces.assets.details.summary.AssetDetailsSummaryControl; @@ -162,7 +164,10 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware final var actionsResponse = workspaceService.getAssetActions(new GetAssetActionsRequest( projectReference.toPackerProjectContext(), assetReference)); - final AssetWorkspaceAssetDetails details = mapDetails(response.details(), actionsResponse.actions()); + final AssetWorkspaceAssetDetails details = mapDetails( + response.details(), + response.diagnostics(), + actionsResponse.actions()); Platform.runLater(() -> applyLoadedDetails(generation, assetReference, details)); } catch (RuntimeException exception) { Platform.runLater(() -> applyLoadFailure(generation, assetReference, exception)); @@ -305,7 +310,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware final var visibleActions = viewState.selectedAssetDetails().actions().stream() .filter(AssetWorkspaceAssetAction::visible) .toList(); - if (visibleActions.isEmpty()) { + if (visibleActions.isEmpty() && viewState.selectedAssetDetails().diagnostics().isEmpty()) { nodes.add(AssetDetailsUiSupport.createSectionMessage(Container.i18n().text(I18n.ASSETS_ACTIONS_EMPTY))); return nodes; } @@ -315,6 +320,10 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware button.setOnAction(ignored -> executeAction(action)); nodes.add(button); } + final Button analyseButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_ANALYSE)); + analyseButton.setDisable(viewState.selectedAssetDetails().diagnostics().isEmpty()); + analyseButton.setOnAction(ignored -> openDiagnosticsDialog()); + nodes.add(analyseButton); return nodes; } @@ -365,6 +374,17 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware Container.backgroundTasks().submit(() -> deleteSelectedAsset(assetReference)); } + private void openDiagnosticsDialog() { + if (viewState.selectedAssetDetails() == null || getScene() == null) { + return; + } + AssetDiagnosticsDialog.showAndWait( + getScene().getWindow(), + projectReference, + viewState.selectedAssetDetails().summary().assetName(), + viewState.selectedAssetDetails().diagnostics()); + } + private void registerSelectedAsset(AssetReference assetReference) { try { final RegisterAssetResult result = Container.packer().workspaceService().registerAsset(new RegisterAssetRequest( @@ -428,7 +448,14 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware private AssetWorkspaceAssetDetails mapDetails( PackerAssetDetailsDTO details, + java.util.List diagnostics, java.util.List actions) { + final java.util.List mergedDiagnostics = new java.util.ArrayList<>(details.diagnostics()); + for (PackerDiagnosticDTO diagnostic : diagnostics) { + if (!mergedDiagnostics.contains(diagnostic)) { + mergedDiagnostics.add(diagnostic); + } + } return new AssetWorkspaceAssetDetails( AssetListPackerMappings.mapSummary(details.summary()), actions.stream() @@ -444,7 +471,8 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware details.codecConfigurationFieldsByCodec(), details.metadataFields(), mapBankComposition(details.bankComposition()), - Map.copyOf(details.inputsByRole())); + Map.copyOf(details.inputsByRole()), + mergedDiagnostics); } private AssetWorkspaceBankCompositionDetails mapBankComposition(p.packer.dtos.PackerBankCompositionDetailsDTO details) { diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java new file mode 100644 index 00000000..3483b134 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/dialogs/AssetDiagnosticsDialog.java @@ -0,0 +1,138 @@ +package p.studio.workspaces.assets.dialogs; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.Window; +import p.packer.dtos.PackerDiagnosticDTO; +import p.packer.messages.diagnostics.PackerDiagnosticSeverity; +import p.studio.Container; +import p.studio.projects.ProjectReference; +import p.studio.utilities.i18n.I18n; +import p.studio.workspaces.assets.details.AssetDetailsUiSupport; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +public final class AssetDiagnosticsDialog { + private final Stage stage; + private final ProjectReference projectReference; + private final String assetName; + private final List diagnostics; + + private AssetDiagnosticsDialog( + Window owner, + ProjectReference projectReference, + String assetName, + List diagnostics) { + this.projectReference = Objects.requireNonNull(projectReference, "projectReference"); + this.assetName = Objects.requireNonNull(assetName, "assetName"); + this.diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); + this.stage = new Stage(); + stage.initOwner(owner); + stage.initModality(Modality.WINDOW_MODAL); + stage.setTitle(Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_DIALOG_TITLE)); + stage.setScene(new Scene(buildRoot(), 720, 560)); + stage.getScene().getStylesheets().add(Container.theme().getDefaultTheme()); + } + + public static void showAndWait( + Window owner, + ProjectReference projectReference, + String assetName, + List diagnostics) { + new AssetDiagnosticsDialog(owner, projectReference, assetName, diagnostics).stage.showAndWait(); + } + + private VBox buildRoot() { + final Label title = new Label(Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_DIALOG_TITLE)); + title.getStyleClass().add("studio-launcher-section-title"); + + final Label subtitle = new Label(Container.i18n().format( + I18n.ASSETS_DIAGNOSTICS_DIALOG_SUMMARY, + diagnostics.size(), + assetName)); + subtitle.getStyleClass().add("studio-launcher-subtitle"); + subtitle.setWrapText(true); + + final VBox diagnosticsContent = new VBox(10); + diagnosticsContent.getStyleClass().add("assets-diagnostics-dialog-content"); + if (diagnostics.isEmpty()) { + diagnosticsContent.getChildren().add(AssetDetailsUiSupport.createSectionMessage( + Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_EMPTY))); + } else { + diagnostics.forEach(diagnostic -> diagnosticsContent.getChildren().add(createDiagnosticCard(diagnostic))); + } + + final ScrollPane scrollPane = new ScrollPane(diagnosticsContent); + scrollPane.setFitToWidth(true); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.getStyleClass().add("assets-diagnostics-dialog-scroll"); + + final Button closeButton = new Button(); + closeButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_CANCEL)); + closeButton.getStyleClass().addAll("studio-button", "studio-button-cancel"); + closeButton.setOnAction(ignored -> stage.close()); + + final HBox actions = new HBox(closeButton); + actions.setAlignment(Pos.CENTER_RIGHT); + + final VBox root = new VBox(16, title, subtitle, scrollPane, actions); + root.setPadding(new Insets(24)); + VBox.setVgrow(scrollPane, Priority.ALWAYS); + return root; + } + + private VBox createDiagnosticCard(PackerDiagnosticDTO diagnostic) { + final Label severity = new Label(severityLabel(diagnostic)); + severity.getStyleClass().add("assets-details-diagnostic-severity"); + + final Label message = new Label(diagnostic.message()); + message.getStyleClass().add("assets-details-diagnostic-message"); + message.setWrapText(true); + + final VBox card = new VBox(8, severity, message); + card.getStyleClass().addAll("assets-details-diagnostic-card", severityToneClass(diagnostic.severity())); + + card.getChildren().add(AssetDetailsUiSupport.createKeyValueRow( + Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_LABEL_CATEGORY), + diagnostic.category().name())); + card.getChildren().add(AssetDetailsUiSupport.createKeyValueRow( + Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_LABEL_BLOCKING), + AssetDetailsUiSupport.booleanLabel(diagnostic.blocking()))); + card.getChildren().add(AssetDetailsUiSupport.createKeyValueRow( + Container.i18n().text(I18n.ASSETS_DIAGNOSTICS_LABEL_EVIDENCE), + evidenceLabel(diagnostic.evidencePath()))); + return card; + } + + private String severityLabel(PackerDiagnosticDTO diagnostic) { + return diagnostic.blocking() + ? diagnostic.severity().name() + " / BLOCKING" + : diagnostic.severity().name(); + } + + private String severityToneClass(PackerDiagnosticSeverity severity) { + return switch (severity) { + case ERROR -> "assets-details-diagnostic-blocker"; + case WARNING -> "assets-details-diagnostic-warning"; + case INFO -> "assets-details-diagnostic-hint"; + }; + } + + private String evidenceLabel(Path evidencePath) { + if (evidencePath == null) { + return "—"; + } + return AssetDetailsUiSupport.projectRelativePath(projectReference, evidencePath); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java index b4da19d3..23e56a52 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java @@ -1,6 +1,7 @@ package p.studio.workspaces.assets.messages; import p.packer.dtos.PackerCodecConfigurationFieldDTO; +import p.packer.dtos.PackerDiagnosticDTO; import p.packer.messages.assets.OutputCodecCatalog; import p.packer.messages.assets.OutputFormatCatalog; @@ -18,7 +19,8 @@ public record AssetWorkspaceAssetDetails( Map> codecConfigurationFieldsByCodec, List metadataFields, AssetWorkspaceBankCompositionDetails bankComposition, - Map> inputsByRole) { + Map> inputsByRole, + List diagnostics) { public AssetWorkspaceAssetDetails { Objects.requireNonNull(summary, "summary"); @@ -30,5 +32,6 @@ public record AssetWorkspaceAssetDetails( metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole")); + diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } } diff --git a/prometeu-studio/src/main/resources/i18n/messages.properties b/prometeu-studio/src/main/resources/i18n/messages.properties index c6a278da..60c9ff73 100644 --- a/prometeu-studio/src/main/resources/i18n/messages.properties +++ b/prometeu-studio/src/main/resources/i18n/messages.properties @@ -94,6 +94,7 @@ assets.section.diagnostics=Diagnostics assets.section.actions=Actions assets.actions.empty=No actions available for this asset. assets.action.register=Register +assets.action.analyse=Analyse assets.action.delete=Delete assets.deleteDialog.title=Delete Asset assets.deleteDialog.description=Type the asset name below to confirm deletion of {0}. @@ -153,6 +154,11 @@ assets.progress.loadingDetails=Loading selected asset details... assets.logs.title=Logs assets.inputs.empty=No previewable inputs are currently declared for this asset. assets.diagnostics.empty=No diagnostics are currently attached to this asset. +assets.diagnostics.dialog.title=Asset Diagnostics +assets.diagnostics.dialog.summary={0} diagnostics for {1} +assets.diagnostics.label.category=Category +assets.diagnostics.label.evidence=Evidence +assets.diagnostics.label.blocking=Blocking assets.preview.empty=Select an input to preview it here. assets.preview.zoom=Zoom assets.preview.textError=Unable to read this text-like input for preview. diff --git a/prometeu-studio/src/main/resources/themes/default-prometeu.css b/prometeu-studio/src/main/resources/themes/default-prometeu.css index 57b52824..731a46ca 100644 --- a/prometeu-studio/src/main/resources/themes/default-prometeu.css +++ b/prometeu-studio/src/main/resources/themes/default-prometeu.css @@ -572,6 +572,19 @@ -fx-spacing: 10; } +.assets-diagnostics-dialog-scroll { + -fx-background-color: transparent; + -fx-fit-to-width: true; +} + +.assets-diagnostics-dialog-scroll > .viewport { + -fx-background-color: transparent; +} + +.assets-diagnostics-dialog-content { + -fx-spacing: 10; +} + .assets-details-action-button { -fx-max-width: Infinity; -fx-padding: 6 10 6 10; diff --git a/test-projects/main/.studio/activities.json b/test-projects/main/.studio/activities.json index ee852ced..8e076158 100644 --- a/test-projects/main/.studio/activities.json +++ b/test-projects/main/.studio/activities.json @@ -1649,20 +1649,105 @@ "severity" : "INFO", "sticky" : false }, { - "source" : "Studio", - "message" : "Project ready", + "source" : "Assets", + "message" : "7 assets loaded", "severity" : "SUCCESS", "sticky" : false }, { - "source" : "Studio", - "message" : "Project loading started", + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", "severity" : "INFO", "sticky" : false }, { - "source" : "Studio", - "message" : "Project opened", + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", "severity" : "SUCCESS", "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false }, { "source" : "Assets", "message" : "7 assets loaded", @@ -2270,49 +2355,9 @@ "sticky" : false }, { "source" : "Assets", - "message" : "Discovered asset: bla", + "message" : "Asset scan diagnostics updated.", "severity" : "INFO", "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false }, { "source" : "Assets", "message" : "Discovered asset: bla", @@ -2360,49 +2405,9 @@ "sticky" : false }, { "source" : "Assets", - "message" : "Discovered asset: bla", + "message" : "Asset scan diagnostics updated.", "severity" : "INFO", "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false }, { "source" : "Assets", "message" : "Discovered asset: bla", @@ -2448,6 +2453,11 @@ "message" : "7 assets loaded", "severity" : "SUCCESS", "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false }, { "source" : "Assets", "message" : "Discovered asset: bla", @@ -2488,14 +2498,4 @@ "message" : "Asset scan started", "severity" : "INFO", "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false } ] \ No newline at end of file diff --git a/test-projects/main/assets/.prometeu/cache.json b/test-projects/main/assets/.prometeu/cache.json index ebdfb346..8376c09e 100644 --- a/test-projects/main/assets/.prometeu/cache.json +++ b/test-projects/main/assets/.prometeu/cache.json @@ -19,7 +19,28 @@ "height" : 16, "paletteIndices" : "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAEBAQEBAQEBAQEBAQAAAAABAQEBAQEBAQEBAQEAAAAAAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAAAAAABAQEBAQEAAAAAAAAAAAAAAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAABAQEBAQEBAQEAAAAAAAAAAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAABAQEBAQEBAQEAAAAAAAAAAQEBAQEBAQEBAAAAAAAAAAEBAQEBAQ==" } - } + }, + "diagnostics" : [ ] + }, { + "relative_path" : "wrong-palette.png", + "mime_type" : "image/png", + "size" : 248, + "last_modified" : 1773911050482, + "fingerprint" : "15850f68547775866b01a0fe0b0012bb0243dec303ce1f9c3e02220e05b593e6", + "metadata" : { }, + "diagnostics" : [ { + "severity" : "ERROR", + "category" : "STRUCTURAL", + "message" : "Invalid tile dimensions for wrong-palette.png: expected 16x16 but got 32x32", + "evidence_path" : "/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/studio/test-projects/main/assets/ui/atlas2/wrong-palette.png", + "blocking" : true + }, { + "severity" : "ERROR", + "category" : "STRUCTURAL", + "message" : "Tile image exceeds color limit for wrong-palette.png: expected at most 15 colors for indices 1..15", + "evidence_path" : "/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/studio/test-projects/main/assets/ui/atlas2/wrong-palette.png", + "blocking" : true + } ] } ] }, { "asset_id" : 7, @@ -35,24 +56,21 @@ "files" : [ ] }, { "asset_id" : 12, - "contract_fingerprint" : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "contract_fingerprint" : "d27435217b20e485d55447c55427995bec564b27481b76fd9d74798adf835005", "files" : [ { "relative_path" : "confirm.png", "mime_type" : "image/png", "size" : 137, "last_modified" : 1773253076764, "fingerprint" : "aa7d241deabcebe29a6096e14eaf16fdc06cf06380c11a507620b00fc7bff094", - "metadata" : { - "palette" : { - "originalArgb8888" : [ -265674 ], - "convertedRgb565" : [ -122 ] - }, - "tile" : { - "width" : 16, - "height" : 16, - "paletteIndices" : "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAEBAQEBAQEBAQEBAQAAAAABAQEBAQEBAQEBAQEAAAAAAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAAAAAABAQEBAQEAAAAAAAAAAAAAAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAABAQEBAQEBAQEAAAAAAAAAAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAABAQEBAQEBAQEAAAAAAAAAAQEBAQEBAQEBAAAAAAAAAAEBAQEBAQ==" - } - } + "metadata" : { }, + "diagnostics" : [ { + "severity" : "ERROR", + "category" : "STRUCTURAL", + "message" : "Invalid tile dimensions for confirm.png: expected 32x32 but got 16x16", + "evidence_path" : "/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/studio/test-projects/main/assets/recovered/atlas2/confirm.png", + "blocking" : true + } ] } ] }, { "asset_id" : 13, diff --git a/test-projects/main/assets/recovered/atlas2/asset.json b/test-projects/main/assets/recovered/atlas2/asset.json index ac7c650c..bc6af686 100644 --- a/test-projects/main/assets/recovered/atlas2/asset.json +++ b/test-projects/main/assets/recovered/atlas2/asset.json @@ -7,13 +7,13 @@ "output" : { "format" : "TILES/indexed_v1", "codec" : "NONE", - "codec_configuration" : { } + "codec_configuration" : { }, + "metadata" : { + "tile_size" : "32x32" + } }, "preload" : { "enabled" : false }, - "artifacts" : [ { - "file" : "confirm.png", - "index" : 0 - } ] + "artifacts" : [ ] } \ No newline at end of file diff --git a/test-projects/main/assets/recovered/atlas2/confirm.palette.json b/test-projects/main/assets/recovered/atlas2/confirm.palette.json index 2c60a786..33afab5a 100644 --- a/test-projects/main/assets/recovered/atlas2/confirm.palette.json +++ b/test-projects/main/assets/recovered/atlas2/confirm.palette.json @@ -1,4 +1,4 @@ { - "originalArgb8888" : [ -265674 ], - "convertedRgb565" : [ 65414 ] + "convertedRgb565" : [ 65414 ], + "originalArgb8888" : [ -265674 ] } \ No newline at end of file diff --git a/test-projects/main/assets/recovered/atlas2/confirm.tile.json b/test-projects/main/assets/recovered/atlas2/confirm.tile.json index c83d7359..ca73da31 100644 --- a/test-projects/main/assets/recovered/atlas2/confirm.tile.json +++ b/test-projects/main/assets/recovered/atlas2/confirm.tile.json @@ -1,5 +1,5 @@ { - "paletteIndices" : [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 ], + "height" : 16, "width" : 16, - "height" : 16 + "paletteIndices" : [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 ] } \ No newline at end of file diff --git a/test-projects/main/assets/ui/atlas2/confirm.tile.json b/test-projects/main/assets/ui/atlas2/confirm.tile.json index 19855a64..dc1c3c19 100644 --- a/test-projects/main/assets/ui/atlas2/confirm.tile.json +++ b/test-projects/main/assets/ui/atlas2/confirm.tile.json @@ -1,5 +1,5 @@ { - "width" : 16, "height" : 16, - "paletteIndices" : [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 ] + "paletteIndices" : [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 ], + "width" : 16 } \ No newline at end of file diff --git a/test-projects/main/assets/ui/atlas2/wrong-palette.png b/test-projects/main/assets/ui/atlas2/wrong-palette.png new file mode 100644 index 0000000000000000000000000000000000000000..c629483da09860f95c4872b645efa5e2f4a3a8ab GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}n><|{Ln2y} z|NK4q#@?Hs**Rpf!QnKw^0xDRdw8u+PD~!#D7o2