본문으로 건너뛰기

빌드 리포트 — 찾은 문제를 고칠 사람에게 보이게 하기

문서 목록으로

상태: — 4단계 전부. 구현에서 드러난 것은 §14

실행이 찾은 문제를 로그가 아니라 보고서로 냅니다 — 사람이 읽는 HTML 한 장과 CI가 읽는 JSON 한 장. 목적은 하나입니다: 문제가 누적되기 전에, 그 문제를 고칠 수 있는 사람이 바로 고치게 만드는 것. 구글 시트의 셀은 클릭 한 번으로 그 셀이 열리고, warning 이상이 있으면 보고서가 브라우저로 자동으로 열립니다.

이 설계는 이 도구의 전신에서 검증된 것입니다 — 로그 파일에 적기만 한 문제는 데이터를 가진 쪽이 읽지 않았고(어려워서든 귀찮아서든), 보고서로 내고 셀로 바로 이동하게 하자 수정이 빨라졌습니다.


1. 로그가 닿지 않는 이유

문제를 고칠 사람과 로그를 읽는 사람이 다른 사람입니다. 메시지 ID 스펙 §3의 분류가 그대로 적용됩니다 — 보고의 대부분은 시트의 데이터 문제이고, 그것을 고칠 수 있는 사람은 데이터를 가진 쪽입니다. 그런데 지금 보고가 도착하는 자리는 셋 다 그쪽이 읽는 형태가 아닙니다.

지금 보고가 가는 곳누가 읽습니까
콘솔실행을 돌린 사람. 스크롤이 지나가면 끝입니다
로그 파일 (logs/)아무도. CI가 보관할 뿐입니다
예외 메시지실행을 돌린 사람 — 그리고 첫 화면에 들어오는 만큼만

수집된 보고 자체는 이미 좋은 상태입니다. 다만 실행이 끝나면 버려집니다.

