HTML 보고서 레거시 양식 호환을 보강한다
- TemplateService 공통 CSS에 h4/dl/matrix/comparison/board_report/metrics/roadmap 레거시 블록 호환 스타일을 추가해 raw body 기반 HTML 보고서의 뒤쪽 섹션도 동일한 폰트 크기와 카드 양식을 유지하도록 수정 - roadmap 내부 timeline/owner 배지가 전역 timeline 스타일과 충돌하던 문제를 override로 분리하고 다크 모드·모바일 레이아웃까지 함께 정리 - HtmlSkillLegacyBodyCompatibilityTests를 추가하고 dotnet build 및 HtmlSkillLegacyBodyCompatibilityTests|HtmlSkillConsultingSectionsTests 통과로 검증
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# AX Commander
|
||||
|
||||
- 업데이트: 2026-04-15 22:45 (KST)
|
||||
- HTML 보고서 뒤쪽으로 갈수록 폰트 크기와 카드 레이아웃이 흔들리던 raw body 호환 문제를 보강했습니다. `src/AxCopilot/Services/Agent/TemplateService.cs`에 `h4`, `dl`, `matrix`, `comparison`, `decision_matrix`, `board_report`, `metrics`, `roadmap` 같은 레거시 블록 전용 CSS를 추가해, 구조화 섹션이 아닌 자유 본문 HTML로 생성된 보고서도 앞부분과 같은 문서 톤을 유지하도록 맞췄습니다.
|
||||
- 특히 `roadmap` 안의 `<span class="timeline">`가 기존 전역 `.timeline` 블록 스타일과 충돌해 뒤쪽 일정/담당 배지가 세로 타임라인처럼 깨지던 문제를 별도 override로 분리했습니다. 다크 모드와 모바일 레이아웃도 함께 맞춰 이후 생성되는 HTML 보고서가 같은 문제를 반복하지 않도록 정리했습니다.
|
||||
- 회귀 테스트 `src/AxCopilot.Tests/Services/HtmlSkillLegacyBodyCompatibilityTests.cs`를 추가했고, 즉시 확인이 필요했던 실제 산출물 `E:\\docu\\삼성전자_사업분석_보고서_20260415_2140.html`에도 동일 호환 스타일을 직접 주입해 현재 문서부터 읽기 흐름이 무너지지 않게 보정했습니다.
|
||||
- 검증:
|
||||
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_html_legacy_body_style\\ -p:IntermediateOutputPath=obj\\verify_html_legacy_body_style\\` 경고 0 / 오류 0
|
||||
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "HtmlSkillLegacyBodyCompatibilityTests|HtmlSkillConsultingSectionsTests" -p:OutputPath=bin\\verify_html_legacy_body_style_tests_relevant\\ -p:IntermediateOutputPath=obj\\verify_html_legacy_body_style_tests_relevant\\` 통과 2
|
||||
|
||||
- 업데이트: 2026-04-15 21:19 (KST)
|
||||
- AX Copilot 앱 아이콘을 더 크게 보이도록 다시 생성했습니다. `src/AxCopilot/Assets/icon.ico`의 실제 도형 점유율이 작아 작업 표시줄과 트레이에서 다른 앱보다 작게 보였는데, 같은 4다이아몬드 계열 형태를 유지한 채 내부 여백을 줄이고 캔버스를 더 넓게 쓰도록 멀티사이즈 아이콘을 다시 만들었습니다.
|
||||
- 트레이 DPI 대응도 함께 보강했습니다. `tools/IconGenerator/Program.cs`가 현재 앱 아이콘 스타일을 기본으로 생성하고 `16/20/24/32/40/48/64/128/256` 프레임을 포함하도록 바뀌어, `src/AxCopilot/App.xaml.cs`의 `LoadAppIcon()`이 고DPI 트레이 크기에서도 더 알맞은 프레임을 읽게 됩니다.
|
||||
|
||||
@@ -1634,3 +1634,11 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫
|
||||
- 검증:
|
||||
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_background_conversation_live_ui\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui\\` 경고 0 / 오류 0
|
||||
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|AppStateServiceTests" -p:OutputPath=bin\\verify_background_conversation_live_ui_tests\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui_tests\\` 통과 49
|
||||
업데이트: 2026-04-15 22:45 (KST)
|
||||
- HTML 보고서 raw body 호환 스타일을 보강했습니다. `src/AxCopilot/Services/Agent/TemplateService.cs`에 `h4`, `dl`, `matrix`, `comparison`, `decision_matrix`, `board_report`, `metrics`, `risks`, `next-steps`, `roadmap` 블록용 CSS를 추가해 자유 본문 HTML로 생성된 뒤쪽 섹션도 앞부분과 같은 폰트 크기/카드 양식을 유지하도록 정리했습니다.
|
||||
- 기존 전역 `.timeline` 블록 스타일이 `roadmap` 내부 `<span class="timeline">` 배지와 충돌하던 문제를 `.roadmap .phase .timeline` override로 분리했고, `.owner` 배지도 같은 방식으로 정리했습니다. 모바일 1열 전환과 다크 모드 색상도 함께 보강했습니다.
|
||||
- 회귀 테스트 `src/AxCopilot.Tests/Services/HtmlSkillLegacyBodyCompatibilityTests.cs`를 추가해 legacy body HTML이 호환 CSS를 자동 주입받는지 확인합니다.
|
||||
- 즉시 확인이 필요했던 산출물 `E:\\docu\\삼성전자_사업분석_보고서_20260415_2140.html`에도 동일 스타일을 직접 주입해 현재 문서부터 뒤쪽 섹션이 무너지지 않도록 보정했습니다.
|
||||
- 검증:
|
||||
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_html_legacy_body_style\\ -p:IntermediateOutputPath=obj\\verify_html_legacy_body_style\\` 경고 0 / 오류 0
|
||||
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "HtmlSkillLegacyBodyCompatibilityTests|HtmlSkillConsultingSectionsTests" -p:OutputPath=bin\\verify_html_legacy_body_style_tests_relevant\\ -p:IntermediateOutputPath=obj\\verify_html_legacy_body_style_tests_relevant\\` 통과 2
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class HtmlSkillLegacyBodyCompatibilityTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithLegacyBodyBlocks_ShouldInjectCompatibilityStyles()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-html-legacy-body-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var tool = new HtmlSkill();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "legacy-report.html",
|
||||
"title": "Legacy Report",
|
||||
"body": "<h2>Overview</h2><div class=\"matrix\"><h3>Matrix</h3><div class=\"quadrant\"><h4>Quick Wins</h4><ul><li>Automate reporting</li></ul></div><div class=\"quadrant\"><h4>Strategic Bets</h4><ul><li>Rebuild workflow</li></ul></div></div><div class=\"board_report\"><h3>Board Ask</h3><div class=\"decision-summary\"><p>Approve phase 2</p></div><div class=\"metrics\"><div class=\"metric-item\"><span class=\"metric-label\">Margin</span><span class=\"metric-value\">+4.2pt</span><span class=\"metric-note\">pilot mix</span></div></div><div class=\"roadmap\"><div class=\"phase\"><h4>Phase 1</h4><p>Prepare rollout</p><span class=\"timeline\">Q2</span><span class=\"owner\">PMO</span></div></div></div><dl><dt>HBM</dt><dd>High bandwidth memory</dd></dl>"
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
var html = File.ReadAllText(Path.Combine(workDir, "legacy-report.html"));
|
||||
html.Should().Contain(".matrix .quadrant");
|
||||
html.Should().Contain(".board_report");
|
||||
html.Should().Contain(".metric-item");
|
||||
html.Should().Contain(".roadmap .phase .timeline");
|
||||
html.Should().Contain("h4 { font-size: 13.5px;");
|
||||
html.Should().Contain("dt { font-size: 13px;");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1038,10 +1038,65 @@ public static class TemplateService
|
||||
padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
|
||||
.card-header { font-size: 15px; font-weight: 700; margin-bottom: 8px; }
|
||||
|
||||
/* ── 레거시 보고서 블록 호환 스타일 ── */
|
||||
h4 { font-size: 13.5px; font-weight: 700; margin: 16px 0 8px; color: #1f4b7a; }
|
||||
h5 { font-size: 12.5px; font-weight: 700; margin: 12px 0 6px; color: #334155; }
|
||||
h6 { font-size: 12px; font-weight: 700; margin: 10px 0 6px; color: #475569; }
|
||||
dl { margin: 14px 0; }
|
||||
dt { font-size: 13px; font-weight: 700; color: #003366; margin-top: 10px; }
|
||||
dd { margin: 4px 0 10px; padding-left: 12px; font-size: 13.5px; color: #4b5563; }
|
||||
.matrix, .comparison, .decision_matrix, .board_report, .roadmap { margin: 18px 0 24px; }
|
||||
.matrix { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
||||
.matrix > h3 { grid-column: 1 / -1; }
|
||||
.matrix .quadrant { border: 1px solid #e5e7eb; border-radius: 12px; padding: 14px 16px;
|
||||
background: linear-gradient(180deg, #fff, #f8fafc); min-height: 168px; }
|
||||
.matrix .quadrant h4 { margin-top: 0; }
|
||||
.comparison { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
|
||||
.comparison .comparison-item { border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px 18px;
|
||||
background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||
.decision_matrix { margin-top: 16px; }
|
||||
.board_report { border: 1px solid #dbe4f0; border-radius: 16px; padding: 18px 20px;
|
||||
background: linear-gradient(180deg, #f8fbff, #fff);
|
||||
box-shadow: 0 10px 26px rgba(15,23,42,.05); }
|
||||
.board_report > h3 { margin-top: 0; }
|
||||
.board_report .decision-summary { padding: .9rem 1rem; border-radius: 12px; background: #eef2ff;
|
||||
color: #312e81; margin: .8rem 0 1rem; }
|
||||
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: .85rem;
|
||||
margin: 1rem 0 1.25rem; }
|
||||
.metric-item { border: 1px solid #dbe4f0; border-radius: 14px; padding: .9rem 1rem; background: #fff;
|
||||
box-shadow: 0 8px 24px rgba(15,23,42,.05); display: flex; flex-direction: column; gap: .25rem; }
|
||||
.metric-label { font-size: .78rem; font-weight: 700; color: #64748b; text-transform: uppercase;
|
||||
letter-spacing: .05em; }
|
||||
.metric-value { font-size: 1.35rem; font-weight: 800; color: #0f172a; }
|
||||
.metric-note { font-size: .82rem; color: #475569; line-height: 1.55; }
|
||||
.risks, .next-steps { margin-top: 1rem; padding: .95rem 1rem; border-radius: 14px; background: #fff;
|
||||
border: 1px solid #e5e7eb; }
|
||||
.risks h4, .next-steps h4 { margin-top: 0; }
|
||||
.roadmap { display: grid; gap: .9rem; }
|
||||
.roadmap .phase { display: grid; grid-template-columns: 160px 1fr; gap: 1rem; align-items: start;
|
||||
border: 1px solid #e5e7eb; border-radius: 14px; padding: 1rem 1.1rem;
|
||||
background: linear-gradient(180deg, #fff, #f8fafc); }
|
||||
.roadmap .phase h4 { margin: 0 0 .35rem; }
|
||||
.roadmap .phase p { margin: 0; }
|
||||
.roadmap .phase .timeline,
|
||||
.roadmap .phase .owner { position: static; padding-left: 0; margin: 0; display: inline-flex;
|
||||
align-items: center; width: auto; line-height: 1.2; }
|
||||
.roadmap .phase .timeline::before,
|
||||
.roadmap .phase .owner::before { content: none; }
|
||||
.roadmap .phase .timeline { margin: 0 0 .35rem; padding: .28rem .62rem; border-radius: 999px;
|
||||
background: #eef2ff; color: #4338ca; font-size: .82rem; font-weight: 700; }
|
||||
.roadmap .phase .owner { padding: .28rem .62rem; border-radius: 999px; background: #f3f4f6;
|
||||
color: #475569; font-size: .82rem; font-weight: 600; }
|
||||
|
||||
/* ── 구분선 ── */
|
||||
.divider { border: none; border-top: 1px solid #e5e7eb; margin: 32px 0; }
|
||||
.divider-thick { border: none; border-top: 3px solid #e5e7eb; margin: 40px 0; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.matrix { grid-template-columns: 1fr; }
|
||||
.roadmap .phase { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* ── 인쇄/PDF 최적화 ── */
|
||||
@media print {
|
||||
.ax-theme-toggle, .ax-fab-toc { display: none !important; }
|
||||
@@ -1056,6 +1111,29 @@ public static class TemplateService
|
||||
a[href]::after { content: ' (' attr(href) ')'; font-size: 10px; color: #999; }
|
||||
.no-print { display: none !important; }
|
||||
}
|
||||
|
||||
[data-theme="dark"] h4,
|
||||
[data-theme="dark"] h5,
|
||||
[data-theme="dark"] h6 { color: #e2e8f0; }
|
||||
[data-theme="dark"] dt { color: #cbd5e1; }
|
||||
[data-theme="dark"] dd { color: #94a3b8; }
|
||||
[data-theme="dark"] .matrix .quadrant,
|
||||
[data-theme="dark"] .comparison .comparison-item,
|
||||
[data-theme="dark"] .board_report,
|
||||
[data-theme="dark"] .metric-item,
|
||||
[data-theme="dark"] .risks,
|
||||
[data-theme="dark"] .next-steps,
|
||||
[data-theme="dark"] .roadmap .phase {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
[data-theme="dark"] .board_report .decision-summary { background: rgba(99,102,241,0.14); color: #c7d2fe; }
|
||||
[data-theme="dark"] .metric-label { color: #94a3b8; }
|
||||
[data-theme="dark"] .metric-value { color: #f8fafc; }
|
||||
[data-theme="dark"] .metric-note { color: #cbd5e1; }
|
||||
[data-theme="dark"] .roadmap .phase .timeline { background: rgba(99,102,241,0.18); color: #c7d2fe; }
|
||||
[data-theme="dark"] .roadmap .phase .owner { background: rgba(148,163,184,0.18); color: #cbd5e1; }
|
||||
""";
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user