From 16e136107c3b4f9f73b1fee75a40295b97625ad0 Mon Sep 17 00:00:00 2001 From: lacvet Date: Wed, 15 Apr 2026 22:47:39 +0900 Subject: [PATCH] =?UTF-8?q?HTML=20=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EB=A0=88?= =?UTF-8?q?=EA=B1=B0=EC=8B=9C=20=EC=96=91=EC=8B=9D=20=ED=98=B8=ED=99=98?= =?UTF-8?q?=EC=9D=84=20=EB=B3=B4=EA=B0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TemplateService 공통 CSS에 h4/dl/matrix/comparison/board_report/metrics/roadmap 레거시 블록 호환 스타일을 추가해 raw body 기반 HTML 보고서의 뒤쪽 섹션도 동일한 폰트 크기와 카드 양식을 유지하도록 수정 - roadmap 내부 timeline/owner 배지가 전역 timeline 스타일과 충돌하던 문제를 override로 분리하고 다크 모드·모바일 레이아웃까지 함께 정리 - HtmlSkillLegacyBodyCompatibilityTests를 추가하고 dotnet build 및 HtmlSkillLegacyBodyCompatibilityTests|HtmlSkillConsultingSectionsTests 통과로 검증 --- README.md | 8 ++ docs/DEVELOPMENT.md | 8 ++ .../HtmlSkillLegacyBodyCompatibilityTests.cs | 60 ++++++++++++++ .../Services/Agent/TemplateService.cs | 78 +++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 src/AxCopilot.Tests/Services/HtmlSkillLegacyBodyCompatibilityTests.cs diff --git a/README.md b/README.md index 60ee9e8..334e33d 100644 --- a/README.md +++ b/README.md @@ -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` 안의 ``가 기존 전역 `.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 트레이 크기에서도 더 알맞은 프레임을 읽게 됩니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 6e35584..cf4e93e 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -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` 내부 `` 배지와 충돌하던 문제를 `.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 diff --git a/src/AxCopilot.Tests/Services/HtmlSkillLegacyBodyCompatibilityTests.cs b/src/AxCopilot.Tests/Services/HtmlSkillLegacyBodyCompatibilityTests.cs new file mode 100644 index 0000000..80efd69 --- /dev/null +++ b/src/AxCopilot.Tests/Services/HtmlSkillLegacyBodyCompatibilityTests.cs @@ -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": "

Overview

Matrix

Quick Wins

  • Automate reporting

Strategic Bets

  • Rebuild workflow

Board Ask

Approve phase 2

Margin+4.2ptpilot mix

Phase 1

Prepare rollout

Q2PMO
HBM
High bandwidth memory
" + } + """).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 + { + } + } + } +} diff --git a/src/AxCopilot/Services/Agent/TemplateService.cs b/src/AxCopilot/Services/Agent/TemplateService.cs index d39d910..999c4b4 100644 --- a/src/AxCopilot/Services/Agent/TemplateService.cs +++ b/src/AxCopilot/Services/Agent/TemplateService.cs @@ -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 }