이미 있는 것자리리포트가 쓰는 방식
위치를 든 보고 수집 (Severity · Location · MessageId)Diagnostics.cs항목 그대로가 리포트의 행입니다
구글 시트 셀 딥링크 (#gid=…&range=A1)Location.SheetUrl — 임포트 때 셀마다 만들어집니다클릭하면 그 셀이 열립니다
위치순 정렬Diagnostics.SortByLocation같은 시트의 문제가 모여 보입니다
보고의 정체성메시지 ID — 이 브랜치JSON을 ID로 필터할 수 있고, 실행 간 대조의 키입니다
알려진 문제의 구분known-problemsInfo로 강등새 문제와 섞이지 않게 따로 싣습니다

버려지는 자리도 특정되어 있습니다 — 검증은 단계마다 Diagnostics를 만들어 Finish에서 인쇄하고 버리고, 멈추는 문제는 ThrowIfAny가 예외 하나에 담아 던지며, ProgramLogException이 그것을 콘솔에 인쇄하는 것으로 끝납니다. 리포트는 새 검출을 하나도 추가하지 않습니다. 버려지던 것을 남길 뿐입니다.

규모도 실측이 있습니다 — 한 샘플의 전량 변환이 한때 검증 보고 5,831건을 냈습니다 (로드맵). 콘솔에서 그 규모는 읽는 것이 불가능하고, 이 문서의 상한과 그룹핑 결정(§4)은 그 숫자에서 옵니다.

2. 결정 요약

항목결정근거
산출물report.htmlreport.json 두 벌고칠 사람은 HTML을, CI는 JSON을 읽습니다. 한 형식으로 둘을 겸하면 둘 다 나빠집니다
타깃이 아니라 실행 설비ITarget이 아닙니다타깃은 임포트·쿠킹·검증을 통과해야 도달하는데, 이 산출물의 본 목적이 통과하지 못한 실행입니다. 실패한 실행이 스테이징을 롤백해도 리포트는 남아야 합니다
실패해도 씁니다Program의 catch에서도 리포트를 쓰고 나서 종료합니다성공한 실행의 리포트는 확인이고, 실패한 실행의 리포트가 본론입니다
자리.tabbit/ 아래, 빌드 씰과 같은 이름 규칙레시피마다 하나, 같은 실행의 다른 산출물과 같은 폴더입니다
마지막 리포트 열람경로가 고정이고, --show-report가 실행 없이 엽니다「아까 그 보고서 어디 있지」가 명령 하나로 답해집니다
자동 열기warning 이상 또는 실행 실패면 기본 브라우저로. CI·리다이렉트된 출력·--silent에서는 열지 않습니다문제가 생긴 순간이 고치게 만들 수 있는 순간입니다. 열람을 사람의 기억에 맡기면 로그와 같아집니다
신규 · 지속 · 해소직전 report.json과 대조해 항목마다 표시합니다목적이 누적 방지이므로, 쌓이고 있는지가 보고서의 첫 문장이어야 합니다
셀 링크구글 시트는 SheetUrl 그대로, xlsx는 위치 텍스트와 복사 버튼xlsx는 특정 셀을 여는 이식 가능한 URL이 없습니다. 못 하는 것을 하는 척하지 않습니다
페이지 문구메시지 카탈로그에서 — report. 접두어메시지 ID의 언어가 오면 리포트도 함께 옵니다. 보고 본문은 이미 카탈로그를 지나갑니다
외부 요청0개폐쇄망 전제는 HTML 문서와 같습니다. 스타일 · 스크립트 전부 인라인
테마 · 스크롤 · 아이콘HTML 문서의 규약 승계같은 도구의 산출물이 다른 규칙을 가질 이유가 없습니다
Info싣되 접습니다. JSON에는 전량「무엇을 검사했는가」는 필요할 때만 펼쳐 봅니다
HTML 상한기본 5,000항목, 0은 무제한. 절단은 페이지에 명시5,831건의 실측에서. JSON은 언제나 전량입니다
기본 켜짐Report.Enabled 기본 true켜야 보이는 것이면 로그와 같습니다. 끄는 것은 recipe 한 줄입니다

3. 수집 — 실행 하나에 수집기 하나

리포트 수집기는 recipe가 읽힌 직후 만들어져 실행 끝까지 남습니다. 그 전에 실패한 실행 — recipe 파일이 없거나 JSON이 깨진 것 — 은 리포트를 쓸 자리조차 정해지지 않으므로 지금처럼 콘솔로만 보고합니다.

수집하는 것은 넷이고, 전부 이미 지나가는 자리에서 받습니다.

무엇어디서 받습니까
단계별 Diagnostics의 전 항목 (Error · Warning · Info)각 단계가 인쇄하는 그 자리 — 인쇄를 대체하지 않고 병행합니다
멈춘 문제의 목록ThrowIfAny가 던진 예외의 DetailsLogException이 받는 자리에서
실행을 끝낸 예외 자체Program의 catch. TabbitDefectException도구의 결함으로 구분해 싣습니다 — 콘솔의 「데이터 문제가 아닙니다」 안내와 같은 구분이 리포트에도 있어야, 데이터를 가진 쪽이 자기 시트에서 없는 원인을 찾지 않습니다
실행 메타recipe 경로 · 도구 버전 · 시작 시각 · 소요 시간 · 단계별 시간(RunTimings) · 결과 (성공 / 검증 중단 / 실패 / 할 것 없음)

항목 하나의 내용은 Diagnostics가 이미 담는 그대로입니다 — 심각도, 위치, 현재 언어로 확정된 문구, 메시지 ID. 리포트를 위해 보고 형태를 바꾸지 않습니다.

리포트 쓰기의 실패는 실행의 실패가 아닙니다 — 씰이 못 써졌을 때와 같은 취급으로, 경고 한 줄을 남기고 실행의 결과는 그대로 둡니다. 반대 방향도 같습니다: 리포트는 exit code에 아무 영향이 없습니다.

4. HTML — 고칠 사람이 읽는 쪽

한 파일, 인라인 전부, 외부 요청 0.

머리 결과 한 줄 + 카운트 (에러 · 경고 · 신규 · 지속 · 해소)
실행 메타 한 줄 — recipe · 버전 · 시각 · 소요 시간
탭 문제 | 알려진 문제 | 해소됨 | 기록 ← 목록은 한 번에 하나
도구 막대 검색 · 심각도 토글 · 묶기(시트별/종류별) · 펼치기/접기 · 테마
목록 워크북 → 시트로 묶고, 행 하나짜리 자리는 그냥 행으로
행: [심각도] 위치 · 문구 한 줄 ← 클릭하면 펼쳐지고 ID가 나옵니다

목록은 한 번에 하나입니다. 「문제」·「알려진 문제」·「해소됨」·「기록」은 서로 다른 질문이고, 한 페이지에 쌓으면 찾아온 하나에 닿기까지 나머지 셋을 스크롤해야 합니다. 탭입니다. 비어 있는 탭도 흐리게 남깁니다 — 없애면 실행마다 나머지 탭의 자리가 움직이고, 매번 다른 자리에 있는 컨트롤은 아무도 위치를 외우지 않습니다.

행은 한 줄이고, 펼치면 그 이상입니다. 보고 하나는 문단입니다 — 무엇이 잘못됐는지, 그래서 무엇이 따라오는지, 무엇을 하면 되는지. 그런 문단 14개는 벽이고, 행마다 다른 부분은 문장 앞쪽에 있습니다. 그래서 한 줄로 자르고 클릭하면 펼칩니다. 페이지를 먼저 훑고 그다음에 읽습니다.

행 하나짜리 그룹은 만들지 않습니다. 실측에서 시트 12개가 각각 보고 1건씩이었고, 그것을 전부 그룹으로 두면 항목마다 머리글을 하나씩 무는 평면 목록이 됩니다. 그런 자리는 그냥 행이고, 대신 자기 위치 전체를 답니다.

행에는 머리글에 없는 것이 적힙니다. 시트로 묶였으면 셀만(D1), 종류로 묶였거나 아무 머리글 아래도 아니면 위치 전체를 답니다 — 그 축에서 행을 구별하는 것이 그쪽이기 때문입니다.

묶는 축이 둘입니다. 시트별은 일하는 순서입니다 — 워크북 하나를 열고 그 셀들을 고칩니다. 종류별은 이해하는 순서이고, 한 종류가 페이지의 대부분일 때 그쪽이 맞습니다. 기본은 시트별, 전환은 클릭 하나입니다. 다시 그리지 않고 행을 옮깁니다 — 행이 이미 두 축의 키와 두 가지 위치 표기를 함께 들고 있기 때문입니다.

  • 메시지 ID는 닫힌 행에 없습니다. 그것은 CI가 필터하고 우리가 카탈로그를 찾는 이름이지, 시트를 가진 사람이 할 수 있는 일이 아닙니다. 페이지 오른쪽에 같은 문자열이 행 수만큼 늘어서는 것이 실제로 처음 나온 모습이었습니다. 펼친 행에 있고, 종류별로 묶으면 그룹 머리글이며, 검색은 숨겨져 있어도 걸립니다. 펼친 행에서도 문장 옆 칸이 아니라 아래 한 줄입니다 — 옆에 두었더니 좁은 칸에서 세 줄로 접히면서 읽을 것의 폭을 뺏었습니다.
  • 보고가 백틱으로 인용한 것은 코드로 놓입니다. 보고는 전부 무언가를 이름으로 부릅니다 — 컬럼 · 값 · 설정 — 그리고 그 이름이 바로 윗 행과 다른 부분입니다. 평문으로 두면 다르지 않은 부분과 똑같이 읽힙니다. 짝이 맞는 것만 코드가 되고 남는 백틱 하나는 글자입니다. 마크다운의 나머지는 읽지 않습니다 — 보고는 이 도구가 쓴 문장이지 누가 작성한 문서가 아니고, *를 강조로 읽으면 패턴을 인용한 보고에서 별표가 사라집니다.
  • 셀 링크. 구글 시트에서 온 위치는 Location.SheetUrl이 이미 셀 단위 딥링크이므로 그대로 <a>가 됩니다 — 새 탭으로. xlsx에서 온 위치는 파일 : 시트 : C7 텍스트에 복사 버튼을 붙입니다. 로컬 파일의 특정 셀을 여는 URL은 플랫폼 공통으로 존재하지 않고, 존재하는 척 하는 링크는 눌러서 아무 일도 안 일어나는 링크입니다. 복사 버튼은 호버와 펼친 행에만 보입니다 — 위치마다 버튼 하나는 같은 컨트롤을 행 수만큼 반복하는 것입니다.
  • 위치는 열입니다. 폭을 rem으로 잡아 문장들이 같은 자리에서 시작합니다 — 눈으로 아래로 훑을 수 있게. 열보다 넓은 위치는 밀고 나갑니다: 시트 이름은 여기서 잘리면 안 되는 유일한 것입니다. ch가 아닌 것은 ch가 「0」의 폭이고 한국어 시트 이름은 그것과 전혀 다르기 때문입니다.
  • 필터. 심각도 토글과 검색 하나(문구 · 시트 이름 · 메시지 ID에 걸립니다). 5,000항목 규모에서 필터 없는 목록은 목록이 아닙니다. 필터가 비운 그룹은 머리글째 숨고, 그룹의 숫자는 남은 개수로 바뀝니다 — 총계가 그대로인 필터는 믿을 수 없는 필터입니다.
  • 위치 없는 보고 — recipe 문제, 실행 수준의 보고 — 는 「실행」 그룹 하나에 모이고 언제나 맨 뒤입니다. 보낼 수 있는 자리가 없기 때문입니다.
  • 절단은 명시합니다. 상한에 걸리면 「N건 중 5,000건을 실었습니다. 전량은 report.json에 있습니다」가 페이지에 적힙니다 — 「전부 봤다」로 읽히지 않게.
  • 검색어는 찾은 자리에 표시됩니다. 표시는 마크업을 치환하는 것이 아니라 텍스트 노드를 훑습니다 — 보고는 인용한 이름을 code로 들고 있어서, innerHTML에 대고 치환하면 태그 안쪽까지 걸려 페이지가 소스로 렌더됩니다. 검색창의 지움 버튼도 직접 그립니다: 브라우저가 주는 것은 Chromium에만 있어서, 어떤 기계에서는 지워지고 어떤 기계에서는 안 됩니다.
  • 테마 3단 · 스크롤 영역 하나는 HTML 문서의 결정을 그대로 씁니다.

겉모습 — GitHub Primer

색 토큰 · 14px 시스템 서체 · 테두리 하나에 행 사이 실선인 박스 · 알약 라벨과 카운터 · 밑줄 내비게이션 · 버튼까지 Primer를 그대로 따릅니다.

이 페이지가 GitHub에 있어서가 아닙니다. 이걸 읽는 사람은 하루의 대부분을 그렇게 생긴 화면 앞에서 보냅니다. 도구마다 제 나름의 회색 상자 방언을 쓰면, 내용을 읽기 전에 그 방언부터 익혀야 합니다. 익힐 것이 없는 화면이 목적입니다.

예외는 하나입니다 — 보고가 인용한 이름의 색. GitHub의 인라인 코드는 무채색인데 그것으로는 한 페이지에 늘어선 이름들 사이에서 눈으로 찾아지지 않았습니다. 팔레트 안의 done 보라를 씁니다.

5. JSON — CI가 읽는 쪽

스키마에 버전을 답니다. 항목의 키는 메시지 ID이므로, 문구를 다듬어도 CI의 필터는 깨지지 않습니다메시지 ID가 이 산출물의 전제입니다.

{
"Version": 1,
"Tool": "1.2.3",
"Recipe": "recipes/all.jsonc",
"StartedAt": "2026-08-22T10:31:05+09:00",
"Elapsed": 103.4,
"Outcome": "stopped-by-validation", // success · stopped-by-validation · failed · nothing-to-do
"Counts": { "Errors": 3, "Warnings": 7, "Infos": 120, "New": 2, "Persisting": 8, "Resolved": 1 },
"Entries": [
{
"Severity": "error",
"Id": "validate.reference-missing",
"Message": "…현재 언어로 확정된 문구…",
"Fate": "new", // new · persisting — resolved는 아래 목록으로
"Location": {
"File": "excel/Item.xlsx", "Sheet": "Item",
"Row": 6, "Column": 2, "Cell": "C7",
"Url": "" // 구글 시트일 때만
}
}
],
"Resolved": [ /* 직전에 있었고 이번에 없는 항목 — Id와 Location */ ],
"KnownProblems": [ /* 맞춰진 항목 — recipe의 이유와 함께 */ ]
}

6. 신규 · 지속 · 해소 — 누적을 보이게 하기

이 보고서의 목적이 문제를 누적시키지 않는 것이므로, 쌓이고 있는지가 수치로 먼저 보여야 합니다. 리포트를 쓰기 전에 직전 report.json을 읽어 대조합니다.

판정기준
대조 키메시지 ID + 위치 (파일 · 시트 · 행 · 열). ID가 없는 항목(아직 이행 전인 호출 지점)은 문구로 대신합니다
신규직전에 없던 키
지속직전에도 있던 키
해소직전에 있었고 이번에 없는 키 — 별도 목록으로 싣습니다. 고친 것이 보이는 것도 빨리 고치게 만드는 장치입니다

한계를 그대로 적습니다: 시트에 행이 끼어들면 같은 문제의 위치가 밀리므로 지속이 신규+해소 한 쌍으로 나타납니다. 대조는 참고이지 판정이 아니고, 그래서 exit code나 검증 결과에는 아무 관여가 없습니다. 직전 파일이 없으면(첫 실행) 전부 신규가 아니라 대조 없음으로 표시합니다 — 첫 실행의 「전부 신규」는 정보가 아닙니다.

7. 자동 열기와 다시 열람

자동 열기

"Report": { "OpenInBrowser": "problems" } // never · problems · always

기본은 problemswarning 이상이 하나라도 있거나 실행이 실패했으면 OS 기본 브라우저로 엽니다. 알려진 문제로 강등된 Info는 세지 않습니다 — 이미 아는 것으로 매번 열리면, 여는 것 자체가 무시하는 습관이 됩니다.

열지 않는 조건은 설정보다 우선합니다. 셋 다 「보는 사람이 그 자리에 없다」의 신호입니다.

조건판정
CICI 환경 변수가 설정됨
출력이 터미널이 아님리다이렉트 · 파이프 — 스크립트가 돌린 실행입니다
--silent조용히 하라고 이미 적혀 있습니다

브라우저를 여는 데 실패해도 경고 한 줄로 끝냅니다 — 열람 편의가 실행 결과를 좌우하면 안 됩니다.

다시 열람

리포트의 경로는 실행마다 같은 자리에 덮어써지므로, 마지막 실행의 리포트는 언제나 그 자리에 있습니다.

  • 실행이 끝날 때 콘솔에 경로 한 줄을 인쇄합니다 — 문제가 있었던 실행에서는 카운트와 함께.
  • --show-report: 실행 없이 그 recipe의 마지막 리포트를 브라우저로 열고 종료합니다. --history · --stats와 같은 「하고 끝나는」 플래그의 관례입니다.

8. recipe 표기

"Report": {
"Enabled": true, // 기본 true — 끄는 것이 명시적입니다
"Path": "", // 비면 .tabbit/ 아래, 씰과 같은 이름 규칙
"OpenInBrowser": "problems", // never · problems · always
"MaxHtmlEntries": 5000 // 0은 무제한. JSON은 언제나 전량
}

기본 자리는 빌드 씰과 같은 규칙입니다 — .tabbit/<recipe 이름>-<해시 12자리>.report.html · .report.json. 같은 이름의 recipe가 다른 폴더에 있어도 겹치지 않고, .tabbit/이 이미 버전 관리 밖이므로 리포트도 함께 밖입니다. Path를 지정하면 그 폴더에 씁니다 — CI가 아티팩트로 걷어 가는 자리를 지정하는 용도입니다.

모르는 값(OpenInBrowser: "smoetimes")은 recipe 문제로 거부합니다. 모르는 키는 거부하지 않습니다 — 이 섹션만의 결정이 아니라, Targets 항목 밖에서는 어느 섹션도 그렇게 하지 않기 때문입니다. 그 자리를 이 기능이 혼자 바꾸면 recipe의 규칙이 섹션마다 달라집니다.

9. 코어 규칙과의 관계

리포트는 코어입니다 — 어느 프로젝트도 모릅니다. 레이아웃 파서가 낸 보고는 자기 접두어를 단 메시지 ID로 도착하고(메시지 ID §4), 리포트는 그것을 다른 항목과 똑같이 실을 뿐입니다. 레이아웃 파일 하나를 지워도 리포트 쪽에는 지울 것이 없습니다.

10. 이 산출물이 아닌 것

이것과 다릅니다무엇이 다릅니까
HTML 문서 타깃그쪽은 들어간 데이터를 열람하는 산출물이고, 이쪽은 이번 실행의 문제입니다. 그쪽은 성공해야 나오고, 이쪽은 실패가 본론입니다
History그쪽은 커밋 사이의 데이터 변화 기록이고, 이쪽은 실행 하나의 진단입니다. 직전 실행과의 대조(§6)가 겹쳐 보이지만, 대조 상대가 데이터가 아니라 보고입니다
로그대체하지 않습니다. 콘솔과 파일 로그는 지금 그대로이고, 리포트는 같은 보고의 세 번째 종착지입니다

11. 게이트

  • 골든 무관입니다. 생성기 · 템플릿 · 와이어에 닿지 않으므로, 골든이 한 바이트도 움직이지 않는 것을 ConversionGolden으로 확인합니다.
  • 실패한 실행이 리포트를 남기는가 — 검증이 멈춘 실행에서 report.json이 쓰였고 exit code는 그대로 실패인 것. 이 산출물의 존재 이유이므로 첫 게이트입니다.
  • JSON 스키마 왕복 — 쓴 것을 되읽어 항목 수 · ID · 위치가 맞는 것. Version 필드가 있는 것.
  • 신규 · 지속 · 해소 판정 — 직전 파일이 있는 경우 · 없는 경우 · 위치가 밀린 경우.
  • 자동 열기의 억제 — CI 변수 · 리다이렉트 · --silent에서 열리지 않는 것. 테스트에서 브라우저 실행 자체는 언제나 주입으로 차단합니다 — 게이트가 확인하는 것은 「열기로 판정했는가」이지 실제로 열렸는가가 아닙니다.
  • HTML은 구조 단정으로 — 타임스탬프와 소요 시간이 들어가므로 바이트 비교가 성립하지 않습니다. 카운트 · 그룹 · 딥링크 href · 절단 문구를 파싱해 확인합니다.
  • 셀 딥링크의 열 표기 — bijective base-26은 Location.ColumnName에 이미 게이트가 있고, 리포트는 그것을 그대로 씁니다. 새 변환을 만들지 않습니다.
  • 필터가 실제로 숨기는가 — §13의 첫 항목입니다. 화면에서만 드러나는 종류라, 그 한 줄이 스타일에 있는지를 단정으로 못박습니다.

전부 BuildReportTests에 있고, 워크북을 열지 않으므로 초 단위로 끝납니다.

12. 구현 순서

단계무엇끝의 판정
1수집기와 report.json — 실패 경로 포함검증이 멈춘 실행이 JSON을 남기고, 카운트가 콘솔의 카운트와 일치합니다
2report.html과 실행 끝의 경로 인쇄5,000항목 실측 데이터에서 열리고 필터가 동작합니다
3자동 열기와 --show-report억제 조건 3종이 게이트로 확인됩니다
4신규 · 지속 · 해소 대조같은 recipe를 두 번 돌린 픽스처에서 세 판정이 다 나옵니다

1단계만으로도 CI가 쓸 수 있고, 2단계까지가 이 문서의 본 목적입니다. 3 · 4는 각각 독립이라 순서를 바꿔도 됩니다.

13. 구현에서 드러난 것

설계가 예측하지 못한 자리 3개가 실제 페이지를 화면에 띄우고서야 나왔습니다. 셋 다 마크업도 스크립트도 옳은 채로 화면만 틀린 종류라, 단정으로는 찾을 수 없고 그려 보아야 보였습니다.

무엇왜 안 보였는가
필터가 아무것도 숨기지 않았습니다. 행이 display: flex인데 hidden 속성이 붙이는 display: none은 작성자 규칙에 집니다. 일치 1건이라고 적힌 그룹이 3행을 보여주고 있었습니다카운트는 맞게 계산되고 있었습니다 — 틀린 것은 그 결과를 화면에 반영하는 마지막 한 단계뿐입니다. [hidden] { display: none !important; } 한 줄이고, 그 줄이 있는지를 게이트가 확인합니다
그룹 키에 셀이 들어가 있었습니다. 셀마다 그룹이 하나씩 생겨서, 그룹핑이 「제목 붙은 평면 목록」이 되어 있었습니다그룹은 정상으로 그려집니다. 개수를 세어 보아야 이상한 것이고, 그것을 세는 단정을 쓰기 전에는 스크린샷에서도 그럴듯해 보입니다
성공한 실행의 제목이 빨간색이었습니다. 경고가 있으면 실패와 같은 색이었습니다색을 「문제가 있는가」로 갈랐기 때문입니다. 경고와 함께 끝난 실행은 실패가 아니고, 그것을 빨강으로 칠하면 실제로 멈춘 실행의 빨강이 아무 말도 하지 않게 됩니다. 성공 · 경고 · 실패 3색으로 나눴습니다

그리고 접기의 기준을 한 번 뒤집었습니다. 처음에는 「오류를 가진 그룹만 펼친다」였는데, 경고만 있는 실행에서 페이지 전체가 접힌 채로 열려 아무것도 못 찾은 것처럼 보였습니다. 지금은 그 페이지에서 가장 나쁜 심각도를 가진 그룹이 펼쳐집니다 — 오류가 있으면 오류 그룹만, 없으면 경고 그룹이 펼쳐집니다.

그리고 실제 데이터를 띄우고서 한 번 더 갈렸습니다. 워크북 36개의 실행이 낸 보고 14건을 페이지에 올렸더니, 기능은 전부 동작하는데 읽히지가 않았습니다.

무엇이 문제였는가무엇으로 바꿨는가
행 하나가 3~4줄 문단이고, 그중 뒷문장은 14행이 전부 똑같았습니다. 다른 것은 문장 앞쪽 한 조각뿐입니다한 줄로 자르고 클릭하면 펼칩니다
시트 12개가 각각 보고 1건이라, 그룹 머리글 12개가 각각 행 하나를 덮고 있었습니다행 하나짜리 자리는 그냥 행입니다
메시지 ID가 오른쪽에 14번 늘어서 있었습니다닫힌 행에서 뺐습니다. 펼치면 나오고, 종류별로 묶으면 머리글이 됩니다
목록 넷이 한 페이지에 쌓여 있었습니다탭입니다
위치 열의 폭이 행마다 달라 문장 시작점이 들쭉날쭉했습니다rem으로 잡은 열입니다. ch는 「0」의 폭이라 한국어 시트 이름에 맞지 않습니다
「모두 펼치기」가 아무 일도 안 하는 것처럼 보였습니다. 그룹 접힘만 열었는데, 행 하나짜리 자리가 대부분인 페이지에는 그룹이 거의 없습니다이 페이지에서 접히는 것 둘 다 엽니다 — 그룹과 행
복사 버튼이 규칙 없이 나타났습니다. 호버에도 보이고 펼친 행에도 보여서, 읽는 쪽이 볼 수 없는 이유로 나타나는 컨트롤이었습니다트리거 하나 — 호버뿐입니다
보고가 인용한 이름이 옅은 회색이라 문장에서 구분되지 않았습니다자기 색을 줬습니다. 행에 이미 있는 링크 파랑과 다른 색이어야 해서 보라 계열입니다
메타 줄의 ·에 실제 공백이 없어 복사하면 붙어 나왔습니다여백이 아니라 글자로 씁니다. 그 줄은 「어느 실행이었는지」를 붙여 넣으려고 있는 것입니다

그리고 기능이 다 맞는데도 「내용에 집중이 안 된다」는 지적이 한 번 더 왔습니다. 원인은 하나로 모입니다 — 행마다 같은 것이 앞자리를 차지하고 다른 것이 뒤에 있었습니다.

무엇이 앞자리에 있었는가바꾼 것
위치가 맨 왼쪽·고정폭·긴 문자열로 첫 자리였습니다. 위치는 맥락이지 내용이 아닙니다문장이 먼저, 위치는 행 끝으로. 행마다 끝나는 자리가 같으므로 폭을 정하지 않아도 오른쪽이 맞습니다
samples/canopy/가 14행 전부에 반복되었습니다공통 폴더는 머리글에 한 번만 적고 행에서 뗍니다. 복사되는 텍스트는 전체 경로 그대로입니다 — 그건 이 페이지가 없는 사람에게 갑니다
still here 배지가 14개 전부 같았습니다신규 답니다. 「지속」은 문제의 평상 상태라 매 행에 적으면 아무 말도 하지 않습니다. 그 수는 머리글에 있습니다
행마다 테두리와 둥근 모서리 — 카드 14장이 그 안의 글과 경쟁했습니다목록 하나에 테두리 하나, 행 사이는 실선 하나
문장·위치·배지가 전부 같은 크기와 농도였습니다문장만 본문 강도로, 위치는 작고 흐리게

전부 「기능은 맞고 읽히지 않는」 종류이고, 단정으로는 하나도 잡히지 않습니다. 실제 데이터로 그려서 보는 것 말고 이것을 찾는 방법이 없었습니다.

그룹 순서도 함께 고쳤습니다. 심각도만으로 정렬하면 한 워크북의 시트들이 페이지 곳곳으로 흩어집니다 — 묶은 이유가 바로 그것을 막는 것이었는데 정렬이 도로 풀고 있었습니다. 지금은 오류를 가진 그룹이 먼저 오고, 그 안에서는 파일 순서를 지킵니다. 위치가 없는 보고는 언제나 맨 뒤입니다 — 보낼 수 있는 자리가 없기 때문입니다.

14. 확인 항목

  • xlsx 위치에 file:// 링크를 함께 둘지 — 파일은 열 수 있지만 셀로는 못 갑니다. 워크북 경로가 기계마다 다른 문제도 있어, 1차는 복사 버튼만으로 갑니다.
  • --serve(History HTTP 서버)가 마지막 리포트도 함께 서빙할지 — 팀원이 서로의 실행 결과를 보는 통로가 되지만, History와 리포트의 수명이 달라 별도 결정이 필요합니다.
  • 리포트 이전 세대를 몇 개 남길지 — 지금 설계는 마지막 하나입니다. 실행 간 추이는 §6의 대조로 알 수 있고, 더 긴 추이는 History의 영역으로 봅니다.