workflow-visualizer:비즈니스 프로세스 → 인터랙티브 플로우차트
렌더링 스크립트:scripts/flow.mjs(이 스킬 디렉토리 기준 상대 경로)
절대 경로:scripts\flow.mjs
전체 필드 및 문법 매뉴얼:references/spec.md(사양을 작성하기 전에 먼저 읽으세요)
一、핵심 제약 사항(이렇게 해야 하는 이유)
- 제로 디펜던시:
node:내장 모듈만 사용합니다.npm install이 필요 없고, mermaid를 설치할 필요도 없습니다. - 순수 오프라인:레이아웃과 렌더링은 이 스크립트가 직접 구현한 SVG로, 어떤 CDN도 참조하지 않습니다. 네트워크가 끊기거나, 내부 네트워크, 외부 네트워크가 없어도 차트를 생성할 수 있습니다(로컬
web_fetch/docs.deepseek.com도 연결되지 않으며, CDN에 의존하는 모든 방식은 실패합니다). - 자체 포함된 산출물:내보낸
.svg에는<style>이 내장되어 있어, 별도로 보내도 올바르게 표시됩니다..html은 단일 파일로, 더블 클릭하면 바로 열립니다.
二、세 단계 사용법
1단계:프로세스 추출(이 단계는 생략할 수 없습니다) 사용자의 실제 자료에서 추출하며, 임의로 만들어내지 마세요. 출처 우선 순위:
- 작업 공간의 기획안/회고 문서(
*.md,보통 "워크플로우/SOP/일정" 섹션이 있음) - 사용자 구술(이 경우 확실하지 않은 부분은
status: hold로 표시하고note에 확인 필요 사항을 명시) - 대화 기록 또는 로그
추출 시 다음 다섯 가지를 고정적으로 추출하세요:단계(누가 무엇을 하는지), 시간대, 선후 의존성, 판단 분기, 위험/수동 검토 필요 지점.
2단계:사양 작성
- 그림이 복잡하지 않은 경우(순수 선형 + 한두 개 분기)→ Mermaid로 직접 작성하여
.mmd로 저장하는 것이 가장 간편합니다. - 시간/담당자/상태/설명/그룹이 필요한 경우 → JSON을 작성합니다(정보 밀도가 훨씬 높아 패널에 표시할 내용이 있음).
- 먼저
sample을 실행하여 완성된 사양을 템플릿으로 얻는 것이 가장 빠릅니다:
node "scripts\flow.mjs" sample --out ".\out\내_프로세스.json"
3단계:검증 + 렌더링
$sk = "scripts\flow.mjs"
node $sk check ".\out\내_프로세스.json" # 구조 검사:고립 노드/도달 불가/종료점 없음/사이클
node $sk render ".\out\내_프로세스.json" --all --out ".\out\내_프로세스.html"
산출물은 기본적으로 입력 파일과 동일한 디렉토리에 생성됩니다.
한 번의 render로 두 가지 레이아웃 생성(동일한 HTML 내에서, 화면에 따라 자동 선택, 하단에서 수동 전환 가능):
| 레이아웃 | 배치 | 적응 기기 |
|---|---|---|
세로형 portrait | 단일 열 세로(wrap: 0) | 휴대폰:세로 스크롤, 너비 꽉 채움 |
가로형 landscape | 긴 프로세스 자동 다중 열 분할(wrap: -1,목표 가로세로비 1.6) | 컴퓨터:한 화면에 표시 |
자주 사용하는 매개변수:--out <file.html>、--all(추가로 <이름>-세로형.svg、<이름>-가로형.svg、.mmd 출력)、--wrap N(가로형 각 열 고정 N 행)、--debug(두 레이아웃의 열 수/행 수와 각 열 구성원 출력).
두 레이아웃 검증(권장):
node $sk preview ".\out\내_프로세스.html" # 「【컴퓨터용】비교 미리보기.html」 생성:휴대폰/컴퓨터 두 iframe 나란히 표시
디버그 브라우저에서 열어보면 두 레이아웃이 모두 올바른지 한눈에 확인할 수 있습니다. 실제 기기 크기까지 정확하게 확인하려면 CDP 장치 시뮬레이션을 사용하세요:
# 휴대폰 + 컴퓨터 두 장의 실제 측정 스크린샷, 그리고 canvas/aside/footer의 실제 높이와 자동 선택된 레이아웃 출력
node $sk\..\tools\devshot.mjs "http://127.0.0.1:8765/내_프로세스.html" 휴대폰.png 컴퓨터.png
# 페이지에서 JS를 실행하여 상호 작용 검증하기(예:노드 클릭, 검색 입력, 서랍 열기):
node $sk\..\tools\probe.mjs <url> probe.js --w 390 --h 844 --mobile --touch
이 두 도구는 모두 자체 임시 탭을 새로 만들고, 사용이 끝나면 닫습니다. 사용자가 현재 탐색 중인 탭에는 영향을 주지 않습니다(초기 버전에서는 첫 번째 페이지 대상을 직접 가져와 사용자가 보고 있던 페이지를 테스트 대상으로 삼았으며, 그 교훈은 아래에 기록되어 있습니다). 하지만 주의할 점:새 탭은 사용자의 브라우저 창에 나타나며 포커스를 빼앗습니다——사용자가 현재 해당 디버그 브라우저를 사용 중이라면 먼저 물어보고 사용하세요. 그렇지 않으면 서로 방해할 수 있습니다.
三、차트 생성 후 반드시 자체 점검("명령이 성공했다"고만 보지 마세요)
명령 성공 ≠ 차트가 보기 좋다는 의미. 최소 한 번 시각적 확인을 수행하세요. browser-control을 사용하세요:
# 1) ASCII 파일명 복사본을 생성하여 file://의 한글 인코딩 문제를 피함
Copy-Item ".\out\내_프로세스.html" ".\out\preview.html" -Force
# 2) 시스템 URI 생성기를 사용하여 올바른 file:// 주소를 얻음(수동으로 퍼센트 인코딩하면 오류가 발생하기 쉬움)
$u = [System.Uri]::new((Resolve-Path ".\out\preview.html").Path).AbsoluteUri
node browser-control\webctl.mjs shot $u "out\shot.png" 5000
그런 다음 이미지를 확인하세요:텍스트가 잘리지 않았는지, 분기가 서로 엉키지 않았는지, 그룹 밴드가 노드를 가리지 않는지.
클릭 상호 작용도 스크립트로 검증할 수 있습니다(webctl.mjs evalfile,단 (function(){...})() 표현식이어야 하며, 맨 return은 Illegal return statement 오류를 발생시킵니다).
四、사람에게 보여주기:컴퓨터 / 휴대폰 두 가지 열기 방식
컴퓨터:해당 .html을 더블 클릭하세요(단일 파일, 오프라인 사용 가능). 문서에 첨부하려면 .svg를 끌어다 놓으세요.
휴대폰(HTML이 컴퓨터에 있고, 휴대폰 브라우저에는 "로컬 파일 열기" 기능이 없음)——파일을 전송하지 않고 LAN을 통해 접속하세요:
node "scripts\serve.mjs" ".\out" 8765
스크립트는 사용 가능한 휴대폰 주소를 직접 출력합니다(VPN/가상 네트워크 카드/169.254는 자동으로 「무시」 표시됨). 형식은 다음과 같습니다:
휴대폰 → http://<내_LAN_IP>:8765/ ✔ 이 주소 사용(WLAN)
휴대폰을 동일한 Wi-Fi에 연결하고, 브라우저에서 이 주소를 연 후 파일명을 클릭하면 됩니다. 핵심 사항:
- 산출물 디렉토리만 노출시키고, 작업 공간 루트를 가리키지 마세요.
- Windows에서 처음 실행 시 방화벽 팝업이 나타나면 「전용 네트워크」를 선택하여 인바운드를 허용하세요. 팝업이 없는데도 열리지 않는다면 방화벽에서 차단된 것입니다.
- 디렉토리 페이지는 파일 목록이며, 휴대폰에서 임의의
.html/.svg/.png에 접근할 수 있습니다. - 확인이 끝나면 이 서비스를 중지하도록 요청할 수 있습니다(백그라운드 작업임). 또는 해당 터미널 창을 직접 닫으면 됩니다.
동일한 네트워크가 아니거나 / 다른 사람에게 보내야 하는 경우:해당 HTML 파일 자체를 직접 보내세요——자체 포함되어 있고 오프라인에서 열 수 있으며, WeChat/QQ로 파일 전송해도 됩니다. 그림만 필요한 경우 페이지의 「PNG 내보내기」를 클릭하여 이미지를 보내거나, 브라우저에서 Ctrl+P를 눌러 PDF로 저장하세요.
휴대폰에서의 조작:한 손가락 드래그로 이동, 두 손가락으로 확대/축소, 노드를 살짝 눌러 세부 정보 확인, 하단 「맞추기」로 전체 화면 보기. 세로형/가로형 버튼은 페이지 하단 왼쪽에 있습니다(휴대폰에서는 아이콘만 표시됨).
| 영역 | 기능 |
|---|---|
| 상단 바 | 브랜드 로고 + 제목, 검색창, 「전체 플로우차트」 버튼(오른쪽 상단), 상태 범례 캡슐(클릭하여 필터링) |
| 오른쪽 패널 | 선택되지 않은 경우 프로세스 개요;검색 시 먼저 일치 목록 표시(아래 참조);노드 선택 후에는 세부 정보 카드 + 「개요로 돌아가기」 |
| 하단 바 | 세로형/가로형 전환, 개요/세부 정보(휴대폰), 확대/축소, 맞추기, SVG/PNG 내보내기, 밝은/어두운 테마, Mermaid 소스 코드 |
검색의 세 가지 일치 수준(중요, 버그로 오해하지 마세요)
검색은 제목 / id / 시간 / 담당자 / 설명 / 비고를 동시에 검색하지만, 캔버스에서는 제목만 볼 수 있으므로 일치는 두 가지 유형으로 나뉩니다:
- 보이는 일치(제목/id/시간/담당자)→ 노드에 파란색 테두리, 개수는 하나로 계산
- 설명/비고에만 있는 일치 → 노드에 주황색 점선 테두리, 개수는
2개 일치(1개는 설명/비고에만 있음)로 표시, 오른쪽에 일치 목록도 함께 표시됨(클릭하면 해당 위치로 이동)
일치 색상은 전용 변수
--hit(다크 테마#4a9dff, 미색 테마#2b7fe0)를 사용해야 하며,--accent를 사용할 수 없습니다 —— 미색 테마의 강조 색상은 테라코타#bf6b3a이며, 이를 사용하면 일치 항목이 "빨간색 테두리"가 되어 "파란색 테두리" 설명과 맞지 않습니다(실제로 겪은 문제임).
예시:샘플 프로세스에서 컵을 검색하면 2개가 일치합니다 —— 두 번째 음료 출구 열기(제목)와 오후 보충(설명의 "컵기"). f를 검색하면 id가 f인 노드와 설명/비고에 문자 f가 포함된 모든 노드가 일치합니다. 동일한 노드는 한 번만 계산되며, 이는 중복 계산이 아닙니다.
밝은/어두운 테마:전체 설정 / 단일 차트 / 잠금(나중에 변경한 것이 우선 적용됨)
세 가지 키 모두 브라우저의 localStorage에 저장됩니다(모두 JSON {mode, ts} 저장, 타임스탬프로 비교, 나중에 변경한 쪽이 적용됨):
| 키 | 작성자 | 의미 |
|---|---|---|
flowviz-theme | 홈페이지 「표시 설정」 | 전체 설정:시스템 따름 / 항상 어둡게 / 항상 밝게 |
flowviz-theme:<파일명> | 플로우차트 페이지의 밝음/어두움 키 | 단일 차트의 개별 설정(값은 dark/light) |
flowviz-lock:<파일명> | 플로우차트 페이지의 「색상 잠금」 | 잠금 시 해당 차트 색상 고정({locked:true, mode}) |
적용 규칙(페이지 <head>의 부트스트랩 스크립트와 페이지 JS가 완전히 일치하여 색상 깜빡임 방지):
- 잠금 상태 → 잠금 순간에 고정된 색상 사용, 전체 설정과 단일 차트 설정 모두 영향 미치지 않음
- 그렇지 않으면 전체 설정과 단일 차트 설정의 타임스탬프 비교, 새로운 쪽이 적용됨
- 둘 다 없음 → 시스템 따름(
prefers-color-scheme), 시스템 전환 시 실시간으로 따라감
따라서:단일 차트를 먼저 변경한 후 전체 설정을 변경하면 → 모두 전체 설정을 따라 변경됨. 전체 설정을 먼저 변경한 후 단일 차트를 변경하면 → 해당 차트만 변경됨. 잠금 후에는 먼저 잠금을 해제해야 색상을 변경할 수 있음(잠금 상태에서 밝음/어두움 키를 클릭하면 차단되고 프롬프트가 표시됨).
실제 측정 매트릭스(8단계 모두 통과, 0 이상):초기화 → 전체=밝음 → 차트=밝음. 단일 차트 밝음/어두움 클릭 → 차트=어두움(단일 차트 키에 기록). 그 다음 전체=밝음으로 변경 → 차트=밝음(전체 설정 업데이트로 단일 차트 덮어씀). 단일 차트 다시 클릭 →이 차트만 어두워짐. 잠금 → 전체 설정 변경 후에도 잠긴 어두운 색상 유지. 잠금 상태에서 밝음/어두움 키 클릭 → 차단 및 "먼저 잠금 해제" 프롬프트 표시. 잠금 해제 → 전체 설정의 밝은 색상으로 돌아감. 홈페이지 카드에 「🔒 잠금됨 밝은 색상」 배지 표시.
기타 사항:
- 동일한 키에 반복적으로
setItem을 호출하면 덮어쓰기가 됨. 누적되지 않음(실제로 밝음/어두움 키를 12번 클릭해도Object.keys(localStorage)는 여전히 1개 항목, 총 18바이트). "이전 항목 삭제"가 필요하지 않음 - 설정은 기기 브라우저에 저장되므로, 서비스를 다시 시작해도 영향을 받지 않음. IP/포트 변경(origin 변경), 브라우저 데이터 삭제, 브라우저 변경 시에만 초기화됨
- 밝은 테마는 밀레니엄 베이지 스타일로, 순수 흰색이 아님:배경
#f2ebdd, 패널#fdfaf3, 본문#33302a, 강조 색상 테라코타#bf6b3a, 차트 내 바탕#e6dcc8(어두운 테마는 기존 유지)
상단 버튼
🏠 홈 파일 목록 페이지로 돌아가기(href="./")|전체 플로우차트 오른쪽 서랍 열기|검색창|상태 범례|밝음/어두움 이 차트 전환|색상 잠금 이 차트 색상 고정.
서랍에서 현재 페이지는 비링크로 렌더링됨(클릭해도 반응 없음, "고장난 줄 알았다"는 혼동 방지). 목록은 파일명 순서로 고정 정렬되므로, fetch로 목록을 새로고침해도 순서가 변경되지 않음.
오른쪽 상단 버튼을 클릭하면 오른쪽 서랍이 열리며, 동일한 디렉토리에서 이 스킬로 생성된 모든 플로우차트를 나열합니다(파일명 순서로 고정 정렬, 현재 페이지는 「현재」로 표시되고 클릭할 수 없는 비링크로 렌더링됨). 한 항목을 클릭하여 전환할 수 있습니다. 식별 방식은 페이지의 data-flow-viz="1" 마커를 통해 이루어지므로, 다른 HTML은 포함되지 않습니다.
- 목록 파일은 디렉토리의
프로세스_index.json이며,render가 실행될 때마다 자동으로 재구축됩니다(또한node flow.mjs index <디렉토리>로 별도 재구축 가능) - HTTP로 열 때 서랍은 실시간으로
fetch하여 목록을 새로고침합니다. 로컬 파일을 더블 클릭하여 열 때는 페이지에 포함된 스냅샷을 사용합니다(각 페이지에 하나씩 포함되어 있으므로 로컬에서도 전환 가능)
검색 상호 작용("일치가 눈에 띄지 않음"에 대한 세 가지 패치)
- 결과 패널 자동 확장:일치 항목을 찾았고 현재 선택된 노드가 없으면
setPanel(true)실행 —— 휴대폰에서 결과 목록은 기본적으로 접힌 하단 서랍에 숨겨져 있으며, 펼치지 않으면 힌트가 제공되지 않음(이것이 "검색해도 힌트가 없다"는 근본 원인) - 이전 / 다음:결과 영역 제목 표시줄에
↑ / ↓와현재/전체개수 표시. 검색창에서 Enter = 다음, Shift+Enter = 이전.Esc는 지우기 - 자동 포커스(해제 가능):결과 영역에 「일치 항목 자동 포커스」 스위치가 있습니다. 켜면 ——
- 일치 항목의 경계 상자를 수용할 수 있고, 필요한 확대/축소 비율이 현재 확대/축소 비율 × 0.6 이상인 경우 → 모든 일치 항목을 프레임에 포함(여러 일치 항목을 한 화면에서 볼 수 있음)
- 그렇지 않은 경우(일치 항목이 너무 멀리 떨어져 있음)→ 축소하지 않고, 현재 확대/축소 비율을 유지한 채 "현재 일치 항목"으로 중앙 정렬, ↑/↓로 하나씩 이동 이렇게 하면 "두 개의 멀리 떨어진 일치 항목을 모두 포함"하기 위해 텍스트를 너무 작게 만들지 않음
- 검색 중에 svg에
.searching추가:일치하지 않는 항목은 14%로 페이드 아웃(연결선은 10%, 그룹 밴드는 35%), 일치 항목 테두리는 3px로 굵게 표시 - 프로그래밍 방식으로 이동(포커스, 점프)시
#vp에.vp-anim추가(0.28s 전환), 수동 드래그는 영향을 받지 않음
중복 이름 플로우차트:먼저 "내용 동일"과 "단지 이름만 같음"을 구분
동일한 제목 아래에 여러 파일이 나타나는 경우(일반적인 출처:파일을 삭제했다가 복구하여 시스템이 xxx - 복사본.html을 생성한 경우), 홈페이지 상단에 프롬프트 상자가 표시되며, 내용에 따라 두 가지로 처리됩니다:
| 상황 | 판단 기준 | 제공 액션 |
|---|---|---|
| 내용 동일(동일한 프로세스를 두 번 렌더링한 경우) | 프로세스 의미론적 지문 동일 | "최신 버전만 유지하고, 나머지는 휴지통 폴더로 이동" |
| 내용 다름(동일한 이름이지만 다른 버전) | 지문 다름 | 삭제하지 않음, "이름 변경…"만 제공(기본 제안 원래 이름(이전 버전 날짜).html) |
- 지문 = 프로세스 의미론으로,
제목/부제목/방향/그룹/노드/엣지만 추출하고selfFile、flows、slug등 페이지 수준 필드는 무시합니다. 따라서 "동일한 프로세스를 다른 시간에 렌더링한 두 파일"의 지문은 동일하며, "페이지 메타데이터만 변경"한 경우 잘못 판단되지 않습니다. - 삭제는 항상 "휴지통 폴더로 이동":동일한 디렉토리에
.trash-YYYY-MM-DD/를 만들고, HTML을 해당 SVG / MMD와 함께(정규화된 이름으로 연결을 찾아)rename으로 이동하며, 언제든지 다시 가져올 수 있습니다. - 처리 액션은 백엔드 엔드포인트
POST /__flow/act를 통해 이루어집니다({op:'trash', remove:[...]}/{op:'rename', file, to}):- 127.0.0.1에서만 호출 가능(휴대폰, LAN에서는 403 반환)——그렇지 않으면 "파일 삭제" 기능이 Wi-Fi에 노출됨
- 파일명은 화이트리스트를 통과해야 함:경로 구분 기호 미포함, 길이 ≤200, 반드시
.html로 끝나야 함. 중복 이름 충돌 시 409 반환 - 휴대폰 / 로컬 file:// 모드에서는 버튼이 표시되지 않고 프롬프트만 표시됨(이 두 시나리오에는 쓰기 가능한 백엔드가 없음)
- 카드에는 파일 이름이 계속 표시됩니다(제목이 중복된 경우), 수동 식별을 용이하게 하기 위해
두 가지 열기 방식(모두 홈페이지에 접근 가능해야 함)
| 방식 | 홈페이지 접근 방법 | 홈페이지 출처 |
|---|---|---|
로컬 더블 클릭(file://) | 플로우차트 오른쪽 상단 🏠 → ./index.html | render가 생성한 정적 index.html(브라우저의 기본 디렉토리 목록은 파일 이름만 표시하므로, 이 파일이 필요함) |
| LAN / HTTP | 🏠 → ./ | serve.mjs가 동적으로 생성 |
render가 실행될 때마다index.html을 재구축하며, 이 파일은 HTTP 홈페이지와 동일한 렌더링 코드를 공유합니다(serve.mjs가 내보낸buildHome().serve.mjs는 직접 실행될 때만 서비스를 시작하며, import될 때는 함수만 제공함)- 정적 버전과 HTTP 버전의 차이점:이동 경로는
📁 디렉토리명(로컬 폴더)로 표시, 배지는 「플로우차트 페이지」로 표시, 페이지 하단에 "로컬 파일 모드:휴대폰에서 접속하려면 먼저 서비스를 시작해야 함" 문구 추가 - 🏠 버튼은 프로토콜을 인식합니다:
location.protocol==='file:'일 때 href를./index.html로 변경 index.html자체는 카드 목록에 표시되지 않습니다(초기 버전에서는 첫 번째 카드가 "index"로 표시됨). 목록에 없으면서flow-stats를 파싱할 수 없는 HTML은 「기타 파일」로 분류됩니다.
홈페이지(컴퓨터/휴대폰 동일 페이지)
- 이동 경로:
🏠 플로우차트 / 서브디렉토리(현재), 각 레벨 클릭 가능 - 디스크 경로 블록:
\out을 직접 표시, 「경로 복사」 버튼 포함——원본 파일을 보내거나 소스 파일을 찾아야 할 때 다시 추측할 필요 없음 - 검색 + 정렬 + 요약:제목/파일명으로 필터링. 정렬은 「최근 열기 우선 / 이름순 / 수정 시간순」으로 전환 가능. 요약은
3개 · 총 36개 단계 / 47개 연결 · 주의 필요 4곳(2개 차트)로 표시 - 최근에 열기:카드를 클릭하면
flowviz-recent에 기록(최대 8개), 기본적으로 맨 위에 고정되고 「최근 열기」 배지 표시 - 카드에는 또한 「🔒 잠금됨 X」「별도로 설정됨 X」 배지가 표시됨(로컬 localStorage를 읽음)
- 문구는 기기에 따라 다름:동일한 HTML에 두 세트의 프롬프트를 작성하고,
@media (hover:none),(pointer:coarse)로 전환(너비로 판단하는 것보다 더 정확하며, 가로 모드 휴대폰에서도 정확함)——- 컴퓨터(
.only-pc):🖱️ 카드를 클릭하여 엽니다. 차트에서 스크롤 확대/축소, 길게 누르기 드래그 이동 가능. 오른쪽 상단 「표시 설정」에서 어두움/밝음 테마 전환 - 휴대폰(
.only-touch):📱 카드를 클릭하여 엽니다. 차트에서 한 손가락 드래그, 두 손가락 확대/축소, 단계를 눌러 세부 정보 확인… - 카드 배지 「휴대폰에서 여기 클릭」에는
.touchonly추가, 컴퓨터에서는 표시되지 않음. 비교 페이지 배지는 중립적인 「나란히 비교 페이지 · 컴퓨터용」(양쪽에서 모두 이해 가능) - 플로우차트 페이지의 페이지 하단/사이드바 프롬프트도 마찬가지. 컴퓨터에서는
스크롤 확대/축소 · 드래그 이동 · Esc, 휴대폰에서는한 손가락 드래그 · 두 손가락 확대/축소
- 컴퓨터(
- 중복 이름 플로우차트(예:
xxx - 복사본.html과xxx.html)는 제목만으로 구분하기 어려움 → 이러한 카드는 제목 아래에 파일명을 추가로 표시함 - 썸네일은 정규화된 이름으로 매칭:공백과
-가로형/-세로형을 제거하고 비교하므로,예제_프로세스 - 복사본.html이예제_프로세스-가로형 - 복사본.svg와 매칭될 수 있음(접미사 위치가 달라도 매칭됨) - 디렉토리 페이지 응답에는
cache-control: no-store가 포함되어야 함:동적으로 생성되므로, 브라우저가 한 번 캐시하면 새 버전을 볼 수 없음(이전에 사용자가 계속 이전 버전의 기본 목록을 보며 "홈페이지가 너무 단순하고 썸네일이 없다"고 오해한 경험 있음)
휴대폰에서의 조작
한 손가락 드래그 이동, 두 손가락 확대/축소, 노드를 살짝 눌러 세부 정보 확인, 하단 「맞추기」로 전체 화면 보기.
휴대폰에서 개요/세부 정보는 오버레이 방식의 하단 서랍(position:fixed):노드를 클릭하면 슬라이드 아웃, 빈 공간을 클릭하면 접힘, 하단 바 「개요」 버튼으로 수동 열기. 이는 flex 형제 노드가 아닌 오버레이 레이어이므로——서랍을 열고 닫아도 캔버스 높이는 변하지 않음(실제 측정 607→607), "캔버스가 짧아짐 → 다시 맞추기 → 차트 잘림" 연쇄 반응이 발생하지 않음.
"확대/축소 후에 잘못 표시됨"과 관련된 두 가지 구현 세부 사항:
- 점자 바탕 무늬는
#vp내부에 있으며, 범위는 캔버스의 3배이고 콘텐츠를 중심으로 함(x=-W, y=-H, w=3W, h=3H). 이렇게 하면 바탕 무늬가 확대/축소 및 이동을 따라가므로, 시야에 바탕 무늬 사각형의 딱딱한 가장자리가 드러나지 않음(초기 버전에서는 바탕 무늬가 캔버스 크기만 했기 때문에 150%로 확대한 후 이동하면 가장자리가 보였음). - HTML 버전에서는 SVG 제목 블록을 그리지 않음. 내보낸
.svg에서만 그림(페이지 상단 바에 이미 제목이 있음). 그렇지 않으면 확대 후 제목이 노드를 가릴 수 있음. - 좁은 화면에서 가로형으로 전환하면 항상 전체 화면에 맞춤:판단 기준은
캔버스 너비/콘텐츠 너비 > fit×1.8이고 콘텐츠 가로세로비 ≤ 1.2인 경우에만 "너비를 채움"을 사용. 가로형은 1.58의 납작한 차트이며, 390px 화면에서 너비를 채우면 150%로 확대되어(한쪽 모서리만 보임), 현재는 안정적으로 100%를 유지함.
五、비즈니스 플로우차트 작성 경험 규칙
- 노드 세분화 정도:하나의 노드 = 하나의 전달 가능한 작업("스크립트 대본 작성"은 노드, "진지하게 생각하기"는 노드가 아님). 단일 차트는 8–16개 노드가 가장 적합하며, 20개를 초과하면 메인 차트와 서브 프로세스로 분할합니다.
- 상태를 적극 활용하세요:
done/doing/todo/risk/hold는 장식이 아닙니다. 실제 프로세스에서 "확인 대기 중/자료 대기 중"과 같은 차단은hold로 표시하고, "반드시 수동 검토 필요"는risk로 표시하세요——이것이 바로 플로우차트가 문서보다 강력한 점입니다. - 판단 노드에는 실제 분기만 넣으세요:
{...}는 서로 다른 경로로 이어지는 결정에 사용됩니다. 단지 "확인하고 계속 진행"하는 경우에는milestone을 사용하세요. - 시간대는 가능한 한 자료에서 가져오세요. 임의로 만들지 마세요. 자료에 없으면 비워두고 추측하지 마세요.
- 저작권/컴플라이언스/레드라인 노드는 명시적으로 그리고
note에 작성해야 합니다. 그렇지 않으면 차트가 보기 좋아도 실제로는 위험을 초래할 수 있습니다. - 긴 체인에는 가로
LR을 사용하지 마세요:13단계 체인을 LR로 만들면 1980×489의 4:1 긴 막대가 되어, 1100px 너비의 캔버스에서 맞추면 0.55배로 축소되어 글자가 모두 뭉개집니다. 반면 다중 열 분할(가로형)은 동일한 차트를 약 1.6:1로 압축하여 한 화면에 표시하면서도 글자를 축소하지 않습니다. 경험:체인 프로세스를 가로로 표시하려면 → 다중 열 분할을 사용하고, LR을 사용하지 마세요. - 두 레이아웃 선택 방법:휴대폰에서는 세로형(한 열로 아래로 스크롤, 세로 화면 직관에 적합). 컴퓨터에서는 가로형(다중 열 분할, 한 화면 전체 보기).
autoVariant()의 임계값은너비<760px 또는 가로세로비<1.08 → 세로형입니다. - 다중 열의 읽기 방향은 여전히 아래 방향:지그재그 형태(위아래 교대)는 사용하지 않습니다. 역방향 열은 화살표 방향을 혼란스럽게 만들기 때문입니다. 열 간 연결선은 열 사이의 공백(행에 노드가 하나만 있는 경우)또는 하단 통로(행에 여러 노드가 있는 경우)를 통해 이동하며, 노드를 가로지르지 않습니다.
- 그룹이 열을 넘을 때는 자동으로 여러 세그먼트로 분할되고 「(계속)」이 표시되어, 관련 없는 노드를 포함하는 사각형을 방지합니다.
六、검증 기록(2026-10-04)
- 입력 형식:JSON / Mermaid 서브셋 /
.md의```mermaid코드 블록, 세 가지 경로 모두 실제 테스트 통과. - Mermaid 왕복:파싱 → 재내보내기
.mmd, 판단 노드{}, 점선-.->, 엣지 레이블-->|있음|,subgraph그룹 모두 올바르게 보존됨. - 두 레이아웃(13개 노드 / 13개 연결 / 3개 그룹의 예제 프로세스):세로형 533×1936(단일 열 12행). 가로형 1119×710(자동 3열 × 4행, 가로세로비 1.58, 한 화면 표시). 7개 노드 예제 프로세스:세로형 533×890, 가로형 806×459(자동 2열).
- 자동 레이아웃 선택(CDP 장치 시뮬레이션 실제 측정):390×844 →
show=portrait. 1440×820 →show=landscape. - 휴대폰 버전 레이아웃(390×844 실제 측정 높이):canvas 364 / 개요 패널 253 / 페이지 하단 81, 개요 콘텐츠 완전 렌더링(panelLen 1533).
- 터치 제스처(CDP 합성 TouchEvent 실제 측정):한 손가락 드래그 이동 ✓, 두 손가락 확대/축소 ✓, 노드 살짝 눌러 선택 및 「← 개요로 돌아가기 | s | T일 12:00 작업 시작 | 시작점 | 완료됨」 팝업 ✓. 마우스 경로도 동일하게 세 가지 항목 통과.
- 상호 작용:노드 클릭 → 세부 정보 카드 + 업스트림/다운스트림 강조 + 나머지 어두워짐. 범례 필터링은
data-hide-status에 기록. 검색 일치 개수.+/0키 확대/축소 및 맞추기로 100% 복귀. 밝음/어두움 테마 전환. 두 레이아웃에서 선택/검색/필터 상태 유지. - 로딩 중 예외 검사(
Runtime.exceptionThrown):두 샘플 페이지 모두 0 예외. - 개선 버전:카드형 노드(상태 색상 세로 막대 + 부드러운 그림자), 유형 배지, 시간 알약, 둥근 모서리 직각 우회로, 캡슐 엣지 레이블, 그룹 밴드 레이블 캡슐, 오른쪽 패널 프로세스 개요(개수 + 상태 필터 + 주의 필요 목록), 점자 바탕 무늬, 아이콘 도구 모음.
겪었던 문제점(모두 코드에서 수정 완료, 기록해두면 다음에 시간을 절약할 수 있음)
- Mermaid 판단 노드가 적용되지 않음:
touch()는shape필드를 작성하는데,normalize()는kind를 읽음 —— 필드명이 일치하지 않아 마름모가 모두 직사각형으로 그려짐. - 레이블 따옴표가 사라짐:
라벨 "진짜 문제 개작"이라벨 "진짜 문제 개작이 됨. 이유는 조건 없이 처음과 끝의 따옴표를 제거했기 때문. 처음과 끝의 따옴표가 같을 때만 제거하도록 수정. - 확대/축소 비율이 실제와 일치하지 않음:SVG
viewBox자체가meet확대/축소를 한 번 수행하므로, 원래의transform이 그 위에 중첩되어 「100%」가 실제로 26%로 표시됨. 요소 픽셀 공간에서view{K,TX,TY}를 통일적으로 계산한 후 다시 해석하도록 수정. - PowerShell
Set-Content가 UTF-8 BOM을 추가하여#!/usr/bin/env node를 손상시켜SyntaxError를 발생시킴. 이 스크립트를 수정할 때는 shell을 사용하여 파일을 쓰지 마세요. b.members가 정의되지 않음:그룹 밴드 객체에members가 누락되어, 레이블이 있는 차트가 바로 충돌.- fit 전체 화면이 긴 플로우차트를 26%로 축소:"전체 화면 맞춤과 너비 채움의 차이가 1.8배 미만이면 전체 화면 맞춤, 그렇지 않으면 너비 채움 + 상단 정렬"로 수정.
- 마름모에 글자가 눌림:판단 노드를 내접 사각형 조건으로 크기를 다시 계산한 후,
문법인가 의미론 문제인가?가 한 줄에 들어갈 수 있게 됨. - 두 레이아웃 공존 시 id 충돌:
renderSvg의<g id="vp">에 버전 접미사가 없어, 클라이언트getElementById('vp-landscape')가 null을 반환 →paint()예외 발생, 초기화 전체가 중단되고 개요 패널이 비어짐(현상은aside가 25px만 남음). 두 레이아웃의 모든 id(defs/filter/pattern/marker/vp)에는-${vid}접미사가 반드시 필요함. wrap매개변수 의미가 반대로 작성됨:이는 "각 열의 행 수"를 나타내며, 줄바꿈 없음은R = L이어야 하는데R = 1로 작성됨(R=1은 각 레이어가 한 열인 가로 막대가 되어, 세로형이 한때 3476×101로 배치됨).- CDP 예외 재생이 속일 수 있음:
Runtime.enable후 이전 문서의 예외도 함께 전송됨. "페이지가 실제로 오류를 발생시켰는지" 판단하기 전에 먼저Page.navigate를about:blank로 이동한 후 대상 페이지로 들어가야 함. 그렇지 않으면 이전 페이지의 오류를 새 페이지의 오류로 계산함. - 살짝 누름 선택 검증은 먼저 맞추기를 수행해야 함:제스처 조작 후 대상 노드가 이미 보이는 영역 밖으로 이동했을 수 있으며,
elementFromPoint가 자연스럽게 노드를 찾지 못함——이는 테스트 스크립트의 문제이지 페이지의 문제가 아님. - CDP 검증이 사용자의 탭을 빼앗음:
webctl shot/ 첫 번째 page target을 직접 가져오면, 모두 사용자가 보고 있는 탭을 이동시킴. 사용자가 동시에 브라우저를 사용하는 경우, 서로 방해함(실제 측정:내가 이동시켰지만, 사용자가 검색하면 다시 돌아와서location이 수십 초마다 인턴 웹사이트로 변경됨).tools/cdp.mjs의withOwnTab()으로 수정——자체적으로Target.createTarget으로 임시 탭을 만들고,/json/list에 나타날 때까지 폴링, 사용 후Target.closeTarget으로 닫음. 그럼에도 불구하고 새 탭은 여전히 포커스를 빼앗음. 사용자가 브라우저를 사용 중일 때는 먼저 물어봐야 함. - 검색에서 "추가로" 일치하는 것은 버그가 아님:
입을 검색하면 2개가 일치하는 이유는 설명/비고("입구 보정")와 id(노드 idj)를 동시에 검색하기 때문. 수정 방법은 숫자를 변경하는 것이 아니라, 보이지 않는 일치 항목을 표시하는 것(주황색 점선 원 + 수준별 개수 + 일치 목록). - 백그라운드 탭은 스크린샷을 찍을 수 없음:
Target.createTarget({background:true})은 사용자 포커스를 빼앗지 않지만, 백그라운드 탭은 프레임을 생성하지 않아Page.captureScreenshot이 영원히 대기함(실제 측정 120초 타임아웃). 스크린샷을 찍으려면 포그라운드 탭을 사용해야 함(포커스를 빼앗음). 백그라운드 탭은 순수한 기하학적 측정에만 적합함(getBoundingClientRect/getComputedStyle은 정상 작동). 또한 백그라운드 탭의 CSS 트랜지션은 진행되지 않음——측정 전에 먼저*{transition:none!important}를 주입해야 하며, 그렇지 않으면 애니메이션 시작점을 측정하게 됨(서랍의rect.x=398을 "열리지 않음"으로 잘못 판단했지만, 실제로는 스로틀링이었음). - "컴퓨터용" 산출물에도 "휴대폰에서 여기 클릭"이 표시됨:파일 목록의 배지는 확장자만 봄. 이제
【/_로 시작하는 파일은 별도로 「컴퓨터용, 휴대폰에서는 클릭하지 마세요」로 표시하고 회색 처리함——판단 조건은 표시 문구와 함께 변경되어야 함. - 검증 산출물을 서비스 중인 디렉토리에 배치하지 마세요:
shot-*.png, 프로브 스크립트가 휴대폰의 파일 목록에 나타남. 모두 서비스되지 않는 디렉토리(예:out/_shots/)에 통일하여 배치하고,serve.mjs가 서비스하는 산출물 디렉토리에 넣지 마세요. - 서랍 목록이 손가락 아래에서 재정렬되면 = 잘못된 항목 클릭:페이지 내장 스냅샷은 원래 "현재 페이지가 가장 먼저"였지만,
fetch('프로세스_index.json')가 돌아올 때는 mtime 순서로 정렬됨 —— 서랍을 연 후 약 100ms 후에 목록이 한 번 변경되어, 사용자가 클릭한 항목이 의도한 항목이 아닐 수 있으며, "클릭해도 반응 없음 / 돌아갈 수 없음"으로 나타남. 수정 방법 두 가지:목록을 파일명 순서로 고정 정렬(스냅샷과 목록 순서가 항상 일치), 현재 페이지를 비링크로 렌더링. 교훈:"열린 후 비동기로 새로고침"되는 모든 목록은 순서가 안정적이어야 함. - serve.mjs를 수정한 후 서비스를 다시 시작해야 함:페이지 템플릿은 프로세스 메모리에 있으므로, 다시 시작하지 않으면 계속 이전 버전임(이 때문에 "홈페이지에 h1이 없다"고 한동안 헤맸음).
- 디렉토리 페이지에
cache-control이 누락되면 사용자가 영원히 이전 홈페이지를 보게 됨:사용자가 두 번 "컴퓨터 버전 홈페이지가 너무 단순한 것 같다/썸네일이 없다"고 말했지만, 실제로는 이미 카드+썸네일로 변경했으나 브라우저가 계속 캐시된 이전 응답을 사용했기 때문. 동적으로 생성된 HTML은 반드시no-store여야 함. Start-Process로 분리하여 시작한 서비스는 명령이 끝나면 같이 종료됨:샌드박스는 명령이 끝날 때 프로세스 트리를 정리하므로 서비스가 조용히 사라짐(다음 요청 시 "원격 서버에 연결할 수 없습니다" 오류 발생). harness 백그라운드 작업을 사용하거나, 사용자가 직접 터미널에서 실행하도록 하세요.- 디렉토리에서 이름이 같거나 비슷한 JSON을 입력으로 사용하지 마세요:디렉토리에
갑_프로세스.json이 있는데, 이름은 갑 프로세스처럼 보이지만 내용은 을 프로세스의 사양임. 이를render하면 기존갑_프로세스.html을 다른 내용으로 덮어씀. 렌더링 전에 JSON의title/노드 수를 먼저 확인하고, 산출물과 동일한 이름의 입력을 우선 사용하세요. - 중국어 주석에 곧은 따옴표가 있으면 JS 문자열의 큰따옴표를 자를 수 있음(
" // 이야기하는 것은 "순수 파일명" 목록"→ 조기 종료). 동일한 문제를 세 번 겪음:중국어 주석에서는 따옴표를 사용하지 않거나 「」를 사용하세요. file://에서는 "디렉토리 홈페이지"가 없음:href="./"는 로컬에서 브라우저의 순수 파일명 목록으로 이동하여, 사용자에게 "파일 이름만 표시"됨. 동적으로 생성된 디렉토리 페이지는 별도의 정적index.html을 작성해야 하며, 홈페이지 버튼이 프로토콜에 따라 대상을 전환하도록 해야 함.- 디버그 브라우저가 없을 때 스크린샷을 찍는 방법:샌드박스에서 Edge가 실행되지 않지만(mojo 명명된 파이프가 거부됨),
msedge --headless=new --screenshot=... --window-size=W,H <url>일회성 렌더링으로 파일 쓰기는 가능함(일회성 제한 없는 권한 필요).file://로컬 페이지도 촬영할 수 있고, 크기에 따라 휴대폰/컴퓨터도 시뮬레이션 가능하며, "휴대폰 첫 화면에서 카드가 보이지 않음" "요약이 0으로 계산됨"을 찾아내는 데 핵심적인 역할을 함. - 템플릿 문자열에서 정규식을 작성할 때는 이중 백슬래시를 사용해야 함:
\d/\s는 템플릿 문자열에서d/s로 퇴화되어, 페이지에 생성된 정규식이 무효화됨(요약이 한때 "총 0단계"로 표시됨). 페이지 스크립트에 작성된 정규식은 통일적으로\\d、\\s를 사용하세요. - 문구를 기기에 관계없이 하드코딩하지 마세요:컴퓨터 홈페이지에 「휴대폰에서 여기 클릭」이라고 쓰거나, 휴대폰 플로우차트에서 「스크롤 확대/축소/더블 클릭/Esc」를 가르치는 것은 모두 "기기 불일치" 문제임. 해결 방법은 동일한 위치에 두 세트를 작성하는 것(
.only-pc/.only-touch)+@media (hover:none),(pointer:coarse)전환 —— 입력 방식으로 판단하는 것이 화면 너비로 판단하는 것보다 더 정확함(가로 모드 휴대폰, 좁은 창 컴퓨터 모두 정확함). - 동일한 디렉토리에 동일한 이름의 플로우차트가 있으면 카드가 똑같이 보임:
xxx - 복사본.html과xxx.html은 제목이 동일하여 사용자가 구분하지 못함. 제목이 중복되면 파일명을 한 줄 추가로 표시하고, 썸네일도 "공백 제거 및 -가로형/-세로형 제거" 정규화된 이름으로 매칭해야 함. - 템플릿 문자열의
\n은 실제 줄바꿈이 됨:homeScript()같은 템플릿에 작성된 페이지 JS에서'...\n\n' + x는 페이지에서 문자열 리터럴에 실제 줄바꿈이 포함된 형태가 되어 → 페이지 스크립트 구문 오류 발생(node --check로 serve.mjs를 검사해도 발견되지 않음).\\n을 사용해야 함. 검증 방법:buildHome()이 생성한 두<script>를 추출하여 각각node --check로 검사(이 회귀 단계를 추가함). - "내용 동일" 여부는 의미론적으로 판단해야 하며, 파일 해시로 판단하지 마세요:전체 페이지 해시는 당연히 다름(렌더링 시간 등이 포함되므로). 이로 인해 동일한 프로세스를 여러 번 렌더링한 파일이 "내용이 다른 중복 파일"로 잘못 처리되어 원본이 손실될 수 있음. 수정 방법은 프로세스 의미론적 지문을 사용하는 것임.
- "전체 플로우차트" 서랍의 fetch가 실패해도 페이지가 중단되지 않아야 함:서랍을 열 때 fetch가 실패하면(네트워크 연결 끊김 등)→ 내장 스냅샷을 사용하면 되지만, 스냅샷이 목록의 순서를 보장하지 않음. 수정:fetch 실패 시 서랍을 닫고 사용자에게 "목록을 불러오지 못했습니다. 로컬 모드로 전환"을 알림.
- 코드 완성 검증 시 "이미지가 보이지 않음"은 대부분 서비스가 실행되지 않았거나 포트가 잘못되었기 때문:기억하세요:서비스와 render는 서로 다른 터미널 창입니다.
- 실제 사례 (2026-10-04):사용자가 "프로세스 그려줘"라고 말함, 13단계 제품 체인. 내부 토큰 마감(SVL)이 하이라이트 키로 표시되고, 수동 검토 지점이 risk로 표시됨. 두 레이아웃 모두 통과, 자동 선택이 올바름. 사용자가 "이 파일을 보내줘"라고 함.
serve.mjs를 시작하여 휴대폰에서 접속, 휴대폰 Safari로 테스트. 검색 아이콘 입력이f를 트리거하지 않음(실제 수정:input[type=search]가 포커스될 때 자동으로f를 트리거하지 않도록 방지하기 위해 한 번에 30ms 지연 추가). 수동 입력은 정상. 사용자가 "가져가기"라고 함. 완료.
추가 참고 사항
- Mermaid
graph TB의 등호는===가 아닌 실선 ===입니다.``` - Mermaid 스타일은
style id fill:#xxx,stroke:#xxx - Mermaid 하위 그래프는
subgraph title...end - JSON 사양 예제는
references/spec.md의## JSON 사양섹션을 참조하거나,sample을 실행하여 직접 확인하세요