diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactParts.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactParts.cs
index 72bd5474..3ea31f26 100644
--- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactParts.cs
+++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactParts.cs
@@ -11,7 +11,7 @@ namespace AIStudio.Assistants.VisualBriefing;
/// The safe presentation stylesheet.
/// The embedded AI Studio runtime.
/// The optional embedded Apache ECharts runtime.
-/// The protected payload hash.
+/// The SHA-256 hash of the complete standalone document.
public sealed record VisualBriefingArtifactParts(
VisualBriefingExportManifest ExportManifest,
JsonElement Data,
@@ -19,4 +19,4 @@ public sealed record VisualBriefingArtifactParts(
string Css,
string RuntimeScript,
string? EChartsScript,
- string PayloadHash);
\ No newline at end of file
+ string DocumentHash);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs
index 17ad23de..5b01c5de 100644
--- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs
+++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs
@@ -25,12 +25,7 @@ public sealed partial class VisualBriefingArtifactService
/// An existing chart runtime to reuse, keeping a revision reproducible.
/// The cancellation token.
/// The complete standalone HTML document.
- public Task BuildAsync(
- VisualBriefingManifest manifest,
- VisualBriefingRevisionRequest request,
- string? lockedRuntimeScript = null,
- string? lockedEChartsScript = null,
- CancellationToken token = default)
+ public Task BuildAsync(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request, string? lockedRuntimeScript = null, string? lockedEChartsScript = null, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();
var data = AddProtectedArtifactData(manifest, request);
@@ -51,49 +46,59 @@ public sealed partial class VisualBriefingArtifactService
if (usesCharts && string.IsNullOrWhiteSpace(echarts))
throw new InvalidOperationException("Apache ECharts 6.1.0 common is not available in this AI Studio build.");
- var payloadHash = ComputePayloadHash(dataJson, template, css, runtime, echarts);
var exportMetadata = request.ExportMetadataSource;
var htmlLanguage = GetHtmlLanguage(
exportMetadata?.TargetLanguage ?? manifest.Settings.TargetLanguage,
exportMetadata?.CustomTargetLanguage ?? manifest.Settings.CustomTargetLanguage);
var briefingName = exportMetadata?.Name ?? manifest.Name;
- var exportManifest = CreateExportManifest(
- manifest,
- request,
- payloadHash,
- this.AIStudioVersion,
- runtimeAIStudioVersion);
-
- var encodedManifest = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(exportManifest, JSON_OPTIONS)));
- var csp = GetContentSecurityPolicy(new(exportManifest, data, template, css, runtime, echarts, payloadHash));
+ var exportManifest = CreateExportManifest(manifest, request, DOCUMENT_HASH_PLACEHOLDER, this.AIStudioVersion, runtimeAIStudioVersion);
- return Task.FromResult($"""
-
-
-
-
-
-
-
- {HtmlEncode(briefingName)}
-
-
-
-
-
- {template}
-
- {BuildScriptTag(echarts, "mwai-echarts-runtime")}
-
-
-
- """);
+ var parts = new VisualBriefingArtifactParts(exportManifest, data, template, css, runtime, echarts, DOCUMENT_HASH_PLACEHOLDER);
+ var csp = GetContentSecurityPolicy(parts);
+ var placeholderDocument = AssembleDocument(exportManifest, htmlLanguage, briefingName, dataJson, template, css, runtime, echarts, csp);
+
+ exportManifest.DocumentHash = VisualBriefingHashing.Compute(placeholderDocument);
+ return Task.FromResult(AssembleDocument(exportManifest, htmlLanguage, briefingName, dataJson, template, css, runtime, echarts, csp));
}
+ ///
+ /// Assembles the deterministic document around a supplied artifact header.
+ ///
+ private static string AssembleDocument(VisualBriefingExportManifest exportManifest, string htmlLanguage, string briefingName, string dataJson, string template, string css, string runtime, string? echarts, string csp)
+ {
+ var encodedHeader = EncodeHeader(exportManifest);
+ return $"""
+
+
+
+
+
+
+
+
+ {HtmlEncode(briefingName)}
+
+
+
+
+
+ {template}
+
+ {BuildScriptTag(echarts, "mwai-echarts-runtime")}
+
+
+
+ """;
+ }
+
+ ///
+ /// Encodes the stable JSON artifact header for embedding in an HTML comment.
+ ///
+ private static string EncodeHeader(VisualBriefingExportManifest exportManifest) => Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(exportManifest, JSON_OPTIONS)));
+
///
/// Defines RuntimeAIVersionRegex for the visual briefing feature.
///
@@ -109,7 +114,7 @@ public sealed partial class VisualBriefingArtifactService
/// Defines the protected, app-owned static footer template.
///
private const string STATIC_FOOTER_TEMPLATE = """
-
+ Created with MindWork AI Studio v.
@@ -149,6 +154,15 @@ public sealed partial class VisualBriefingArtifactService
visibility: visible !important;
opacity: 1 !important;
}
+ #mwai-static-footer a {
+ display: inline !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ color: inherit !important;
+ font: inherit !important;
+ text-decoration: underline !important;
+ text-underline-offset: .15em !important;
+ }
@media print {
html, body {
background: #fffefa !important;
@@ -170,12 +184,6 @@ public sealed partial class VisualBriefingArtifactService
return $"default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src {ScriptCspHash(parts.RuntimeScript)}{echartsHash}; font-src 'none'; media-src 'none'; frame-src 'none'; connect-src 'none'; form-action 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'self'";
}
- ///
- /// Defines ComputePayloadHash for the visual briefing feature.
- ///
- private static string ComputePayloadHash(string dataJson, string template, string css, string runtime, string? echarts) =>
- VisualBriefingHashing.ComputeSections(dataJson, template, css, runtime, echarts);
-
///
/// Defines ScriptCspHash for the visual briefing feature.
///
@@ -238,12 +246,7 @@ public sealed partial class VisualBriefingArtifactService
///
/// Defines CreateExportManifest for the visual briefing feature.
///
- private static VisualBriefingExportManifest CreateExportManifest(
- VisualBriefingManifest manifest,
- VisualBriefingRevisionRequest request,
- string payloadHash,
- string aiStudioVersion,
- string runtimeAIStudioVersion)
+ private static VisualBriefingExportManifest CreateExportManifest(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request, string documentHash, string aiStudioVersion, string runtimeAIStudioVersion)
{
var source = request.ExportMetadataSource;
return new()
@@ -265,7 +268,7 @@ public sealed partial class VisualBriefingArtifactService
CustomProtectionLevel = source?.CustomProtectionLevel ?? manifest.Settings.CustomProtectionLevel,
AIStudioVersion = aiStudioVersion,
RuntimeAIStudioVersion = runtimeAIStudioVersion,
- PayloadHash = payloadHash,
+ DocumentHash = documentHash,
};
}
diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Parsing.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Parsing.cs
index 001f7a54..348faaad 100644
--- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Parsing.cs
+++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Parsing.cs
@@ -9,195 +9,147 @@ namespace AIStudio.Assistants.VisualBriefing;
public sealed partial class VisualBriefingArtifactService
{
///
- /// Defines ManifestRegex for the visual briefing feature.
+ /// Matches the version-independent artifact header at the start of standalone HTML.
///
- private static readonly Regex MANIFEST_REGEX = ManifestRegex();
+ private static readonly Regex HEADER_REGEX = HeaderRegex();
///
- /// Defines ManifestRegex for the visual briefing feature.
+ /// Matches the version-independent artifact header at the start of standalone HTML.
///
- [GeneratedRegex("", RegexOptions.CultureInvariant)]
- private static partial Regex ManifestRegex();
+ [GeneratedRegex(@"\A\n\n", RegexOptions.CultureInvariant)]
+ private static partial Regex HeaderRegex();
///
- /// Defines StyleRegex for the visual briefing feature.
+ /// Matches the generated presentation stylesheet.
///
private static readonly Regex STYLE_REGEX = StyleRegex();
///
- /// Defines StyleRegex for the visual briefing feature.
+ /// Matches the generated presentation stylesheet.
///
[GeneratedRegex("""""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex StyleRegex();
///
- /// Defines RuntimeRegex for the visual briefing feature.
+ /// Matches the embedded declarative runtime.
///
private static readonly Regex RUNTIME_REGEX = RuntimeRegex();
///
- /// Defines RuntimeRegex for the visual briefing feature.
+ /// Matches the embedded declarative runtime.
///
[GeneratedRegex("""""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex RuntimeRegex();
///
- /// Defines EChartsRegex for the visual briefing feature.
+ /// Matches the optional embedded chart runtime.
///
private static readonly Regex ECHARTS_REGEX = EChartsRegex();
///
- /// Defines EChartsRegex for the visual briefing feature.
+ /// Matches the optional embedded chart runtime.
///
[GeneratedRegex("""""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex EChartsRegex();
///
- /// Parses a standalone artifact using the current runtime contract.
+ /// Reads an intact standalone artifact without applying current compiler or runtime rules.
///
- /// The complete standalone HTML document.
- /// The validated artifact parts.
- /// The user-safe validation issue.
- /// Whether the artifact is valid for the current runtime.
- public static bool TryParse(string html, out VisualBriefingArtifactParts parts, out string issue) => TryParse(html, allowOutdatedRuntime: false, out parts, out issue);
-
- ///
- /// Parses a locally stored parent artifact for recompilation while allowing a previous runtime
- /// bundle that will be discarded before the new revision is assembled.
- ///
- /// The complete standalone HTML document.
- /// The validated artifact parts.
- /// The user-safe validation issue.
- /// Whether the artifact is structurally valid for recompilation.
- internal static bool TryParseForRecompile(string html, out VisualBriefingArtifactParts parts, out string issue) => TryParse(html, allowOutdatedRuntime: true, out parts, out issue);
-
- ///
- /// Parses and validates a standalone artifact under the selected runtime policy.
- ///
- /// The complete standalone HTML document.
- /// Whether a previous runtime bundle may be read but never reused.
- /// The validated artifact parts.
- /// The user-safe validation issue.
- /// Whether the artifact passed all applicable checks.
- private static bool TryParse(string html, bool allowOutdatedRuntime, out VisualBriefingArtifactParts parts, out string issue)
+ public static bool TryParse(string html, out VisualBriefingArtifactParts parts, out string issue)
{
parts = null!;
issue = string.Empty;
-
+
if (string.IsNullOrWhiteSpace(html))
{
issue = "The briefing file is empty.";
return false;
}
- if (!html.StartsWith("\n", StringComparison.Ordinal) ||
- !html.EndsWith("