Skip to content

변경 이력

운영 규칙

디자인 시스템(토큰 · 컴포넌트 · 패턴 문서)이 변경되면 같은 작업에서 이 페이지에 항목을 추가합니다. 문서만 고치고 이력을 비워두지 않습니다.

2026-09-04 — 떠 있는 어시스턴트도 ESC 로 닫힘 (design/ai-assistant-dock)

변경

  • 떠 있는 어시스턴트(좁은 화면)가 ESC 로 닫힙니다. 그때는 본문 위에 얹힌 팝업이라 ESC 가 자연스럽습니다. 도크는 그대로 둡니다 — 본문을 밀어내고 자리를 차지하는 상시 크롬이라, 모달을 띄운 채로도 쓰라고 만든 것을 키 하나로 접으면 전제가 깨집니다
  • 실측: 떠 있는 AI(z65) + 모달(50) 상태에서 ESC 1회 → 어시스턴트, 2회 → 모달. 도크에서는 ESC 를 눌러도 유지됩니다

2026-09-04 — 캡처가 어시스턴트·시트 레일을 빼던 것 (design/ai-assistant-dock)

수정

  • 어시스턴트를 열어 둔 채 신고하면 그 패널이 그림에서 빠졌습니다(사용자 리포트). 캡처 필터가 .ds-fab-layer 를 통째로 걸러 채팅·어시스턴트 패널과 시트 워크북 레일까지 지웠습니다 — 어시스턴트가 문제일 때가 신고가 가장 필요한 순간인데 정작 증거가 사라진 것입니다. 이제 빼는 것은 신고 UI 자신확인 다이얼로그 둘뿐입니다
  • 도크가 열려 있을 때 오른쪽을 잘라내던 크롭도 제거했습니다. 패널이 필터에서 빠지던 시절 빈 흰 칸을 없애려던 보정인데, 이제는 그 크롭이 어시스턴트를 잘라냅니다

2026-09-04 — 좁은 화면 자동 접힘 · 떠 있는 창은 하나만 (design/ai-assistant-dock)

변경

  • 1280 밑으로 내려가는 순간 어시스턴트가 접힙니다. 떠 있는 창으로 바꿔 두면 그 폭에서 채팅과 80% 겹쳐 뒤쪽을 못 읽습니다 — 열어 둔 채 두는 것보다 접는 편이 낫습니다
  • 떠 있는 상태에서는 채팅과 어시스턴트가 한 번에 하나만 열립니다(UI 겹침 방어). 여는 쪽이 신호를 쏘고 반대쪽이 접습니다 → FAB 배치 계약

함정

  • 경계를 넘는 순간에만 접어야 합니다 — 좁은 화면에서 사용자가 직접 연 떠 있는 창까지 리사이즈마다 닫으면 못 씁니다
  • 도크에서는 상호 배타를 걸지 않습니다 — 채팅이 도크 왼쪽으로 비켜서 겹치지 않으므로 둘 다 열려 있어도 됩니다(실측 겹침 0). 무조건 하나만 열리게 하면 도크의 이점이 사라집니다

2026-09-04 — 도크 폭이 창을 줄였다 넓히면 안 돌아오던 것 (design/ai-assistant-dock)

수정

  • 창을 줄이면 도크 폭이 그대로 굳어 다시 넓혀도 안 돌아왔습니다. 800 으로 쓰던 상태에서 창을 900px 로 줄였다 1920 으로 되돌리면 360 으로 남았습니다(실측). 창이 좁아질 때 고른 폭 자체를 덮어쓰고 있었기 때문입니다 — 이제 표시만 물리고(dockRenderWidth) 고른 값(dockWidth)은 그대로 둡니다. 확인: 800 → 680 → 384 → 680 → 800
  • 드래그 기준도 「지금 그려진 폭」으로 바꿨습니다. 고른 폭이 상한에 물려 있는 상태에서 고른 값을 기준으로 잡으면 손잡이를 잡는 순간 폭이 튑니다

2026-09-04 — ESC 는 가장 위에 뜬 팝업부터 (design/ai-assistant-dock)

변경

  • ESC 가 「화면에서 가장 위에 있는 것」 하나만 닫습니다. 예전에는 팝업마다 자기 @keydown.escape.window 를 달아 먼저 등록된 리스너가 먼저 돌았는데, 그 순서는 Alpine 이 x-data 를 만나는 순서(DOM 순서)라 늦게 뜬 팝업과 무관했습니다 — 모달 위에 신고 팝업을 띄우고 ESC 를 누르면 밑에 깔린 모달이 닫혔습니다(사용자 리포트). esc-guard.ts공용 디스패처를 두고 신고 모달·채팅 패널을 등록했습니다. 실측: 모달+신고 상태에서 ESC 1회 → 신고만, 2회 → 모달
  • 새 팝업은 registerEscLayer(getEl, close) 로 등록하고 마크업의 ESC 리스너는 뺍니다FAB 배치 계약

함정

  • 디스패처는 window 캡처 단계에 걸어야 합니다. 버블 단계면 다른 팝업의 리스너와 다시 순서 싸움이 됩니다
  • 「가장 위」는 시간이 아니라 z-index 로 고릅니다 — 나중에 뜰수록 높은 층에 서므로 결과가 같고, 등록을 빠뜨린 팝업이 있어도 무너지지 않습니다. 레거시 모달이 더 위면 비켜서서 그 모달의 자기 리스너가 처리합니다

2026-09-04 — 신고 모달을 팝업 위로 · 어시스턴트 글자 정합 (design/ai-assistant-dock)

수정

  • 신고 모달이 다른 팝업 아래로 들어가던 것을 고쳤습니다(사용자 리포트). 신고는 화면이 이상할 때 부르는 것이라 그 화면이 이미 모달이거나 모달 위 모달(60)인 경우가 흔한데, 기본 백드롭(50)이라 그 아래에 깔려 「눌렀는데 아무 일도 안 난다」로 보였습니다. .ds-modal-backdrop--report(--ds-z-modal-report 68)를 신설했습니다 — 컨펌(70)보다는 아래라 신고 모달 안에서 띄우는 확인 팝업이 자기를 덮습니다
  • 어시스턴트 답변의 문단만 16px 로 뜨던 것을 고쳤습니다 — 공유 DS 의 unlayered p 규칙이 말풍선의 text-b5(13px)를 이겨서, 사용자 말풍선(13px)과 나란히 놓였을 때 어시스턴트만 커 보였습니다. .ai-md pfont-size: inherit 을 넣었습니다

변경

  • 응답 피드백(👍/👎)을 감췄습니다. ai.feedback 로 저장은 되는데 읽는 곳이 한 군데도 없어 사용자가 누른 값이 아무 데도 쓰이지 않습니다. 쓰는 곳이 생기면 마크업을 되살리면 됩니다

2026-09-04 — 딤 위 신고 버튼 · 어시스턴트에서 신고 진입점 제거 (design/ai-assistant-dock)

수정

  • 버그 리포트 캡처에 팝업이 담기지 않던 것을 고쳤습니다 — 아래 함정 참고

추가

  • 팝업이 떠 있을 때만 뜨는 신고 버튼 .ds-report-fab(흰 원 + 어두운 글리프) — 층은 어시스턴트와 같은 65(--ds-z-report-fab)라 겹쳐 뜬 모달(60) 위에서도 눌립니다 — 팝업이 뜨면 헤더의 「불편·기능 신고」가 딤에 가려 못 누르는데, 정작 팝업이 문제일 때가 신고가 가장 필요한 순간입니다. 딤 위(--ds-z-report-fab 55)에 뜨고, 결정을 묻는 팝업(컨펌 70·권한 75)보다는 아래입니다 → FAB 배치 계약

변경

  • 어시스턴트 상단바의 「버그 리포트 하기」를 뺐습니다. 신고 진입점은 헤더의 상시 진입(확성기)과 오류 다이얼로그의 「리포트 보내기」, 그리고 위 플로팅 버튼이 맡습니다 — 대화창에 같은 입구를 하나 더 두면 어디로 들어가야 하는지가 흐려집니다

함정

  • offsetParent 로 「모달이 떠 있나」를 재면 안 됩니다. 백드롭은 position: fixed항상 null 이라, 그 판정을 쓴 채팅 ESC 가드가 통째로 죽어 있었습니다(2026-09-04 실측) — 모달 위에서 ESC 를 누르면 모달 대신 채팅이 닫혔습니다. computed display 로 봅니다
  • 신고 버튼을 55 에 두면 겹쳐 뜬 모달(60)에 깔립니다 — admin 「근무 설정」 패널 위 「휴가/연차 유형」 같은 흔한 조합입니다(사용자 리포트). 어시스턴트와 같은 65 로 올렸습니다
  • 캡처가 팝업을 통째로 빼고 있었습니다(2026-09-04 사용자 리포트). 필터가 클래스만 보고 .ds-modal-backdrop 을 전부 걸러, 사용자가 신고하려는 그 팝업이 그림에서 사라졌습니다 — 팝업이 문제일 때가 신고가 가장 필요한 순간인데 정작 증거가 빠진 것입니다. 이제 확인 다이얼로그(--top)만 빼고 일반 팝업은 담습니다. 리포트 모달 자신은 data-cs-report-host 로 이미 걸러집니다
  • <button> 으로 두면 원이 안 됩니다. 전역 blanket 규칙이 모든 <button> 의 radius 를 4px 로 강제해 모난 사각으로 렌더됩니다(실측) — 채팅·AI FAB 과 같이 <div role="button"> 을 씁니다
  • 헤더 안 버튼만 위로 올릴 수는 없습니다. 헤더 래퍼의 z-index(그리고 position: sticky)가 쌓임 맥락을 만들어 그 안의 z-index 는 바깥과 겨루지 못합니다 — 자식에 position: fixed 를 줘도 같습니다. 줄 전체를 올리면 팝업 중에 상단바만 밝게 남아 어색해서, 화면 최상위에 따로 그리는 플로팅 버튼으로 갔습니다
  • FAB 줄과 같은 높이에 두면 안 됩니다. AI FAB 과 좌표가 정확히 겹치는데(실측 둘 다 x=1532) AI FAB 은 딤 위(65)라 이 버튼(55)을 덮어 클릭이 그쪽으로 갑니다 — 눈에는 안 보이는 고장입니다. 한 칸 위(bottom: 104px)에 세웁니다
  • 보임/숨김은 CSS :has() 가 정합니다. MutationObserver 는 admin(2만 노드)에서 x-show 토글마다 돌아 버튼 하나 값어치를 넘습니다

2026-09-04 — 대화 말풍선 b5로 축소 · 채팅 ESC 닫기 (design/ai-assistant-dock)

변경

  • 채팅·어시스턴트 말풍선 본문을 text-b3(16px) → text-b5(13px) 로 내렸습니다(실측 확인). 두 대화창이 한 화면에 나란히 뜨므로 같은 값이어야 합니다. 여백도 함께 좁혔습니다 — 어시스턴트 px-3 py-2px-2.5 py-1.5, 채팅 px-3.5 py-2px-3 py-1.5, 메시지 간격 gap-2gap-1.5
  • 어시스턴트 마크다운(.ai-md)의 크기 위계를 다시 잡았습니다 — 제목 b2(18)→b4(14), code b5(13)→b6(12), 문단·목록·표 여백도 한 단계씩. 본문만 내리면 제목이 본문보다 커 보이고 code 가 본문과 같아져 위계가 뭉갭니다
  • ESC 로 채팅 패널이 접힙니다. 어시스턴트는 그대로 안 닫힙니다 — 상시 크롬이라 ESC 의 대상이 아닙니다 → FAB 배치 계약

함정

  • 말풍선 크기는 한쪽만 바꾸면 안 됩니다. 채팅과 어시스턴트가 나란히 떠서 글자가 다르면 바로 눈에 띕니다. 그리고 어시스턴트는 마크다운 스케일이 본문에 물려 있어 두 파일을 같이 봐야 합니다
  • ESC 처리를 stopImmediatePropagation() 에 기대지 마세요. 모달도 같은 window 리스너를 쓰는데 등록 순서는 Alpine 이 x-data 를 만나는 순서라 예측할 수 없습니다 — 화면마다 결과가 달라집니다. 지금 무엇이 떠 있는지로 판정해야 하고, 숨은 백드롭이 DOM 에 여러 개 있으므로 존재가 아니라 표시 여부를 봅니다

2026-09-04 — 우하단 FAB 정리 + 어시스턴트 사이드 패널(도크) (design/ai-assistant-dock)

변경

  • AI FAB 과 채팅 FAB 의 자리를 맞바꿨습니다AI 가 바깥(가장자리 24px), 채팅이 안쪽(80px) 입니다(사용자 결정). 색도 갈립니다: AI = BI 패밀리 그라디언트(.ds-fab--brand), 채팅 = 단색 다크(bg-neutral-900). 예전에는 채팅이 bg-orange-400, AI 가 bg-neutral-900 이었습니다
  • 가로 자리를 마크업에서 걷어냈습니다. right-[calc(80px+…)] 같은 임의값 대신 .ds-fab--ai / .ds-fab--chat 만 붙이고 design-system.css 가 슬롯을 고릅니다 — 채팅은 AI FAB 이 실제로 DOM 에 있을 때만 안쪽으로 물러납니다(body:has(.ds-fab--ai)). AI 는 admin 한 곳에만, 그것도 조직 AI 가 켜져 있을 때만 그려지므로 못 박으면 나머지 화면에서 가장자리가 빕니다 → FAB 배치 계약

추가

  • 어시스턴트가 화면 오른쪽에 붙는 도크로 열립니다 — 덮지 않고 본문을 밀어냅니다(body.ds-ai-dock-openpadding-right). 폭은 좌측 핸들 드래그로 360 ~ min(800, 화면폭 − 720), 키보드(←/→·Home)로도 조절됩니다. 태블릿 이하(<1280)에서는 예전의 떠 있는 패널로 자동 폴백합니다 → 도크 규칙
  • 어시스턴트만 모달 위에 섭니다(--ds-z-fab-ai 65). 상시 크롬이라 모달을 띄운 채 물을 수 있어야 하는데 44 에서는 딤 아래로 내려가 통째로 못 썼습니다. 위 칸은 컨펌 다이얼로그(70)·권한 팝업(75)이 가져갑니다
  • AI FAB 의 새 답변 표시 .ds-fab__dot — 개수를 세지 않으므로 숫자 배지가 아니라 점 하나입니다. 흰 링은 장식이 아니라 필수입니다(그라디언트 면에서 빨간 점이 묻힙니다)
  • iframe 안에서는 두 FAB 을 그리지 않습니다(.ds-embedded) — admin 이 품는 앱들도 자기 화면에서 FAB 을 그려 한 화면에 두 벌 떴습니다

함정

  • 채팅·어시스턴트 패널의 앞뒤를 z-index 로 바꾸려 하지 마세요. 어시스턴트는 모달(50) 위, 채팅은 모달 아래여야 해서 두 요구가 동시에 성립하지 않습니다 — 빈 z 칸을 어떻게 배치해도 풀리지 않습니다. 겹치지 않게 만드는 것이 해법이고, 도크가 그 일을 합니다(겹침 0 실측)
  • --ds-z-fab 토큰을 올려서 해결하지 마세요. 채팅 FAB·패널과 시트 워크북의 수식 범위 바가 함께 씁니다 — 올리면 2026-08 에 고친 「FAB 이 모달의 확정 버튼을 덮는다」가 돌아옵니다. 예외는 요소 단위(더블 클래스)로만 줍니다
  • 도크로 전환해도 AI FAB 은 DOM 에 남습니다(x-show). body:has(.ds-fab--ai) 가 계속 매치해 그냥 두면 채팅이 안쪽에 선 채 바깥 자리만 빕니다body.ds-ai-dock-open 규칙이 특이도로 되돌립니다
  • 드래그 중 iframe 의 포인터 이벤트를 죽여야 합니다. admin 은 iframe 을 여럿 품는데, 커서가 그 위로 가면 부모가 이벤트를 못 받아 드래그가 그 자리에 굳습니다

2026-08-26 — 안내 아이콘 한 쌍으로 통합 + 이름 규칙 명문화 (design/icon-check-update-docs)

수정

  • 획을 stroke 가 아니라 「채운 면」으로 그린 아이콘 15개가 굵기 정규화를 피해 갔습니다. 실측하니 organization 1.20px · workspace·team·signature 1.25px · money·setting 1.30px 로 표준(1.67px @20px)의 0.72~0.78배 였어요 — 화면에서 혼자 얇아 보이던 이유입니다. stroke-width 가 아예 없으니 8/25 굵기 개정이 닿지 않았습니다
  • money · organization · workspace 를 stroke 기반 표준으로 다시 그렸습니다(0.99~1.05x 로 수렴)
  • settinggear 와 중복이었고, gear 쪽이 이미 표준 굵기였습니다setting 이름을 유지한 채 도형을 gear 것으로 바꾸고 ic-gear 는 삭제했습니다(호출 4곳 이동). ic-setting-fill 도 같은 실루엣의 solid 로 교체
  • ic-chevron-down 만 혼자 채운 면이라 나머지 세 방향보다 두꺼웠습니다 — stroke 기반으로 바꿔 네 방향이 같아졌습니다
  • ic-dots-grid 는 이름과 다른 그림이었습니다 — 실제로는 키보드(M11 14h2 가 스페이스바)였어요. 이름대로 3×3 점 격자로 다시 그렸습니다
  • ic-party 의 폭죽 파티클이 깨져 보였습니다 — 점 좌표를 정리했습니다
  • ic-refresh-fill 을 삭제했습니다(호출 1곳 → ic-refresh)
  • team · signature 도 같은 원인으로 얇아 다시 그렸습니다signature 는 테두리 안 흘림이 획 2에서 뭉개져 필기선 + 밑줄 형태로 바꿨습니다
  • 점(dot) 크기를 통일했습니다ic-dots-grid 는 이름과 달리 키보드였고, 3×3 격자로 다시 그리면서 점을 ic-dots-horizontal같은 구성(반지름 .75 원 + stroke 2 = 지름 3.5)으로 맞췄습니다. ic-grip 도 지름 2.8 → 3.5
  • line 을 다시 그리면 -fill 짝도 같이 그려야 합니다organizationworkspace 는 line 만 바꿔서 한동안 짝이 서로 다른 그림이었어요. 둘 다 새 구성에 맞춰 solid 를 다시 만들었습니다
  • ic-terminal(1.83x)은 예외입니다 — 짝 없는 solid 글리프라 두꺼운 게 맞습니다. ic-file-xlsx(0.72x)는 안에 «XLSX» 글자가 든 배지형이라 성격이 달라 그대로 뒀습니다
  • fill 변형 7종을 추가했습니다ic-bell-fill · ic-camera-fill · ic-cap-fill · ic-employee-fill · ic-folder-fill · ic-star-fill · ic-tag-fill. line↔fill 쌍이 18 → 25쌍이 됐어요
  • 자동 생성은 열린 선 디테일을 지워버립니다ic-bell-fillic-cap-fill이 사라졌었습니다. 둘 다 열린 선(M10 21h4, M21 7v6)이라 채우면 넓이가 0이 돼요. line 의 획 굵기까지 반영한 범위로 채운 알약을 직접 그려 넣었습니다(예: 추 = M10 20h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2z → line 의 M10 21h4 + stroke 2 와 같은 x 9~15 · y 20~22)
  • ic-question-circle-fill 을 새로 추가했습니다 — line 만 있고 fill 이 없던 쌍입니다. line 과 채움 비율이 같도록 viewBox 를 계산해서("0.4 0.4 19.2 19.2") 넣어 1.00x 로 맞췄습니다
  • ic-navigation 을 지우고 ic-map-pin 으로 통합했습니다. 종이비행기 모양이었는데, 하필 쓰이는 곳도 스케줄러 「제출」 버튼 한 곳뿐이라 rotate-90 으로 돌려 "보내기" 아이콘처럼 쓰고 있었어요 — 이름과 그림과 용도가 셋 다 어긋나 있었습니다. 「제출」 버튼은 원래 그 뜻인 ic-send 로 바꾸고(회전도 불필요해졌습니다), 지도 위치는 이미 있던 ic-map-pin 이 맡습니다. 「한 의미에 한 쌍만」 규칙대로 같은 뜻의 두 번째 이름을 만들지 않았어요
  • ic-map-pin-fill 을 새로 추가했습니다 — line 만 있던 쌍입니다. line 의 획 바깥선과 같은 실루엣으로 그리고 안쪽 원은 fill-rule="evenodd" 로 파냈습니다(1.03x — 허용 범위 8% 이내)
  • 알림 아이콘을 심각도 3종으로 정리했습니다오류는 원+!(ic-exclamation-circle) · 경고는 삼각형+!(ic-warning) · 안내는 원+i(ic-info-circle). 색만으로 구분하면 빨간 상자 안에 안내 기호가 들어가 톤이 어긋나요 → 알림 아이콘
  • 중복이던 ic-infoic-info-circle 로 합쳤습니다(호출 64곳 이동). 두 원(exclamation·info)은 같은 원(r=9)에 막대와 점만 상하 반전한 형제로 다시 그려 한 가족으로 읽힙니다
  • 오류 공지 29곳이 안내 기호(ⓘ)를 쓰고 있던 걸 바로잡았습니다 — 로그인·회원가입 실패, 초대 오류, 연차 정책 불일치 등. 같은 자리에서 원래도 어긋나 있던 2곳(develop 시점부터 ic-info)도 함께 교정했습니다
  • ic-info-circle-filli 를 다시 그렸습니다. line 쪽 i 와 글자 모양이 아예 달랐어요(글자가 눌리고 아래에 이상한 발이 붙어 비대칭). viewBox 도 20 vs 24 로 달라 나란히 놓으면 fill 이 더 커 보였습니다. 이제 line 과 같은 24 좌표계에서 같은 위치·굵기로 그린 뒤 fill-rule="evenodd" 로 안쪽을 파냅니다
  • ic-lightbulb 이 찌그러져 있었습니다. 유리부 호의 현 길이가 11.54 인데 반지름이 6(지름 12)이라 거의 한계치였고, large-arc=1 과 겹쳐 원 둘레의 59% 를 그리는 기형 호가 됐습니다 — 좌상단이 움푹 들어가고 좌우가 어긋나 보였어요. 반지름 5.6 의 정상 호로 다시 그렸습니다

추가

  • 이름 규칙을 문서에 명문화했습니다ic-{의미}-{형태}-{채움} 순서이고 채움 자리는 fill 일 때만 씁니다(line 은 접미사 없음). 한 의미에 한 쌍만 두고, 쌍은 같은 글자·같은 좌표계로 그립니다

  • line↔fill 의 크기가 어긋난 3쌍을 맞췄습니다ic-warning(fill 이 17% 작음) · ic-lock(17% 큼) · ic-check-circle(13% 큼). d 좌표는 건드리지 않고 viewBox 만 조정해 고쳤습니다(예: ic-check-circle-fill "0 0 16 16""-1.5 -1.5 18 18"). 15쌍 전체를 실측해 1.00x 로 수렴한 것을 확인했습니다

앞선 기록 정정

  • "viewBox 가 어긋난 쌍 5개" 는 잘못된 진단이었습니다. 실측해 보니 viewBox 숫자와 실제 보이는 크기는 별개였어요 — ic-menu(24↔16)·ic-refresh(24↔20)는 viewBox 가 달라도 채움 비율이 같아 멀쩡했고, 반대로 viewBox 가 같은 ic-lock(24↔24)이 17% 어긋나 있었습니다. 기준을 "글리프가 박스를 채우는 비율" 로 바꿔 문서에 명시했습니다
  • "fill 안쪽 마크가 얇다" 도 과잉 진단이었습니다. 15쌍을 크게 놓고 보니 문제가 아니었어요 — 파낸 마크는 검은 바탕 위 흰 영역이라 같은 폭이어도 더 두껍게 보여서(irradiation), 얇게 그린 것이 오히려 올바른 광학 보정입니다. line 굵기에 맞춰 키우지 마세요
  • ic-message(0.91x)·ic-user(0.92x)는 정상 범위로 두었습니다 — solid 는 원래 조금 작게 그립니다

변경

  • 락업 최소 높이를 24px → 16px 로 내렸습니다브랜드 · 크기와 보호 영역. 「그보다 작으면 워드마크를 떼고 심볼만」이라는 판단 자체는 그대로이고, 그 경계선만 옮겼습니다. 심볼 단독에 하한이 없다는 것도 종전과 같습니다
  • 데모의 높이 슬라이더 하한도 16 → 10 으로 내렸습니다. 규칙 하한과 슬라이더 하한이 둘 다 16 이면 「최소 크기 미만」 경고를 한 번도 볼 수 없게 되기 때문입니다 — 규칙은 보여줄 수 있어야 규칙입니다

추가

  • LNB 하단 브랜드 마크(.ds-lnb__brandmark) — 영문 outline 락업(다크 배경용 흰색 단색) · 높이 18px · 불투명도 30% · 가운데 정렬 · 비클릭. 접힘 레일(안쪽 폭 44px)에서는 락업 대신 같은 outline 심볼로 바뀌고, 메뉴가 세로로 다 들어가지 않는 화면에서는 숨겨 메뉴에 자리를 돌려줍니다(세로가 짧은 노트북에서 안 보이는 것이 정상입니다). CSS 는 공용이지만 지금 렌더하는 앱은 admin 하나입니다
  • outline 자산 2종을 레지스트리에 등록했습니다logo-lockup-hereby-medical-outline-on-dark · logo-hereby-medical-outline-on-dark. outline 은 색이 배경에서 갈리므로 이름에 배경이 붙습니다

수정

  • LNB 헤더의 「hereby 심볼 × 병원 로고」 코브랜딩을 걷어냈습니다. 208px LNB 에서 브랜드 텍스트가 쓸 수 있는 폭이 29px 까지 밀려 「hereby Me…」로 잘렸어요(.ws-brand 안쪽 136 − 심볼 24 − gap 8 − 코브랜딩 블록 75). 이제 로고를 등록한 조직은 심볼 자리를 로고가 대체하고(같은 24px 정사각), hereby 는 위의 하단 브랜드 마크가 맡습니다
  • 「LNB 헤더에 락업을 못 쓰는 이유」가 규칙 위반에서 디자인 판단으로 바뀌었습니다. 132px 폭에 넣으면 높이가 19.8px 인데, 하한이 24 이던 때는 위반이었고 16 인 지금은 규칙상 가능합니다. 그래도 화면은 심볼 + 텍스트를 유지합니다 — 승격하려면 시안을 먼저 받으세요

2026-08-25 — 획 굵기 규격 16px 기준 재정의 + 아이콘 갤러리 신설 (design/icon-check-update-docs)

추가

  • 아이콘 라이브러리 페이지를 채웠습니다 — 그동안 "준비 중" 스텁이라, 등록된 아이콘 162개를 훑어볼 방법이 레포에 없었습니다(packages/static/icons/*.svg 폴더를 Finder로 여는 게 유일한 우회로였어요). 이름 검색 · 12~24px 미리보기 크기 토글 · 클릭하면 이름 복사
  • 목록을 손으로 적지 않습니다packages/components/src/icons.ts 를 빌드 시점에 그대로 불러와(getIcon/iconNames) 렌더하므로, 아이콘을 새로 등록하면 이 페이지도 다음 배포에서 자동으로 같이 늘어납니다
  • 이 페이지 자체가 회귀 확인 도구입니다 — 크기 토글로 전 아이콘을 12~24px 구간에서 눈으로 훑을 수 있습니다. 아래 두 결함 다 이 갤러리를 보다가 찾았어요

수정

  • 획 굵기 규격을 "16px 표시에서 1.33px" 로 다시 정하고 획 기반 아이콘 123개를 전부 맞췄습니다. 굵기가 5가지로 갈려 있었어요 — 1.00px(88개) · 1.12px(3) · 1.20px(7) · 1.33px(24) · 1.50px(1). 원인은 하나입니다: Heroicons v1 outline 을 그대로 붙여넣은 것(그쪽 기본이 stroke-width="2", 우리 규격은 1.5였음). ic-refresh 는 path 까지 원본과 바이트 단위로 같았습니다
  • 왜 "적은 숫자" 가 아니라 "16px 환산" 이 기준인가 — 아이콘은 대부분 w-4(16px)로 쓰는데 굵기는 viewBox 좌표계 값이라, 적은 숫자와 보이는 굵기가 다릅니다. 실제 굵기는 stroke-width ÷ viewBox 가 정해요. 옛 규격(24÷1.5)은 24px 표시를 전제한 값이라 실사용 16px 에서는 1.0px 헤어라인으로 렌더되고 있었습니다. 새 규격 24 viewBox · stroke 2(= 16px 에서 1.33px)는 Heroicons·Lucide 와 같은 값이고, 이미 24개가 여기 있었습니다. viewBox 가 24 가 아니면 viewBox ÷ 12 로 환산합니다(16 → 1.3333, 32 → 2.6667) → 스타일 규칙
  • ic-close 는 24 좌표계로 되돌리고 글리프만 키웠습니다. 이 아이콘만 16 좌표계 특례였는데, 그러면 굵기 규격에서 혼자 벗어납니다. 24 박스 안에서 X 가 박스의 50% 밖에 안 차지해 작아 보이던 게 진짜 원인이었어요 — 62.5%(15 유닛)로 키워 16px 에서 X 폭 8px → 10px, 굵기는 새 표준 1.33px 입니다. X 를 쓰는 481곳이 여기 해당합니다
  • 적용은 stroke-width 숫자만 바꿨습니다(ic-close 제외) — path 좌표를 건드리지 않아 도형이 깨질 위험이 없습니다. ic-spinner 는 굵은 링이 목적이라 유일한 예외로 남겼습니다(2.67px)
  • ic-badge-check 도형이 찌그러져 있었습니다. path 끝의 호(arc) 4개가 통째로 빠져 도형이 닫히지 않았고, 시작점까지 대각선으로 뚝 끊겨 이어졌습니다 — 씰(seal) 한쪽이 잘려나간 모양이었어요. 2026-08-10 레지스트리 최초 등록 때부터 그랬습니다. 출처(Heroicons) 원본과 대조해 복원했고, 끝점 좌표 합이 (0,0)으로 닫히는지 계산으로 확인했습니다
  • docs 데모 5개가 아이콘을 손으로 베껴 쓰던 것을 레지스트리 직접 참조로 바꿨습니다DemoLnbSidebar · DemoWorkspaceSwitcher · DemoButtons · DemoEmployeeTable · DemoDataTable. 사본이라 등록본이 바뀌어도 문서만 옛 그림으로 남아 실제로 벌어져 있었습니다: org 는 2×2 격자(실제는 조직도 글리프) · 총무의 ic-clipboard 는 빈 사각형+줄 3개(실제는 클립+체크리스트) · 「수정」은 획 2(실제 1.5) · 「삭제」는 ic-trash 와 아예 다른 모양

재발 방지

  • 데모는 이제 이름만 적습니다getIcon('ic-…') 로 읽으므로 등록본이 바뀌면 문서가 자동으로 따라옵니다. 새 데모에서도 SVG 를 손으로 그리지 마세요(DemoLnbSidebar 에는 "icons copied from icons.ts" 라는 사실과 다른 주석까지 붙어 있어 드리프트를 더 늦게 발견했습니다)
  • 예외는 둘뿐입니다 — ① 브랜드/BI 자산: 그라디언트 id 가 문서 전역이라 한 페이지에 둘이 들어가면 충돌하므로 데모 전용 id 를 쓴 사본을 유지합니다 ② :d 런타임 바인딩(표 정렬 화살표): 레지스트리 대상이 아닙니다

남은 것

  • fill 이 없는 아이콘이 아직 100여 개 있습니다. 이 중 화살표·chevron·정렬선처럼 면이 없는 것 22개는 대상이 아니고(채워도 그릴 게 없습니다), 나머지가 후보입니다
  • 일괄 자동 생성은 안 됩니다 — 시도해 보고 접었습니다. 획을 면으로 바꾸는 걸 fill-rule="evenodd" 로 자동화해 17개를 돌렸더니 5개만 쓸 만했어요. ic-clipboard-check·ic-calendar-check 는 조각만 남고, ic-approval 은 검은 사각형, ic-search 는 손잡이가 사라진 원이 됐습니다 — 내부 디테일이 별도 <path> 인 아이콘은 하나로 합치는 순간 형태가 무너집니다
  • 그래서 나머지는 한 개씩 손으로 그려야 합니다. 서두르면 결함이 그대로 들어갑니다 — 이번에 발견된 ic-badge-check(도형이 안 닫힘)와 ic-info-circle-fill(눌린 i)이 정확히 그렇게 들어온 것들이었어요
  • 자동 생성분에는 크기 보정이 필수입니다. 획의 중심선을 쓰기 때문에 일관되게 0.89~0.91x 로 작게 나옵니다(좌우로 획 절반씩 손실). 이번 7종도 viewBox 를 계산해 1.00x 로 맞췄습니다
  • packages/static/icons/*.svg 파생본 99개pnpm icons:build 로 함께 갱신했습니다 — 등록본을 고칠 때 이 명령을 빼먹으면 pnpm icons:check 가 잡습니다
  • 채움(fill) 전용 38개는 손대지 않았습니다 — 획이 없어 굵기 축 밖입니다. 다만 이들도 Heroicons v1 solid 계열로 보여, 나중에 아웃라인과 나란히 놓았을 때 무게감이 맞는지는 따로 볼 여지가 있습니다

2026-08-13 — 로딩 스피너 문서 정본화 + 확장 축 개방 (design/update-loading-components)

추가

  • Spinner 페이지를 신설했습니다 — 스피너는 이미 4곳에서 돌고 있었지만 규칙이 코드 주석에만 있어, "언제 링이고 언제 아이콘인가"를 판단할 근거가 문서에 없었습니다
  • 종류는 두 개뿐이고, 자리가 정합니다 — 영역이 아직 아무것도 못 그리면 div 링 .ds-spinner, 컨트롤 하나만 바쁘면 SVG 아이콘 ic-spinner + animate-spin. 판단이 애매하면 "이 스피너를 지우면 그 자리에 뭐가 보이나" 를 물어보세요 — 아무것도 안 보이면 링입니다
  • 영역 로딩이 아이콘이 아니라 div 링인 이유(되돌리지 마세요): SVG transform 애니메이션은 메인 스레드가 막히면 합성 레이어로 못 올라가 프레임이 튑니다. 영역 로딩은 정의상 메인 스레드가 바쁜 순간이라 정확히 그때 끊깁니다 — 시트 로딩에서 "뚝뚝 끊김"으로 실제 리포트된 현상이에요. div 테두리 회전 + will-change 는 GPU 합성이 안정적입니다
  • 확장 축 5개를 변수로 열었습니다--ds-spinner-size(2rem) · --ds-spinner-thickness(3px) · --ds-spinner-track(neutral-200) · --ds-spinner-indicator(orange-500) · --ds-spinner-badge-size(4rem). 기본값이 종전과 같아 화면 변화는 없습니다. 새 종류는 .ds-spinner--<이름> 으로 변수만 덮습니다width/border/animation 을 다시 선언하면 GPU 합성과 reduced-motion 대응을 새 종류가 못 물려받아요 → 확장 절차
  • 스피너가 아닌 로딩 표시도 같은 페이지에 지도로 정리했습니다 — 진행률을 아는 경우(N/M)는 스피너가 아니라 카운트 배너(시트 「추가 행 로딩 중… 500 / 1000」), 패널 안 짧은 목록은 텍스트 한 줄. 스켈레톤과 진행률 바는 DS에 없습니다스피너가 아닌 것

수정

  • 인라인 style 로 복제돼 있던 링 1곳을 공용 클래스로 되돌렸습니다 — 시트 [설정 완료] 처리 중 화면(setup-complete.html). 겉보기는 같았지만 사본이라 ① --ds-spinner-* 변수를 안 따라오고 ② prefers-reduced-motion 에서 혼자만 1s 로 빠르게 돌고 있었습니다(다른 스피너는 2.4s). 모달 · 화이트 딤 문서의 예제 코드도 같은 복제본이라 함께 고쳤습니다 — 문서가 잘못된 패턴을 퍼뜨리고 있던 셈입니다
  • 흰 딤 위에서는 받침(.ds-spinner-badge)을 씌우지 않습니다 — 흰 바탕에 흰 원이 겹쳐 테두리만 보입니다. 받침은 링이 콘텐츠 위에 겹쳐 뜰 때만

재발 방지

  • pnpm check:spinners 를 신설했습니다 (pre-commit 차단). 회전(animate-spin)과 원형 테두리를 한 줄에서 조합한 손으로 그린 링추가된 줄만 보고 막습니다. 이번에 정리한 인라인 style 형뿐 아니라 Tailwind 유틸 조합 형(border-2 border-orange-500 border-t-transparent rounded-full animate-spin)도 같이 잡습니다 — 두 철자가 실제로 존재했습니다. 아이콘 스피너(ds-ico/$icon( 가 같은 줄)는 정상 사용법이라 대상이 아니고, 로딩이 아닌 회전 원은 ds-spinner-exempt: <사유> 로 통과시킵니다. 판정기는 7개 케이스(차단 2 · 통과 5)로 확인했습니다
  • 다섯 축을 @property { inherits: false } 로 등록했습니다. CSS 변수는 기본이 상속이라, 패널 하나에 --ds-spinner-size 를 걸면 그 안에 든 부팅·임베드 스피너까지 따라 줄어듭니다. 이제 조상에 건 값은 내려가지 않고 변형 클래스나 요소 자신의 선언만 먹습니다 — 의도한 확장 방식 그대로입니다. @property 를 모르는 브라우저는 블록을 무시할 뿐 렌더는 동일합니다

남은 것

  • ic-spinner 를 영역 로딩에 쓰는 자리 11곳 — admin 스케줄러 4 · team-manager 3 · inventory · general-affairs · module-manager 대시보드 · system-admin/loading.html(부팅 화면인데 아이콘을 씀)
  • 손으로 그린 링 3곳employee-app/modals/work-settings.html · work-type-change.html · team-manager/dashboard.html. 두께 2px 라 공용 링(3px)과 미세하게 다르고 reduced-motion 에서 혼자 빠르게 돕니다
  • 둘 다 마이그레이션 대상이지 위반이 아닙니다 — 일괄 스윕하지 말고 그 파일을 고칠 때 함께 바꾸세요. 전 앱이 design-system.css 를 로드하므로 치환은 <span class="ds-spinner"></span> 한 줄입니다
  • 복제본 없음 확인 — 앱 main.css 어디에도 .ds-spinner 재정의가 없어 정본만 고치면 전 앱에 반영됩니다(모달·페이지네이션 같은 copy trap 이 없는 드문 경우)

2026-08-10 — 처리 중(로딩) 전용 화이트 딤 백드롭 (design/sheet-setup-usage-mode)

추가

  • 백드롭에 --white 변형을 넣었습니다.ds-modal-backdrop.ds-modal-backdrop--white, 딤 색만 var(--ds-glass-70) 로 바뀌고 나머지(fixed inset-0 · 가운데 정렬 · z · 스크롤 잠금)는 기본과 같습니다 → 화이트 딤
  • 어두운 딤과 역할이 다릅니다. 어두운 딤은 "화면을 덮었으니 저기에 집중하라"는 신호지만, 흰 딤은 "같은 화면이 잠깐 멈춤" 으로 읽힙니다. 화면 전환 없이 몇 초 잠그고 스피너 + 한 줄 안내만 얹는 자리에 씁니다. 사용자 확인을 받는 다이얼로그에는 쓰지 않습니다 — 흰 딤 위에 선택 버튼을 얹으면 멈춤 신호가 선택 화면으로 바뀝니다
  • 선택자를 더블 클래스로 둔 이유: 앱 main.css 들이 .ds-modal-backdrop 단일 클래스 복사본을 갖고 있어, 특이도가 낮으면 앱에서 조용히 덮입니다. 더블 클래스(0,2,0)는 !important 없이 항상 이깁니다
  • 첫 사용처는 급여 시트 [설정 완료] 처리 중입니다(UP-008 설정/사용 모드) — 확정 카드를 숨기고 같은 백드롭 안에서 흰 딤 + 스피너로 교체합니다

2026-08-07 — 컨펌 다이얼로그가 열릴 때 확인 버튼에 포커스 (design/sheet-uiux-fx)

변경

  • 다이얼로그(알림·확인·선택형)가 열리면 확인 버튼이 포커스를 가집니다 — Enter 가 곧 확인이 되고(네이티브 confirm() 관례), Tab 으로 취소/대안 버튼에 옮겨 Enter·Space 로 누를 수 있습니다. prompt 는 지금처럼 입력칸이 포커스를 가집니다 → 키보드
  • 배경: 포커스가 다이얼로그를 연 쪽 입력창(급여 시트 수식바)에 남아, 수식을 Enter 로 확정한 뒤 범위 선택 다이얼로그에서 Enter 를 또 쳐도 아무 일도 일어나지 않았습니다("Enter 를 쳐도 수식 작성이 안 끝난다" 리포트). 이제 Enter 두 번이면 수식 확정 → 범위 확정까지 끝납니다

2026-08-06 — 채팅·AI FAB 비킴 변수 --ds-fab-dodge (design/sheet-uiux-fx)

추가

  • 오른쪽 드로어가 열릴 때 FAB 를 왼쪽으로 비켜 세우는 계약을 넣었습니다 — 채팅/AI FAB 와 패널의 rightcalc(<기본값> + var(--ds-fab-dodge, 0px)) 로 바뀌었습니다. 변수 기본값이 0px 라 세우지 않는 앱은 아무 변화가 없습니다FAB 배치 계약
  • 첫 사용처는 급여 시트 워크북입니다: 오른쪽 레일 드로어(셀 상세·메모 / 열 설정 / 급여 반영 / 행 정보, 320px)가 열리면 body.sheet-rail-open 이 320px 를 세우고, admin 임베드에서는 hereby:tm:rail postMessage 로 부모 문서의 FAB 도 함께 밀립니다
  • 배경: FAB 는 position:fixed 라 드로어가 그리드를 밀어내도 그대로 드로어 위에 얹혀 하단 버튼(드로어 푸터)을 가렸습니다. 드로어 푸터 버튼들이 좌측 정렬로 도망가 있던 이유가 이것이었고, 비킴 도입으로 취소=왼쪽·저장=오른쪽 정렬이 가능해졌습니다

2026-08-05 — 컨펌 다이얼로그에 선택형(askChoice) 추가 (design/sheet-uiux-fx)

추가

  • 선택지가 둘인 확인 askChoice 를 넣었습니다"confirm"(오른쪽) · "alt"(가운데) · "cancel"(Esc). 물음이 「할까/말까」가 아니라 「어느 쪽이냐」 일 때 씁니다. 첫 사용처는 급여 시트에서 셀에 넣은 수식의 적용 범위(「이 셀만」 / 「전체 열에 적용」)입니다 → 선택형
  • 네 번째 모달 종류가 아닙니다 — 카드·제목·여백·타이포는 컨펌 다이얼로그와 완전히 같고 푸터 버튼만 갈립니다. 모달 3종 분류는 그대로입니다
  • 취소 버튼을 두지 않았습니다. 선택지 둘만 세우고 되돌아 나가는 길은 Esc 가 집니다. 셋째 버튼을 두면 무엇이 기본인지 흐려지고, 두 선택지 중 하나를 「취소」 자리에 앉히면 사용자가 무엇을 고른 건지 흐려집니다
  • 호스트가 없으면 "cancel" 입니다. askConfirm 은 같은 상황에서 통과(true)시키지만 — 띄울 수 없는 확인창이 동작을 조용히 취소하면 안 되므로 — 선택형에는 통과시킬 기본값이 없습니다. 임의로 고르면 사용자가 고르지 않은 쪽이 실행됩니다

2026-08-04 — 급여 시트에도 가로 스크롤바 + 썸을 반투명으로 (design/sheet-uiux-fx)

변경

  • 가로 스크롤바 제외 목록에서 급여 시트를 뺐습니다 — 이제 .sched-grid(근무표)만 제외입니다. 시트야말로 열이 수십 개라 가로로 넘치는 대표 화면이고, 이 기능을 만든 이유("윈도우에는 표를 가로로 스크롤할 입력 수단이 사실상 없다")가 가장 아프게 걸리는 곳이었습니다 → 가로 스크롤바
  • 시트에는 아래쪽 바만 붙입니다. 시트의 스크롤 박스는 flex-1로 화면 높이에 맞춰져 있어 아래 바가 항상 보이고, 위 바는 sticky 열 헤더를 10px 가려 스프레드시트에서는 손해가 큽니다. 마크업 4곳에 data-ds-xscroll="bottom"을 뿌리는 대신 table-xscroll.tsBOTTOM_ONLY_SELECTOR 한 곳에서 정합니다
  • 시트에서는 바가 표를 덮지 않고 자기 자리(10px)를 차지합니다 — 새 변형 .ds-xscroll--inflow(래퍼에 data-ds-xscroll-inflow, .sheet-scroll은 기본 적용). 다른 표는 아래에 여백이 있어 겹쳐도 되지만, 시트는 스크롤 박스가 화면 끝까지 차서 맨 아래가 데이터 행(또는 sticky 합계 행) 이라 겹치면 그 줄이 잘려 보였습니다. 흐름 안에 두면 flex-1인 박스가 그만큼 줄고 시트가 바 위쪽에 온전히 들어옵니다
  • 표와 바 사이에 분리선(border-top: 1px solid var(--color-line-normal))을 넣었습니다 — 시트 툴바·오른쪽 레일과 같은 선입니다. 겹치는 바에는 넣지 않습니다(표 위에 얹혀 있어 행 사이를 가로지르는 줄로 읽힘). border-box라 테두리가 트랙 안쪽을 먹으므로 바 높이만 10 → 11px로 키워 트랙은 그대로 10px입니다
  • 썸을 평상시 반투명(--ds-opacity-50)으로 낮췄습니다 — 이 바는 표 위에 상시 겹쳐 그려지므로, 진하면 데이터를 읽는 내내 눈에 걸립니다. hover·드래그 중에는 neutral-400 + 불투명으로 또렷해져 조작 피드백은 그대로입니다
  • 호스트 단위 제외 상수(EXCLUDED_HOST_SELECTOR)는 없앴습니다 — 끄는 방법은 data-ds-xscroll="off" 하나로 통일합니다

2026-07-31 — 파비콘 캐시 무효화 + 문서 사이트 파비콘은 마스터 심볼 (design/BI-update)

수정

  • 파비콘을 fill본으로 바꿨는데 탭에는 예전 아이콘이 그대로 남았습니다. 브라우저가 파비콘을 URL 기준으로 따로 캐시하기 때문이고, 강력 새로고침으로도 잘 지워지지 않습니다. 10개 앱의 href?v=2 를 붙여 다시 받게 했습니다 — 앞으로 파비콘 파일을 바꾸면 이 값을 같이 올려야 합니다(값이 갈리면 앱마다 다른 시점의 아이콘이 남습니다)
  • 서빙되는 파일 자체는 정상이었습니다 — 16px로 렌더해 십자가 흰색으로 채워지는 것을 확인했습니다. 파일이 아니라 캐시가 원인이었다는 뜻입니다

추가

  • 문서 사이트(docs)에 파비콘을 붙였습니다 — 마스터 심볼(스우시)입니다. 종전에는 파비콘이 아예 없어 브라우저 기본 아이콘이 떴습니다. 이 사이트의 주체는 특정 패밀리가 아니라 hereby 제품군 전체라 십자·사람·펜을 쓰지 않습니다
  • 자산은 예외적으로 docs/public/favicon.svg에 둡니다 — 문서 사이트는 @hereby/static을 소비하지 않아 /static 미들웨어가 없고 VitePress의 public/이 유일한 경로입니다. href에는 base를 붙였습니다(버전 문서는 /1.2.3/ 하위 경로라 루트 절대경로면 404)

2026-07-31 — BI 심볼 내부를 fill/outline 두 케이스로 등록 + 전 사용처 fill 전환 (design/BI-update)

추가

  • 심볼을 그리는 방식을 둘로 등록하고 기본형을 fill로 정했습니다fill(그라디언트 면 + 안쪽 흰색) · outline(그라디언트 없이 워드마크 색을 그대로 따르는 단색, 안쪽은 채우지 않음). outline선(stroke)이 아닙니다 — 면은 그대로이고 색이 단색으로 바뀌는 것입니다 → 심볼을 그리는 두 방식
  • 심볼만이든 락업이든 기본은 fill입니다. 다크 배경 락업에서 워드는 light로 뒤집히는데 심볼의 십자만 검게 남던 것이 이 규칙을 못 박은 계기입니다 — 워드마크에는 톤 규칙이 있어도 심볼 안쪽에는 규칙이 없었습니다기본형
  • outline은 브랜드 컬러를 쓰지 않습니다 — 오렌지 단색도 그라디언트도 아니고, 워드마크와 같은 색(밝은 배경 #1B1B1B · 어두운 배경 흰색) 하나입니다. 내보내기에는 linearGradient가 아예 들어가지 않습니다
  • outline은 컬러 없는 워드마크와만 조합합니다 — 서비스명 톤 dark·light만 되고 primary(오렌지)와는 금지입니다. 심볼이 워드마크 색을 따라가므로 워드에 컬러가 있으면 심볼까지 그 컬러가 되어 정의 자체가 깨집니다조합 규칙
  • outline을 자리별로 규정하지 않았습니다 — 「어느 화면은 outline」 같은 규칙은 두지 않습니다. 기본은 언제나 fill이고 outline은 데모에서 받아 쓰는 케이스입니다
  • 데모에 「심볼 내부」 선택기를 넣었습니다(기본값 fill, 심볼을 끄면 사라지고 마스터엔 없습니다). outline을 고르면 primary 버튼이 잠기고, primary 상태에서 outline을 고르면 그 배경의 중립 톤으로 물러납니다 — 배경 × 톤 금지 조합과 같은 처리입니다
  • 내보내기 파일명 맨 뒤에 축이 붙습니다 — hereby-bi-medical-en-dark-on-white-fill.svg · hereby-bi-medical-symbol-fill.svg. 기본형도 생략하지 않습니다: 생략하면 예전에 받아 둔 outline 파일과 같은 이름이 되어 조용히 덮어써집니다. outline은 심볼만이어도 배경이 붙습니다(…-symbol-outline-on-white.svg) — 색이 배경에서 갈리기 때문입니다

변경

  • 심볼이 박힌 자리를 전부 fill본으로 교체했습니다(12곳)icons.ts의 심볼 3(logo-hereby-medical·-sign·-works)과 락업 2종 안의 심볼, 파비콘 2(9개 앱이 참조), 랜딩 인라인 3(nav.html·footer.html·데모의 Sign), docs 데모 2(DemoLnbSidebar·DemoWorkspaceSwitcher). PNG 앱 아이콘은 유지합니다
  • fill본은 새로 그리지 않고 등록된 정본에서 파생합니다컨테이너 + 컨테이너 ⊕ outline 원본(fill-rule="evenodd" 대칭차 = 정확히 안쪽 도형) 두 줄입니다. 디자인 재요청이 없고, 원본이 바뀌면 fill본이 따라옵니다. clipPath·mask는 쓰지 않습니다(자산은 어디서 열어도 같아야 합니다) → 파생식
  • 컨테이너를 얻는 방법이 심볼마다 다릅니다 — Medical은 첫 서브패스가 이미 라운드 사각형, Sign은 펜이 외곽선에 통째로 파여 서브패스가 1개라 상자가 같은 Medical 컨테이너를 재사용, Works는 몸통도 외곽선에 파여 있어 그 구간만 Medical 컨테이너 하단으로 되돌립니다
  • Works의 상단 노치는 fill본에서도 비어 있습니다 — 노치는 안쪽 도형이 아니라 컨테이너 실루엣의 일부라, 흰색이 되는 것은 사람(머리+몸통)뿐입니다. 위 파생식이 이것까지 자동으로 지킵니다
  • 마스터엔 이 축이 없습니다 — 스우시 하나라 컨테이너도 안쪽 도형도 없습니다(뚫린 데가 없으니 채울 것도 없습니다). 마스터 데모에는 선택기가 나타나지 않습니다

2026-07-29 — 라벨에 굵기 축 추가 + 채팅 위젯 미확인 배지 (design/review-and-apply-improvements)

추가

  • 라벨 굵기를 크기와 분리했습니다.ds-fw-regular/medium/semibold/bold 하나를 얹으면 크기는 그대로 두고 굵기만 바뀝니다(기본 medium). 버튼이 쓰던 모디파이어를 그대로 공유하고, 각 크기 변형이 읽는 --ds-label-weight 변수만 세팅하므로 !important는 0입니다 → 라벨 → 굵기
  • 라이브 미리보기에 굵기 토글을 넣어 4단계를 실제 CSS로 비교할 수 있게 했습니다

변경

  • 채팅 위젯(FAB)의 반투명을 걷어냈습니다. 닫힌 상태가 bg-orange-500/50 + text-white/80 이라 시트 위에 띄우면 뒤 숫자가 그대로 비쳐 버튼인지 얼룩인지 구분이 안 됐습니다 — 기본은 불투명 orange-400 + 흰 아이콘, hover 에서 primary(orange-500) 로 한 단계 진해집니다. 상시 노출되는 버튼이라 기본은 primary 보다 한 톤 낮추고 강조는 hover 가 집니다
  • 미확인 배지를 DS 라벨로 교체했습니다 — 손으로 조합한 min-w-4 h-4 rounded-full bg-red-500 text-[9px] 대신 .ds-label--xs --round --solid --red + .ds-fw-bold. 세는 대상은 확인 안 한 대화(채널 포함) + 공지 합계이고, 99 초과는 +99 로 줄입니다(예전 99+)

수정

  • admin 의 .admin-app .ds-label { font-weight: 500 } 이 굵기 축을 조용히 삼켰습니다. 특이도(0,2,0)가 크기 변형(0,1,0)보다 높아 admin 안에서만 .ds-fw-bold 가 무시됐습니다 — var(--ds-label-weight, 500) 으로 바꿔 admin 기본값(medium)은 유지하면서 축이 살아 있게 했습니다. 앱 override 가 변수를 안 읽으면 그 앱에서만 DS 축이 죽는다는 사례입니다

2026-07-29 — 정보성 영역은 notice 사용 강제 (design/review-and-apply-improvements)

금지 (재유입 차단)

  • 안내·주의·경고·오류를 알리는 박스는 반드시 notice + 변형으로 만듭니다. 새 기능이든 기존 화면 수정이든 같습니다 — bg-red-50 border border-red-200 text-red-600 같은 유틸 조합 틴트 박스를 새로 만들지 않습니다. 제각각인 크기·간격·색을 한 규격으로 모으는 것이 이 컴포넌트의 존재 이유입니다
  • 이모지로 아이콘을 대신하지 않습니다⚠️· 대신 <svg class="ds-notice__icon">. 이모지는 OS·폰트마다 크기·색이 달라 변형 색과 어긋나고 16×16 규격도 못 맞춥니다
  • notice가 아닌 것을 함께 적었습니다 — 상태 배지(.ds-label) · 흐름을 막는 확인창(모달) · 표 행 하이라이트 · 박스 없는 한 줄 검증 문구
  • 레거시는 마이그레이션 대상이지 위반이 아닙니다. 일괄 스윕하지 않고 고치고 있는 파일 안에서만 바꿉니다(모달·버튼과 같은 정책). bg-*-50 조합 310곳이 잡히지만 상당수는 알림 박스가 아니라 배지·행 하이라이트입니다

변경

  • 로그인·계정 화면의 알림 박스 10곳을 notice로 전환했습니다 — 공용 signin-form(오류 3 · 인증코드 안내 1) · forgot-password-form(성공·안내·오류) · change-password-form(성공·오류) · signup-form(오류). 공용 컴포넌트라 8개 앱 로그인 화면에 한 번에 반영됩니다. 인증코드 발송 안내는 orange 조합이었는데 notice 변형에 orange가 없어 --info(yellow)로 갑니다
  • admin 의 계정 화면 오류 6곳 + 스케줄러 경고 목록을 notice 로 전환했습니다 — 워크스페이스 선택(3) · 회원가입 · 이메일 인증 · 초대 수락은 --danger, 스케줄 경고 모달의 항목은 --warning + 아이콘(⚠️ 이모지를 규격 아이콘으로 교체). 같은 화면의 team-manager 복사본은 아직 남아 있습니다
  • team-manager 의 안내 박스 6곳을 notice 로 전환했습니다 — 병동 설정의 조직 정책 안내·교육 정책 안내 2종(예전엔 orange/amber 로 색이 서로 달랐습니다) · 스케줄 경고 목록(⚠️ 이모지 → 규격 아이콘) · 치료 인센티브의 항목 미등록 경고. admin 에는 이미 적용돼 있고 team-manager 복사본만 옛 상태로 남아 있던 드리프트였습니다
  • 직원앱의 "승인 대기 중인 신청이 있어요"를 --warning, 모듈 매니저의 "API 연결 실패"를 --danger 바꿨습니다. 모듈 매니저 쪽은 박스 안에 있던 재시도 버튼을 notice 밖(아래)으로 뺐습니다 — notice 는 액션 슬롯이 없으니 버튼은 밖에 둡니다
  • hr-system 은 급여 시트 셀 상세 패널의 "발송 완료" 한 곳만 --success 로 바꿨습니다. 나머지 후보는 열어보니 성격이 달랐습니다 — 호봉/급여 미리보기는 값 카드, 엑셀 가져오기 결과는 버튼이 달린 결과 카드(코드 주석에 "not a plain ds-notice"로 이미 명시), 치료 유형 문구는 읽기 전용 필드의 보조 텍스트, "보호된 행이 없습니다"는 빈 상태입니다. 버튼을 품은 결과 카드는 DS 에 정의가 없습니다 — notice 는 아이콘+문구 전용이라 액션 슬롯이 없어서, 결과 카드 패턴 정의는 백로그로 둡니다
  • .ds-modal__desc 는 정본에 없습니다 — admin·hr-system main.css 두 곳에만 있어서, team-manager 에서 쓰면 스타일이 통째로 빠져 전역 p { 16px } 로 렌더됩니다(조용한 깨짐). 두 복사본은 margin 값도 어긋나 있습니다. 근무 불일치 보정 모달의 설명문은 그래서 이 클래스 대신 text-b4 text-label-assistive 로 넣었고, 함정은 .claude/rules/frontend/design-system-architecture.md 의 복제 표에 기록했습니다
  • 모두가 안내 박스인 건 아닙니다 — 자격 유형 폼의 "자격유형 · 기본 · 삭제 불가" 카드와 희망근무 상세의 날짜 목록은 색 틴트를 쓰지만 목록 항목이라 notice 로 바꾸지 않았습니다. 색이 들어간 박스라고 전부 notice 가 아니라, 흐름을 설명·경고하는 박스만 notice 입니다
  • module-manager(4010)가 design-system.css 를 아예 로드하지 않고 있었습니다main.css@import "tailwindcss" 뿐이라, 이 앱만 공용 로그인 폼의 .ds-btn 이 무스타일로 렌더되고 있었습니다(기존 결함). notice 전환 시 투명 박스가 될 자리라 정본 @import 한 줄을 추가해 다른 8개 앱과 같은 로드 체인으로 맞췄습니다
  • 연차 정책의 "5인 미만 사업장" 경고를 --danger + 아이콘으로 전환했습니다(admin 유형·기준 관리). 손으로 조합한 red 박스(bg-red-50 + text-b5 font-semibold text-red-600 + ⚠️ 이모지)를 DS 규격(패딩 10/12 · radius-4 · 12px · 아이콘 red-500)으로 맞췄고, 본문은 <span class="flex-1"> 로 감쌌습니다 — admin의 unlayered p { font-size: 16px } 가 notice 의 12px를 이기기 때문에 notice 안에서는 <p> 를 쓰지 않습니다

2026-07-29 — 신규 모달은 무조건 DS 3종 (design/modal-apply-feedback-DS)

금지 (재유입 차단)

  • 신규 기능에서 모달을 만들 때 DS 3종을 벗어나면 커밋이 막힙니다pnpm check:modals(pre-commit blocking). 잡는 것: 백드롭 직접 조합(fixed inset-0 + 배경) · bg-black/*·bg-[var(--ds-dim…)] · 새 .ds-mini-modal · 모달 카드에 직접 준 패딩 · .ds-modal__title 에 얹은 색
  • 추가된 줄만 봅니다. 레거시가 72곳(백드롭 25 · 딤 24 · mini-modal 17 · 패딩 6) 있어 전체를 막으면 아무 커밋도 못 합니다 — 목적은 과거 청산이 아니라 새 위반 차단입니다. 현황은 pnpm check:modals:audit
  • 정당한 예외는 사유를 적어 통과시킵니다 — ds-modal-exempt: <사유>(같은 줄 또는 윗줄). 컨텍스트 메뉴·피커·라이트박스·드로어 딤처럼 문서가 인정하는 예외용이고, 사유 없는 예외는 두지 않습니다
  • 문서에 새 모달을 만들 때 절을 만들어 3종 복붙 시작점과 막히는 목록을 함께 뒀습니다

변경

  • 컨펌 팝업을 특징별로 나누지 않습니다. 파괴적(삭제) 확인도 카드·제목·여백·타이포가 완전히 같고, 차이는 푸터 버튼이 집니다 — tone: "danger" 는 이제 확인 버튼 색만 바꿉니다(제목 red 제거). 같은 컴포넌트가 두 종류처럼 보이던 것을 없앴습니다

수정

  • 문서 데모의 다이얼로그 상단 여백이 실제와 달랐습니다 — VitePress 의 .vp-doc h3(margin-top:32px, 특이도 0,1,1)가 .ds-modal__title(0,1,0)의 margin:0 을 이겨서 카드 위 여백이 28 이 아니라 60 으로 보였습니다. 실제 앱은 28 이었으니 문서 쪽만 어긋난 드리프트입니다. ds-tailwind-shim.css 에서 모달 카드 안 헤딩의 본문 크롬을 무력화해 고쳤고(특이도 0,2,1), 새 모달 데모에도 자동 적용됩니다

2026-07-29 — 모달 3종 확정 + 컨펌 다이얼로그 여백 28 · 타이틀 「확인」 (design/modal-apply-feedback-DS)

변경

  • 모달의 종류를 셋으로 확정하고 이름을 붙였습니다컨펌 다이얼로그(confirmation dialog, .ds-dialog) · 일반 모달(modal, .ds-modal) · 패널 모달(panel modal, .ds-modal--panel). 셋은 백드롭·닫기·스크롤잠금·카드 내부 구조가 전부 같고 바깥 여백과 크기만 다릅니다. 네 번째 종류는 만들지 않습니다 → 모달은 3종입니다
  • 컨펌 다이얼로그 여백을 상하좌우 28로 일괄 조정했습니다(일반 모달 상44/좌우40/하36). 문장 한두 줄 + 버튼뿐인 카드에 40을 주면 내용 대비 여백이 과해 카드가 비어 보였습니다. 안쪽 간격(타이틀↔본문 24 · 본문↔푸터 32)과 타이포는 일반 모달과 그대로 동일합니다 — 달라진 건 바깥 여백 하나뿐입니다
  • 여백 차이는 마크업이 아니라 CSS가 집니다.ds-dialog .ds-modal__* 가 값을 갖습니다. 선택자 특이도(0,2,0)가 앱 main.css 에 복제된 .ds-modal__header(0,1,0)보다 높아 소스 순서와 무관하게 이깁니다(복사본은 admin·hr-system·management-dashboard 세 곳)
  • 컨펌 다이얼로그 타이틀을 on/off 축으로 만들고 기본값을 「확인」으로 통일했습니다. 예전엔 종류마다 기본 제목이 달랐습니다(alert 「안내」/「오류」, confirm 「확인」, prompt 「입력」) — 같은 컴포넌트인데 화면마다 제목이 갈려 서로 다른 것처럼 보였습니다. 이제 종류 불문 「확인」, 끄려면 title: ""/title: false(헤더가 통째로 빠지고 .ds-dialog--no-title 이 본문 위 여백을 대신 집니다), 바꾸려면 문자열
  • 실패 신호는 제목이 아니라 톤이 집니다showError 의 제목도 「확인」이고, tone: "danger" 가 제목을 red로, 확인 버튼을 lightor 로 바꿉니다
  • 타이틀을 비워 부르던 급여 시트 4곳(rows.ts)의 title: "" 를 지워 기본 타이틀 「확인」이 적용되게 했습니다

메모

  • .ds-mini-modal(저장확인·위저드 확인)은 이 3종 밖의 레거시입니다. 역할은 컨펌 다이얼로그와 같은데 규격이 다릅니다(패딩 24 · gap 32 · 타이틀 14) — .ds-dialog 로 흡수하는 것이 목표이고 새 코드에서는 쓰지 않습니다. 일괄 스윕은 하지 않았습니다

2026-07-29 — 네이티브 얼럿 폐지 + 확인/알림 다이얼로그를 폼 모달 규격으로 (design/modal-apply-feedback-DS)

추가

  • 공용 다이얼로그를 하나 만들었습니다packages/components/src/dialog.ts + html/ds-dialog.html. askConfirm / showAlert / showError / showPrompt 네 함수로 confirm() / alert() / prompt() 를 전부 대신합니다. 문서: 모달 → 확인·알림·입력 다이얼로그
  • 등록은 앱이 아니라 플러그인이 합니다createAuthComponentsPlugin()Alpine.store("dialog", …) 를 대신 걸어, 앱이 빠뜨리면 조용히 no-op(alert)·무조건 통과(confirm)로 떨어지는 사고를 구조적으로 막습니다

변경

  • 다이얼로그 타이포·간격을 폼 모달과 동일하게 맞췄습니다. 예전 .ds-mini-modal(패딩 24 · gap 32 · 타이틀 14/600 · 본문 13)은 같은 화면의 폼 모달(44/40/36 · 타이틀 20/600 · 본문 14)과 눈에 띄게 달랐습니다. 이제 다이얼로그가 .ds-modal__header/__title/__body/__footer 를 그대로 씁니다 — 수치를 복제하지 않으므로 폼 모달이 바뀌면 다이얼로그도 따라갑니다. 타이틀↔본문 24px 는 헤더의 padding-bottom 이 만듭니다
  • 네이티브 alert()/confirm()/prompt() 를 전부 걷어냈습니다 — 231곳(TS) + 13곳(HTML). 실패 경로는 showError(제목 「오류」 + red), 나머지는 showAlert(제목 「안내」)로 나눴고, confirm()await askConfirm() 으로 바꾸며 호출부를 async 로 승격했습니다. 파괴적 확인(삭제·해체·초기화·revoke)은 tone: "danger" → 확인 버튼 lightor
  • 중복 구현 3개를 공용 하나로 접었습니다 — hr-system 시트의 dialog 슬라이스 + dialog-bridge + input-prompt, employee-app 의 confirm 스토어, Messenger 의 자체 폴백. 슬라이스는 얇은 위임만 남기고(this.askConfirm(...) 170여 개 호출부 무변경) 상태·템플릿·브리지는 삭제했습니다

금지 (재유입 차단)

  • 네이티브 시스템 다이얼로그는 이제 금지입니다 — 예외 없음. 걷어내는 것만으로는 다시 들어옵니다. 문서 + 기계적 차단 두 겹으로 막았습니다
    • Biome lint/suspicious/noAlerterror 켰습니다 — .tsalert( · window.alert( · globalThis.prompt(pnpm lint:check 에서 잡습니다
    • pnpm check:dialogs(pre-commit blocking) 를 새로 뒀습니다 — Biome 이 못 잡는 캐스트형 (window as any).confirm( · 인덱스형 window["alert"]( · .html 전부를 담당합니다. 색 검사와 달리 || true 가 없어 걸리면 커밋이 막힙니다
    • 두 겹인 이유는 캐스트형이 실제로 9곳 새어 나갔기 때문입니다. 소스 grep 은 통과했고 빌드 산출물을 스캔해서야 발견했습니다(team-manager·admin 스케줄러). Biome 은 그 형태를 전역 참조로 보지 않습니다
    • 예외는 다이얼로그 자체 구현(dialog.ts · ds-dialog.html)뿐이며 스크립트 ALLOW 목록에 명시돼 있습니다

메모

  • showAlert 는 일부러 await 할 수 없습니다 — 대부분 catch 에서 부르는데 거기서 await 하면 뒤따르는 finally 정리가 사용자 클릭까지 멈춰 섭니다
  • e2e의 page.on("dialog", …) 는 이제 아무것도 잡지 못합니다. 조용한 no-op 이 되어 「얼럿이 안 떴다」 검증을 항상 통과시키는 가짜 초록을 만들었습니다 — 시트 스펙 5개를 새 헬퍼 helpers/ds-dialog.ts 로 옮겼습니다. 다이얼로그를 띄우는 동작은 사용자의 답을 기다리므로 await 하지 말고 먼저 시작해 둔 뒤 확인을 누르고 기다립니다
  • ds-dialog.html 루트의 x-data 는 지우면 안 됩니다. 이 파일은 앱 index.html 최상위(어떤 x-data 스코프에도 없는 자리)에 include 되는데, Alpine 은 x-data 루트에서만 트리를 초기화합니다. 없으면 x-cloak 이 남아 스토어는 열렸는데 화면엔 아무것도 안 뜨는 조용한 실패가 되고, 빌드·린트·타입체크는 전부 green 입니다 — 브라우저에서 실제로 띄워 봐야 잡힙니다
  • ds-dialog.html 주석에 include 지시문을 예시로 적으면 안 됩니다 — 빌드의 html-include 가 주석까지 훑어 자기 자신을 끼워 넣습니다(첫 빌드가 이걸로 깨졌습니다)
  • .ds-mini-modal 은 그대로 남습니다 — 저장확인(close-confirm)·위저드 확인 등 네이티브 대체가 아닌 작은 팝업이 계속 씁니다. 이번 정렬 범위는 「네이티브 다이얼로그 대체」 하나입니다

2026-07-29 — 상태 툴팁은 「상태 + 행동」 2줄로 (design/modal-apply-feedback-DS)

변경

  • hr-system 급여 시트의 반영 상태 칩 툴팁을 멀티라인 2줄로 바꿨습니다. 기존 문구는 「시트 값이 급여대장에 반영된 값과 다릅니다. 다시 반영하면 급여대장이 시트와 같아집니다」로, 다시 반영하는 방법이 문장 어디에도 없었습니다 — 상태만 알려 주고 행동은 사용자가 화면에서 찾아야 했습니다. 이제 1줄은 지금 상태, 2줄은 「오른쪽 「급여대장에 반영」 버튼을 누르면 …」처럼 누를 자리와 그 결과를 적습니다
  • 못 누르는 상태에서는 먼저 풀어야 할 것을 앞세웁니다 — 작업 완료·보호·대상 미지정이면 버튼이 비활성이라 「버튼을 누르세요」가 막다른 안내가 됩니다. 판정은 반영 버튼과 같은 commitBlockedReason 하나를 공유해 칩과 버튼의 안내가 어긋나지 않게 했습니다
  • 급여대장 화면에도 안내를 넣었습니다 — 여기는 집계 열뿐이라 상태 칩도, 「급여대장에 반영」 버튼도 아예 없고 「소스 전체 반영」만 있습니다. 숫자가 여기 보이니 고칠 곳도 여기라고 읽혀 「반영 버튼이 안 보인다」로 되돌아오던 자리라, 그 버튼 툴팁 1줄째에 값이 소스 시트에서 온다는 사실을 먼저 적고 2줄째에 미반영 소스 개수와 「변경된 시트만 반영」을 적습니다
  • 사이드바 상태 점을 색 있는 2단계로 바꿨습니다. 예전에는 미반영일 때만 점이 떠서, 점이 없는 상태가 「작업 완료」인지 「반영까지 끝난 작업 중」인지 구분되지 않았습니다. 이제 작업 완료가 아닌 시트는 전부 점을 달고 색이 반영 여부를 말합니다 — primary=반영 필요 · neutral-300=반영 완료. 라벨도 색의 짝을 읽어 줍니다(「작업 중 / 반영 필요」·「작업 중 / 반영 완료」)
  • 라벨은 상태 이름까지만, 방법은 적지 않습니다. 어떻게 반영하는지는 시트를 열면 툴바 칩과 반영 버튼이 이미 안내하므로 목록에서 같은 말을 두 번 하지 않습니다. 「편집 중」이라는 옛 라벨은 「작업 중」으로 정정했습니다: 시트 상태는 「완료 / 잠김 / 작업 중」 3종인데 「편집 중」은 그중 어느 것도 아니었고, 급여대장에서는 아무도 편집하지 않았는데 소스가 밀려 켜지므로 사실과도 달랐습니다
  • 반영 개념이 없는 시트는 둘째 줄을 뺍니다 — 반영 열도 없고 소스도 받지 않는 시트에 회색 점을 달고 「반영 완료」라고 쓰면 하지도 않은 반영을 했다고 말하게 됩니다. 목록 API가 commitState(needed/done/none)로 이 세 갈래를 내려 줍니다
  • 3줄째는 마지막으로 값을 고친 시각입니다(「3시간 전 수정」). 여기서 쓰는 값은 lastChangeAt(행·셀의 updatedAt 최댓값)이지 시트의 updatedAt 이 아닙니다 — 셀 편집 경로는 sheet_views 레코드를 건드리지 않아서, 시트 updatedAt 을 쓰면 방금 고친 시트가 「3일 전 수정」으로 보입니다(사이드바 목록에 이미 떠 있는 「N분 전」 텍스트가 그 값이라 같은 줄에서 두 시각이 어긋날 수 있습니다). 목록 API가 판정용으로 이미 뽑던 값이라 쿼리는 늘지 않았습니다

메모

  • 줄바꿈 말고는 아무것도 더 하지 않았습니다title\n이 생긴 것만으로 tooltip.ts가 상세 도움말 버블로 자동 전환합니다(data-ds-tip은 원래 있던 placement 지정 그대로, 새 클래스 0). 아래 2026-07-27 「멀티라인 툴팁 범위 확장」이 실제 문구 개선으로 이어진 첫 사례입니다
  • 문구는 마크업이 아니라 TS getter(commitStatusTooltip)에 둡니다. 상태 4갈래 × 2줄을 :title 삼항식에 넣으면 읽을 수 없고, 버튼 툴팁(commitButtonTooltip)과 같은 자리에 있어야 둘이 같이 관리됩니다

2026-07-27 — 툴팁 밝은 톤(light) 추가 (design/recheck-applied-feedback)

추가

  • 툴팁에 밝은 톤을 만들었습니다 — 지금까지 툴팁은 검은 알약 하나뿐이라, 흰 카드·패널 위에서는 필요 이상으로 무겁게 튀었습니다. 밝은 배경 + --color-label-alternative 글자 + --color-line-normal 1px 라인의 반대 톤을 하나 두었습니다
  • 켜는 법은 속성 하나입니다 — 대상에 data-ds-tip-tone="light". 조상에 걸어도 됩니다(closest로 판정) — 밝은 톤은 보통 트리거 하나가 아니라 패널·화면 단위 결정이라, 패널 루트에 한 번만 걸면 그 안의 툴팁이 전부 따라옵니다. 안쪽 마크업은 한 줄도 고치지 않습니다
  • 색 톤은 「세 번째 종류」가 아니라 세 번째 축입니다. 종류는 여전히 한 줄·멀티라인 2가지, placement도 4개 그대로이고, 톤은 그 둘과 직교합니다(멀티라인 밝은 compact-center 같은 조합이 그냥 됩니다). 톤 값은 light 하나뿐이며 늘리지 않습니다 — 기본이 어두움이라 dark가 필요 없고, 버블 스타일이 갈라지기 시작하면 되돌리기 어렵습니다

변경

  • 색을 변수 2개(--ds-tip-bg·--ds-tip-fg)로 모읍니다. 버블 배경과 4개 placement의 화살표가 같은 변수를 읽으므로, 톤 모디파이어는 변수만 덮으면 되고 placement별로 색을 다시 적지 않습니다. 예전처럼 화살표마다 색이 하드코딩돼 있으면 톤을 하나 추가할 때 3곳을 조용히 빠뜨립니다. 어두운 톤의 값은 그대로라 기존 화면의 시각 변화는 0입니다

메모

  • 밝은 톤에만 테두리가 있습니다 — 흰 버블은 흰 배경 위에서 경계를 잃기 때문입니다. 화살표에도 같은 라인을 그리는데, 45° 회전한 사각형이라 바깥을 향하는 두 변이 placement마다 다릅니다(아래 방향 bottom+right · 위 방향 top+left · 왼쪽 방향 left+bottom). 화살표 안쪽 절반이 버블 테두리 위에 겹쳐 그려지므로 화살표를 1px 바깥으로 밀어 두 선을 잇습니다
  • 그림자는 밝은 톤만 --ds-shadow-md로 바꿨습니다 — 어두운 톤의 25% 검정 그림자는 밝은 버블 아래에서 탁하게 번집니다. 어두운 톤의 그림자 2종은 매칭 토큰이 없어 하드코딩 유지라는 기존 결정 그대로입니다
  • 적용 화면은 아직 없습니다. 컴포넌트만 준비된 상태이고, 「밝은 패널 위에서 검은 알약이 과하다」고 판단된 화면부터 붙입니다

2026-07-27 — 멀티라인 툴팁 범위 확장 + 가로 위치 보정 (design/recheck-applied-feedback)

변경

  • 툴팁의 종류를 2가지로 정리했습니다 — 「한 줄」과 「멀티라인 상세 도움말」. 어디에 뜨는지(placement)는 별개의 축이고, 어떤 종류인지는 라벨에 줄바꿈이 있느냐 하나로 갈립니다
  • 멀티라인을 버튼과 여러 구간에서 쓸 수 있게 범위를 넓혔습니다. 그동안은 .icon-btn·select 같은 정해진 셀렉터에 걸리거나 data-ds-tip을 붙인 대상만 DS 툴팁이 떴고, 그 밖의 요소는 여러 줄 title을 줘도 브라우저 기본 툴팁이 떴습니다. 이제 줄바꿈이 곧 opt-in입니다 — 평범한 <button>, 표 셀, 배지, 구간 제목 어디든 여러 줄 title만 주면 상세 도움말 버블이 뜹니다(기본 위치는 대상 위 가운데). data-ds-tip은 위치를 바꾸고 싶을 때만 씁니다
  • 한 줄 툴팁의 규칙은 그대로입니다. 아무 요소에나 한 줄 title을 단다고 DS 툴팁이 되지는 않습니다 — 범위가 넓어진 것은 멀티라인뿐입니다
  • hr-system 급여 시트의 수식 힌트 ƒ마크업을 한 줄도 고치지 않았는데 자동으로 DS 멀티라인 툴팁이 됐습니다(원래 개행 title을 갖고 있었지만 opt-in이 없어 브라우저 기본 툴팁으로 떴습니다). 확장의 효과가 바로 보이는 사례입니다

추가

  • 가로 방향 위치 보정. 지금까지 flip은 위아래 전용이라, 화면 가장자리 대상에 320px 버블이 뜨면 잘렸습니다. 범위를 넓히면 가장자리 버튼이 흔해지므로 같이 넣었습니다 — 버블은 뷰포트 안으로 되돌리고(양옆 8px) 화살표만 대상 위에 남깁니다. 계산은 순수 함수(clampTooltipX · clampTooltipLeft)로 분리해 단위 테스트 12개로 고정했습니다

수정

  • 오른쪽 끝에서 버블이 스스로 좁아지며 세로로 길어지던 버그를 재현·수정했습니다. position: fixed + width: auto 는 shrink-to-fit 폭을 자기 left 기준 잔여 공간으로 계산합니다 — 화면 오른쪽 끝에 앵커되면 남은 공간이 좁으니 버블이 그만큼 찌그러졌습니다(297px → 157px로 재현). .ds-tooltipwidth: max-content 를 주어 폭을 위치와 분리했습니다. 가로 보정을 넣으며 실측하다 발견했고, 확장 이전에는 가장자리 사용 자체가 드물어 드러나지 않던 문제입니다
  • [data-placement="top"][data-flip="down"] 블록이 똑같이 두 번 선언돼 있어(뒤엣것만 적용되는 죽은 코드) 앞의 사본을 지웠습니다. 화살표 동작을 고치려다 죽은 쪽을 고치기 십상인 자리입니다

2026-07-27 — 툴팁 문서 정정: placement 4개 + 멀티라인 사용처 전수 (design/recheck-applied-feedback)

추가

  • 라이브 미리보기에서 멀티라인 툴팁을 직접 확인할 수 있습니다 — 기존 미리보기에는 top·right·compact 3개뿐이라, 방금 문서에 적은 멀티라인을 눈으로 볼 방법이 없었습니다. 새 줄에 ⓘ(top·멀티라인) · ? 배지(compact-center·멀티라인) · 한 줄 비교 3종을 놓았습니다
  • 이 줄은 데모가 아니라 실물입니다. docs 사이트는 앱과 똑같이 setupTooltip()을 한 번 호출해 두고 있어서, data-ds-tip + 여러 줄 title 마크업만 두면 프로덕션 코드가 그대로 처리합니다 — 미리보기에서 보이는 .ds-tooltip--multiline 전환이 실제 화면의 그것과 같은 경로입니다. docs 데모가 실제 컴포넌트와 어긋나는(재구현 드리프트) 문제를 이 지점에서는 구조적으로 없앴습니다
  • 미리보기의 Vue 미러 쪽도 실제 코드에 맞췄습니다 — .ds-tooltip--multiline 자동 토글, top placement의 아래로-flip(문서에는 있는데 데모에만 빠져 있었습니다)

수정

  • placement가 3개라고 적혀 있었지만 실제로는 4개입니다compact-center(data-ds-tip="compact-center")는 tooltip.tsdesign-system.css 양쪽에 이미 구현돼 있는데 문서에만 빠져 있었습니다. compact와 같은 작은 버블이지만 트리거 가운데에 정렬되고 아래 화살표가 붙습니다 — ? 도움말 배지처럼 좁은 트리거에서 좌측 정렬 버블이 어긋나 보이는 문제 때문에 만든 변형입니다. 자동 대상 목록에 .ds-btn--icon도 빠져 있어 함께 채웠습니다

메모

  • 멀티라인 툴팁(.ds-tooltip--multiline)이 실제로 뜨는 곳을 코드에서 전수 확인해 표로 남겼습니다 — admin 근무 유형 모달의 ⓘ 2개(연차 차감 판정 · 공휴일 근무 유형), hr-system 시트 「열 설정」 모달의 ? 배지 1개, 이렇게 3곳뿐입니다
  • ⚠️ 아래 두 항목은 같은 날 범위 확장(맨 위 항목)으로 폐기됐습니다 — 지금은 개행 하나로 충분하고, ƒ 힌트도 DS 툴팁으로 승격됐습니다. 기록으로만 남깁니다
  • 발동 조건은 「개행 title」 하나가 아니라 두 가지입니다 — ① 툴팁 대상일 것(자동 셀렉터 또는 data-ds-tip) ② 평가된 title에 실제 개행이 있을 것. hr-system 시트의 수식 열 ƒ 힌트는 개행 title을 갖고 있지만 data-ds-tip이 없어(그 파일에는 data-ds-tip이 하나도 없습니다) 브라우저 기본 툴팁으로 뜹니다. 미전환 1건으로 문서에 적어 뒀습니다
  • Alpine :title="..." 이 소스에서 여러 줄에 걸쳐 있는 것은 개행이 아닙니다. 이 기준으로 다시 세면 후보 4건은 전부 한 줄 툴팁입니다(스케줄러 희망근무 셀 · 연차 규칙 · 직원 삭제 · 시트 상단 버튼들)
  • 가로 방향 충돌 처리는 없습니다 — flip은 세로 전용이라, 화면 가장자리에 붙은 트리거에 320px 멀티라인 버블이 뜨면 잘립니다. 지금 사용처 2곳이 모두 가운데 뜨는 모달 안이라 표면화되지 않았을 뿐입니다(미리보기 stage를 가운데 정렬한 것도 같은 이유). 필요해지면 그때 좌우 clamp를 넣기로 하고 한계로 적어 뒀습니다

2026-07-21 — AppHeader 배경·구분선을 화면별로 켤 수 있게 (+ 메신저 화면 light 통일) (design/employee-app)

수정

  • AppHeadersolid·dividerboolean 외에 () => boolean도 받습니다. employee-app처럼 헤더 인스턴스 하나가 여러 섹션을 도는 셸에서는 boolean이 무용지물이었습니다 — x-data 표현식은 마운트 시 한 번만 평가되므로 첫 화면의 값이 그대로 굳어, 섹션을 바꿔도 배경이 따라오지 않습니다. 함수로 넘기면 appHeaderRootClass getter가 매번 다시 읽어 화면별로 갈립니다. 기존 boolean 호출부는 그대로 동작합니다(옵션 타입만 넓힘)
  • 판단 기준을 문서에 적었습니다 — 본문 맨 위가 흰 면(서브탭 바·카드)으로 시작하면 solid로 이어 붙이고, 회색 셸이 그대로 이어지면 기본형(투명)을 둡니다

수정

  • 헤더 탭의 활성 밑줄이 웹 탭보다 얇게 보이던 문제. .ds-tab-underline의 기본 오프셋(md -12px / ml -15px)은 nav.ds-tab-nav하단 1px 구분선 위에 겹치라고 1px 더 내려간 값입니다. 헤더 탭 줄에는 그 선이 없고 대신 overflow-y: clip이 걸려 있어서, 2px 밑줄의 아래쪽이 그대로 잘려 나갔습니다 — 브라우저에서 실측하니 밑줄이 아이템 바닥을 0.8px 넘어가 그만큼 깎여 2px가 ~1.2px로 렌더됐습니다. .ds-app-header__tabs 안에서만 1px씩 올려(-11px / -14px) 밑줄 전체가 아이템 박스 안에 들어오게 했습니다. 수정 후 재측정: 헤더 -0.2px(= 완전히 안쪽), 웹 탭과 동일한 굵기
  • 모바일 탭에서 hover·pressed 배경 틴트 제거. 터치 기기에서 :hover탭한 뒤에도 다음 탭을 누를 때까지 남습니다(iOS·Android 공통) — 그래서 선택하지도 않은 탭에 회색 박스가 붙어 있는 것처럼 보였습니다. .ds-app-header__tabs 안에서만 배경을 투명으로 고정했습니다(웹 TabNav는 그대로 틴트 사용). 선택 피드백은 글자색 + 하단 밑줄이 담당합니다. @media (hover: hover)로 가르지 않았습니다 — 그러면 데스크톱 브라우저로 앱을 여는 개발·검수 화면과 실기기가 갈립니다. 문서: AppHeader — 배경 틴트 없음, TabNav 상태 목록에도 예외 표기
  • 헤더 우측 아이콘 버튼을 알림벨과 같은 톤으로. getActions 버튼(.ds-btn--icon --muted --lg)과 알림벨은 박스 36px · 글리프 24px까지는 같은데, 색이 neutral-400(벨은 500)이고 레지스트리 아이콘 선이 1.5(벨의 path는 2)라 나란히 두면 액션만 옅고 가늘게 보였습니다. .ds-app-header__actions 안에서만 벨에 맞춰 색·선 굵기·hover(배경 원 없이 색만 진해짐)를 통일했습니다. 특성도로 이깁니다 — .ds-btn--icon.ds-btn--muted.ds-btn--icon:hover:not(...)가 파일 뒤쪽에 있어 클래스 2개짜리로 쓰면 순서에서 집니다(새 !important 0). 실측 확인: 액션·벨 모두 36×36 박스 / 24×24 글리프 / rgb(94,94,94) / path stroke 2px
  • 확인 방법. 같은 마크업을 nav.ds-tab-nav.ds-app-header__tabs 두 곳에 넣고 앱 빌드 CSS(Tailwind 유틸 포함)를 물린 뒤 getBoundingClientRect로 쟀습니다. ⚠️ design-system.css만 걸고 재면 relative·inline-flex 유틸이 없어 밑줄의 기준 박스가 .ds-tab-item으로 바뀌어 측정값이 통째로 달라집니다(처음에 이걸로 한 번 틀렸습니다)

추가

  • showBack 옵션onBack이 있어도 false면 그 화면만 1depth로 렌더합니다. 앱 셸은 헤더 인스턴스가 하나인데 목록(1depth)과 상세(2depth)를 오가므로, "onBack이 있으면 2depth" 규칙만으로는 깊이를 바꿀 수 없었습니다(onBack 유무는 마운트 시점에 굳습니다). 제목 강조(--depth2)도 onBack 유무가 아니라 실제 백버튼 렌더 여부를 따르도록 고쳤습니다 — 안 그러면 목록 화면에서 날짜만 화면 제목처럼 커집니다
  • showBell·showAvatar() => boolean 허용 — 상세에서 벨·아바타를 끄려면 solid와 같은 이유로 함수가 필요합니다
  • onBellClick 옵션 — 넘기면 공유 벨의 드롭다운을 렌더하지 않고 그 핸들러만 부릅니다. 모바일에서 알림 팝오버는 어차피 화면을 거의 다 덮어서, 직원 앱은 알림을 2depth 화면으로 엽니다. 미읽음 배지는 두 모드가 같은 notificationBell 스코프 안에 있어 카운트 폴링이 그대로 유지됩니다. 문서: AppHeader — 알림 벨
    • ⚠️ 셸이 연 depth의 뒤로가기를 app-depth-back 이벤트로 돌리면 엉뚱한 섹션이 자기 goBack()을 실행합니다(직원 앱은 메신저가 그 이벤트를 듣습니다). 셸이 연 화면은 셸이 직접 닫습니다. 같은 이유로 메신저의 x-effectsetDepth(null)모든 depth를 밀던 것도 kindmessenger*일 때만 닫도록 좁혔습니다 — 안 그러면 알림 화면이 열리자마자 닫힙니다
  • 문서에 헤더 하나가 목록과 상세를 겸할 때 절 추가 — 상태는 라우터 스토어에, 동작(뒤로가기)은 window 이벤트로 넘기라는 원칙 포함
  • ic-message-plus 아이콘 신설 — 말풍선 + 안쪽 플러스("대화를 새로 만든다"). 말풍선 path는 ic-message와 동일하고 중심에 6단위 십자만 얹었습니다
  • AppHeader.test.ts 신설(12케이스) — 함수형 옵션이 매번 다시 읽히는지, 깊이 판정과 제목 강조가 함께 움직이는지, 탭이 있으면 하단 8px이 붙지 않는지를 잠급니다

메모

  • 직원 앱 메신저 탭(대화/연락처)을 본문 → 헤더로 이관(ml 48px). 본문에 nav.ds-tab-nav를 두던 구조라 모바일 상단 내비게이션은 헤더와 탭이 한 컴포넌트라는 규칙을 어기고 있었습니다(결재 탭은 이미 이관 완료). 함께 있던 「+ 그룹 대화」 버튼은 헤더 우측 아이콘 액션(ic-message-plus)으로 옮겼습니다. 그룹 선택 모드의 컨텍스트 바(취소 / n명 / 그룹 만들기)는 탭이 아니라 선택 상태 전용 툴바라 본문에 남고, 그동안 헤더 탭과 그룹 액션은 감춥니다 — 예전에도 탭 줄 자리를 이 바가 통째로 차지했습니다
  • 그룹 대화 만들기(상대 고르기)도 2depth 화면으로. 「+」를 누르면 뷰는 연락처 그대로지만 헤더가 백버튼+「그룹 대화」로 바뀌고 하단 내비가 숨습니다. 본문 툴바의 「취소」 버튼은 제거 — 백버튼이 곧 취소이고(goBack()groupSelectMode를 가장 먼저 풉니다) 둘 다 두면 같은 동작이 한 화면에 두 번 나옵니다. 헤더가 없는 chat-widget에서는 그 버튼이 유일한 취소라 그대로 둡니다
  • messengerGroupSelect 스토어 거울 제거. 그룹 선택이 depth로 승격되면서 탭·액션을 감추던 두 군데 가드가 isDepth 하나로 덮여 읽는 곳이 없어졌습니다 — 남겨두면 "왜 있지" 하는 죽은 상태가 됩니다
  • 헤더가 탭을 품으면 헤더 하단 8px은 붙지 않습니다(요청받아 확인). (solid || divider) && !hasTabs 조건이라 메신저(배경 solid + 탭)는 --pad-bottom 없이 탭 줄이 바로 본문에 이어집니다. 테스트 4케이스로 잠갔습니다
  • 직원 앱: 메시지방·공지 상세를 2depth 페이지로 전환. 자체 헤더 줄 대신 공유 AppHeader가 백버튼·제목·액션(검색·멤버)을 그리고, 하단 내비는 숨습니다. 상세 상태는 router 스토어의 depth가 소유하고(헤더는 앱 셸 스코프, 상세는 섹션 스코프라 로컬로 공유 불가 — approvalTab과 같은 이유), 백버튼·헤더 액션은 window 이벤트(app-depth-back 등)로 섹션에 전달합니다
  • 공지 상세: 「확인 했어요」를 하단 고정 + 끝까지 읽어야 활성화. 버튼이 본문 스크롤 안에 있어 긴 공지는 끝까지 내려야 보였습니다 → 스크롤러 바깥 형제로 빼서 화면 하단에 고정하고(position: fixed가 아니라 채팅 입력줄과 같은 shrink-0 패턴), 본문을 끝까지 본 뒤에만 활성화되도록 했습니다. 함정 둘: ① 내용이 짧아 스크롤이 없는 공지는 scroll 이벤트가 영원히 안 와서 버튼이 영영 비활성이 됩니다 → 열릴 때 한 번 직접 잽니다. ② 그 측정을 화면에 붙기 전에 하면 clientHeight·scrollHeight가 둘 다 0이라 "끝까지 봤다"가 참이 됩니다 → clientHeight === 0이면 재지 않습니다. 두 함정 모두 Messenger.test.ts(8케이스)로 잠갔습니다
  • 공지 상세 메타를 DS 라벨로 교체(--md 24px/13px) · 날짜 text-b4. 중요도·분류 칩이 유틸 조합(px-2 py-0.5 rounded + bg-…/text-… + text-[10px])으로 손수 그려져 있었습니다 → .ds-label --md --square + 색 변형 클래스로 바꿨습니다(라벨은 색 변형으로만 칠하는 것이 강제 규칙). noticePriorityColor()도 Tailwind 유틸 대신 ds-label--red/--primary/--gray/--light-gray를 반환합니다. 분류 칩은 중요도 칩과 바로 옆에 붙어 있어 같은 회색이면 구분이 안 되므로 --line(테두리)로 갈랐습니다
  • 공지 제목 text-h3(24px) · 본문 text-b3(16px). 제목은 <h2 class="text-base">였는데 16px이 아니라 h2 기본값으로 렌더되고 있었습니다 — 공유 DS의 unlayered h1~h6 규칙이 Tailwind 유틸을 이기고, 앱 가드는 DS 토큰 클래스만 덮기 때문입니다(공지 목록의 <p class="text-xs">와 같은 함정)
  • ⚠️ 메신저 마크업은 데스크톱 chat-widget(7개 앱)과 공유입니다. 위젯에는 상단 헤더가 아예 없어서 자체 헤더 줄을 지우면 대화방에서 빠져나올 방법이 사라집니다hostHeader 옵션(기본 false)으로 employee-app에서만 숨깁니다. 또한 safe-area 하단 패딩을 갖고 있던 게 하단 내비라, 내비가 숨는 화면에서는 채팅 입력줄이 .safe-bottom을 직접 가져야 홈 인디케이터에 깔리지 않습니다
  • 죽은 헤더 교체 감지기 제거.safe-top 요소를 찾아 앱 헤더를 숨기려던 MutationObserver(섹션 콘텐츠 전체를 subtree+attributes로 관찰)가 남아 있었는데, 결과값 sectionHeaderReplacementVisible읽는 템플릿이 하나도 없었습니다. 이번 2depth 구조가 그 의도를 제대로 대체하므로 함께 걷어냈습니다
  • 직원 앱 메신저 화면을 전부 light(흰 면) 한 장으로 통일했습니다. 이전에는 투명 헤더(셸 회색) → 서브탭 바(neutral-25) → 대화 목록(neutral-50)으로 세 단계의 회색이 겹쳐 헤더 아래에 회색 띠가 남았습니다. 헤더는 메신저에서만 solid를 켜고, .messenger-subnav-bar는 neutral-25 → 흰색, 대화 검색·목록의 bg-neutral-50과 메시지 검색 바의 bg-gray-50은 걷어내 messenger-content.html 루트가 bg-white 한 곳에서 면을 소유합니다
  • 공지 구간의 카드를 걷고 메시지 구간과 같은 눈금에 올렸습니다. 공지만 흰 카드(mx-4 + px-4 + 행 px-2)로 묶여 있어 제목 열이 메시지 이름 열보다 안쪽으로 밀려 있었습니다 → 구간 px-4 py-2 · 행 px-3 py-2.5 gap-3 · 아이콘 40px로 맞췄습니다. 이 과정에서 <p class="text-xs">가 12px이 아니라 16px로 렌더되던 것도 드러났습니다 — 공유 DS의 unlayered p { font-size }가 Tailwind 유틸을 이기고, employee-app main.css:is(p,h1..h6).text-* 가드는 DS 토큰 클래스만 덮기 때문입니다(text-xs·text-[10px]은 무방비). <span>/<div> + text-b4/text-b6로 바꿔 메시지 행과 같은 스케일이 됐습니다
  • 행에서 테두리와 좌우 여백을 걷어 폭을 넓게 씁니다. 필수 대화방의 border border-orange-100을 없애고(배경 틴트만으로 구분) 행의 px-3도 뺐습니다 — 라인이 사라지면 행을 안쪽으로 물릴 이유가 없습니다. 이제 좌우 여백은 구간의 px-4(16px) 하나만 남아, 아바타·시각·읽지 않음 배지가 각각 12px씩 넓게 자리합니다. 공지·채널 행도 같은 규칙입니다
  • 공지 목록의 중복 마크업 한 벌 제거. slice(0, 3)slice(3)완전히 같은 행 마크업 두 벌이라 한쪽만 고치면 「더보기」 이후 행만 조용히 갈라졌습니다 → slice(0, showAllNotices ? length : 3) 한 벌로 합쳤습니다(35줄 감소). 미확인 공지 아이콘의 하드코딩 fill="#ea580c"(구버전 오렌지)도 fill-orange-500으로 교체
  • 회색 배경을 걷으면 hover:bg-white가 같이 죽습니다. 채널·대화 행과 메시지 검색 결과가 회색 위에서 흰색으로 밝아지는 hover였는데, 바닥이 흰색이 되면서 아무 변화가 없어집니다 → hover:bg-neutral-50 active:bg-neutral-100으로 뒤집었습니다. 연락처 행은 원래부터 틴트 오버레이(.msg-row-select)라 영향이 없습니다

2026-07-20 — 모바일 앱 셸 크롬 2종 신설 (AppHeader · BottomNav) (design/employee-app)

추가

  • AppHeader · BottomNav 신설. 그동안 모바일 상단 바와 하단 탭바는 DS에 정의가 아예 없었고(SoT에 @media 0건 · safe-area-inset 0건), employee-app main.css.bottom-nav/.bottom-nav-item/.unread-badge/.safe-top으로 자체 보유하고 있었습니다. 정의를 공유 DS로 올려 .ds-app-header*(7규칙) · .ds-bottom-nav*(5규칙)로 세웠습니다. 현재 소비 앱은 employee-app 하나이지만, 모바일 프리미티브가 DS 밖에 있던 구조 자체를 해소하는 것이 목적입니다
  • 설정 기반(config-driven) 구조. 탭 추가·삭제·순서 변경·배지·권한 숨김이 호출부 배열 수정만으로 끝납니다(apps/employee-app/src/nav-config.ts). 템플릿과 컴포넌트는 손대지 않아요. 항목 옵션: icon/iconSvg(이중 주입) · activeIcon/activeIconSvg · badge() · hidden() · disabled
  • 활성 상태 fill 아이콘. 선택된 탭은 색뿐 아니라 글리프가 outline → solid로 바뀝니다. 활성 변형은 선택 사항이라 지정하지 않은 항목은 기본 아이콘으로 폴백해요
  • 배지 일반화. 기존 .unread-badge는 메신저 전용이었지만, badge()어느 탭에나 붙고 0·null·"" 반환 시 자동으로 숨습니다(badgeMax 초과분은 "99+")
  • 셸 폭 토큰 --ds-app-shell-max-width(기본 430px) — 하드코딩 430px를 대체

수정

  • AppHeader 기본형 높이 57 → 48px. padding: 12px 16px 8px + 흰 배경 + border-bottom: 1px 이던 것을 padding: … 16px 0으로 바꾸고, 배경·구분선을 기본형에서 걷어내 옵션으로 돌렸습니다. 헤더가 셸 배경과 이어지고, 아래 콘텐츠와의 간격은 콘텐츠가 소유합니다(헤더에 하단 패딩을 되돌리면 화면마다 간격이 이중으로 붙습니다)
  • 배경(solid)·하단선(divider)은 서로 독립 축. 넷 중 아무 조합이나 가능하고 기본형은 둘 다 off입니다 — .ds-app-header--solid(흰 배경) / .ds-app-header--divider(하단 1px). 앱 CSS에서 .ds-app-header에 배경을 덧칠하면 변형이 갈라지므로 옵션으로만 켜도록 문서에 못박았습니다
  • 하단 여백 8px은 조건부로 되살아납니다. 헤더가 배경이나 선을 가져 하나의 "면"이 되면 콘텐츠와의 여백을 헤더가 직접 가져야 하므로 --pad-bottom(8px)이 붙습니다. 단 탭 줄이 있으면 붙지 않습니다 — 탭이 그 아래를 채우므로 더하면 사이가 벌어집니다. 조건은 (solid || divider) && !hasTabs이고, 판정은 컴포넌트가 하고 CSS는 결과 클래스만 받습니다(:has() 의존 회피). 결과 높이: 기본형 48 · 배경만 56 · 선 포함 57px — 초기 구현의 57px이 정확히 divider + 하단 8px 조합이었던 셈입니다
  • AppHeader에 1depth / 2depth 도입. onBack을 넘기면 2depth — 좌측에 백버튼(.ds-app-header__back, chevron-left 24px)이 붙고 .ds-app-header--depth2가 루트에 걸려 __leading이 보조 텍스트(b4·neutral-400)에서 화면 제목(b3·semibold·neutral-900)으로 바뀝니다. 별도 depth 옵션 대신 **"뒤로 갈 곳이 있으면 2depth"**로 판정해 호출부가 한 옵션만 넘기면 되게 했습니다. 백버튼+제목 묶음은 .ds-app-header__lead가 담당
  • 깊이별 시각 예시를 문서에 고정 렌더로 넣었습니다DemoAppHeaderdepth·tabs·bell·avatar prop을 추가해, 체크박스를 켜지 않고도 1depth / 2depth / 2depth+탭 줄을 나란히 비교할 수 있습니다(ASCII 다이어그램을 실제 렌더로 교체). 3depth 이상도 2depth와 동일 형태 — 깊이를 세지 않고 "뒤로 갈 곳이 있는가"로만 판정합니다
  • 메신저 아이콘 점 3개 정렬. 말풍선 원의 중심은 x=11.25인데 점 3개는 Heroicons 원본 좌표(평균 12.375)를 물려받아 1.125 오른쪽으로 치우쳐 있었습니다(말풍선만 다른 path로 교체되고 점이 따라오지 않은 흔적). 시작점 M8.625M7.5로 세 점 중심을 7.125 · 11.25 · 15.375로 맞췄습니다
  • 알림벨 글리프 확대를 클래스 훅으로 이관. 앱 CSS의 [x-data="notificationBell"] > .relative > button > svg는 공유 템플릿의 4단 중첩 구조에 의존해 마크업이 바뀌면 조용히 깨졌습니다 → .ds-app-header__bell로 대체
  • messenger-content.htmltext-[9px]text-c2(10px). 9px는 타이포그래피 스케일에 없는 값이고 employee-app 실렌더 최소값이었습니다. <span>이라 unlayered p 규칙에 눌리지 않고 실제로 9px로 렌더되던 자리예요

문서

  • 내비게이션 섹션에 두 페이지 추가. .ds-tabbar(상단 탭)와 .ds-bottom-nav(하단 탭바)의 혼동, PageHeader(데스크톱)와 AppHeader(모바일)의 혼동을 각 페이지의 규칙 (강제)에 트랩으로 명시
  • 내비게이션 사이드바를 Web / Mobile (App) 2계층으로 재편. 카테고리에 (App)을 명시한 이유는 반응형 모바일 웹과 구분하기 위해서입니다 — 좁아진 데스크톱 화면은 Web 컴포넌트 + 브레이크포인트 규칙이 담당하고, 이 그룹은 Capacitor 네이티브 앱 셸만 다룹니다
  • 모바일 상단 내비게이션은 헤더와 탭이 한 컴포넌트. 작업 중간에 TabNav (Mobile) 문서를 따로 만들었다가, 모바일에서는 헤더와 탭이 같은 것이라는 정리에 따라 그 문서를 없애고 AppHeader가 탭 줄까지 품도록 통합했습니다. getTabs를 넘기면 헤더 줄 아래 .ds-app-header__tabs가 렌더되고, 생략하면 헤더만 남습니다. 웹처럼 본문에 nav.ds-tab-nav를 두지 않습니다
  • 탭 아이템 스타일은 웹 TabNav.ds-tab-item을 공유합니다(현재 --ml 48px 고정, 모바일 전용 사이즈 베리에이션은 미정). 활성 클래스도 tab-active로 동일 — BottomNav의 is-active와 다르니 주의
  • 활성 밑줄이 세로 스크롤을 만드는 문제를 DS 안에서 막았습니다. .ds-app-header__tabsoverflow-y: clip을 자체 보유하므로 앱 로컬 보정이 필요 없습니다. 웹은 반대로 .tabnav-unclip으로 클립을 푸는 opt-in 보정을 쓰므로, 두 규칙을 서로 복사하면 그대로 버그입니다(양쪽 문서에 경고 유지)

메모

  • 라이브 미리보기 신설DemoAppHeader · DemoBottomNav. docs theme이 design-system.css 전체를 import하므로 데모가 실제 .ds-* 클래스로 렌더됩니다(시각은 프로덕션과 동일하고, 재구현되는 건 상태 로직뿐). BottomNav 데모는 활성 fill 아이콘을 토글해 outline과 직접 비교할 수 있고, AppHeader 데모는 슬롯(액션·벨·아바타)을 켜고 끄며 우측 고정 순서를 확인할 수 있습니다. 대신 각 페이지에 **「데모의 한계」**를 명시했습니다 — safe-area 패딩·네이티브 키보드 숨김·벨 드롭다운·오버플로 클립 조건은 브라우저에서 재현되지 않습니다
  • employee-app main.css 76줄 감소
  • 남은 모바일 프리미티브 부채(DS 정의 없음): 바텀시트 · 액션시트 · 토스트 · 스피너/스켈레톤 · 빈 상태 · 카드 · 아바타 · safe-area 유틸

2026-07-24 — 로그인 BI 규칙을 opt-in 으로 완화 (design/employee-app)

변경

  • 「로그인은 텍스트 고정」을 「기본은 텍스트, 심볼은 앱이 켜서 쓴다」로 바꿨습니다. 2026-07-20에 하지 않기로 못박았던 항목인데, 직원 앱이 제품 판단으로 로그인 상단에 Works 심볼을 놓기로 하면서 규칙이 실제 화면을 막고 있었습니다. 금지 대신 확장 가능한 형태로 열었습니다 — loginForm({ showBrandLogo: true }) 로 그 앱만 켜고 기본값은 false 라, 안 켠 앱 7개는 종전과 100% 동일한 텍스트 제목입니다.
  • 열되 경계는 남겼습니다. ① 로그인에 놓을 수 있는 건 심볼뿐이고 가입의 락업을 옮기는 것은 여전히 금지입니다(워드마크가 들어가면 바로 아래 부제의 서비스명이 두 번 적힙니다). ② 심볼은 $store.hereby.brandFamily 를 따라가고 자산 키를 문자열로 박지 않습니다. ③ 켠 화면은 심볼 → 제목 → 부제 3단이고, 「로그인 제목 = 앱 이름」 규칙은 그대로입니다 — 바뀐 건 심볼 노출 여부뿐입니다.
  • 「앱 이름 자체가 패밀리 브랜드인 앱」을 규칙에 명시했습니다. 직원 앱의 제품명은 hereby Works(국문 hereby 웍스)라 앱 이름과 브랜드가 같은 말입니다. 그동안 규칙이 admin(「관리자」 ≠ hereby Medical)처럼 둘이 다른 경우만 상정하고 있어서, 직원 앱 로그인 제목이 규칙 위반처럼 읽혔습니다. 예외가 아니라 일치하는 경우임을 적어 오독을 막았습니다. 아울러 nav.employee_app 은 「직원 앱」이라는 일반 서술어라 제품명이 아니라는 점도 함께 적었습니다.
  • 같은 내용을 CLAUDE.md 의 Brand/BI 한 줄 가드레일에도 반영했습니다(정본은 brand.md).

수정

  • 심볼을 켠 로그인 제목이 「브랜드 조판」대로 렌더되지 않던 문제. 제목 전체에 font-brand 한 장만 걸려 있어서, 영문 hereby 는 Geist SemiBold 600으로 맞았지만 국문 서비스명 웍스스택 폴백의 시스템 한글 서체 · weight 600 으로 떨어졌습니다(정본은 Pretendard Bold 700). 한 요소에는 서체·weight 를 하나씩밖에 못 주므로 loginFormtitleParts getter 를 두어 한글 덩어리만 잘라 내고, 조각별로 font-brand font-semibold / font-pretendard font-bold 를 물렸습니다. 실측 확인: hereby = Geist 600, 웍스 = Pretendard Variable 700. 한 워드마크 안에서 두 폰트가 섞이는 것이 정상이라는 조판 규칙(브랜드)을 그대로 따른 것입니다
    • ⚠️ x-for 템플릿 안팎에 공백·개행을 넣으면 텍스트 노드가 함께 복제돼 낱말 사이가 벌어집니다 — 한 줄로 붙여 씁니다
    • ⚠️ 제목 크기는 반드시 DS 토큰 클래스(text-h2 등)로 줍니다. tokens.css 의 unlayered h1{font-size:32px}@layer utilities 의 Tailwind 유틸을 이겨서 text-xl 같은 클래스는 조용히 무시됩니다

2026-07-24 — 모바일 화면 배율 라벨·디폴트 재정의 (design/employee-app)

변경

  • 글자 크기 배율 3단계 라벨을 한 칸씩 내리고 디폴트를 가운데로 — 이전 기본(×1.0) / 크게(×1.2) / 매우 크게(×1.4), 디폴트 ×1.0 에서 → 작게(×1.0) / 기본(×1.2) / 크게(×1.4), 디폴트 기본(×1.2) 로 바꿨습니다. 웹은 정의 타이포(×1.0) 기준으로 개발하고, 모바일은 최초 진입부터 한 단계 키운 배율(×1.2)을 기본으로 주어 손안의 화면에서 읽기 편하게 시작합니다. CSS zoom 값과 data-font-scale 키(base/lg/xl)는 그대로라 기존 저장값·CSS 마이그레이션이 없습니다(저장소의 base=작게, lg=기본, xl=크게 로 라벨만 이동).

문서

  • 타이포그래피 문서에 모바일 화면 배율 섹션 신설 — 작게/기본/크게를 실제 zoom 으로 나란히 렌더한 세 배율 비교 미리보기와 구현 위치를 명시했습니다.

2026-07-24 — 표 가로 스크롤바 전역 적용 (design/apply-feedback-uiux-2)

추가

  • 가로로 넘치는 모든 표(리스트)에 위·아래 가로 스크롤바를 자동으로 붙입니다setupTableXScroll()(@hereby/components)을 각 앱 main.ts에서 1회 호출하면 DOM을 훑어 붙이고, Alpine이 나중에 그리는 표·모달 안 표까지 MutationObserver로 따라갑니다. 적용: admin · team-manager · hr-system · general-affairs · management-dashboard · inventory. 화면 마크업은 한 줄도 고치지 않았습니다
  • 출발점은 윈도우입니다. 윈도우에는 표를 가로로 스크롤할 입력 수단이 사실상 없습니다 — 휠은 세로 전용이고 Shift+휠은 대부분 모릅니다. 네이티브 가로 스크롤바는 스크롤 컨테이너 바닥에만 생기는데 표가 길면 그 바닥이 화면 밖이라, 「옆에 열이 더 있다」는 사실조차 보이지 않습니다. 그래서 위에도 하나 더 두고 둘 다 상시 노출입니다
  • 레이아웃 높이는 0입니다 — 바(10px)는 같은 크기의 음수 마진을 받아 표 위에 겹쳐 그려집니다. 표가 아래로 밀리지 않습니다. 겹치는 자리가 sticky thead(z 30)·sticky 헤더 컬럼(z 31)이라 --ds-z-table-xscroll(36) 토큰을 새로 뒀습니다
  • PC·태블릿(≥768px = md)만. 모바일은 터치 스크롤이 기본이라 바를 띄울 이유가 없습니다. CSS 미디어쿼리와 JS 양쪽에서 막습니다

메모

  • 네이티브 스크롤바(::-webkit-scrollbar)를 쓰지 않기로 했습니다. 처음엔 그렇게 만들었다가 되돌렸습니다 — macOS는 오버레이라 스크롤하는 동안에만 보이고(상시 노출 요구 위반), Chrome은 같은 요소에 scrollbar-width/scrollbar-color가 있으면 ::-webkit-scrollbar 규칙을 통째로 무시하며, OS의 「스크롤 막대 표시」 설정에도 흔들립니다. 실제로 헤드리스 Chromium에서는 격리 테스트로도 커스텀 스크롤바가 전혀 렌더되지 않았습니다. 트랙(.ds-xscroll) + 썸(.ds-xscroll__thumb)을 직접 그리고 포인터 드래그를 직접 처리하는 쪽이 어느 OS에서도 동일합니다
  • 가로 flex/grid 부모 아래 래퍼에는 붙지 않습니다. 바는 래퍼의 앞뒤 형제로 들어가는데, 부모가 가로 flex면 형제가 새 「열」이 되어 배치가 무너지기 때문입니다. 안전하게 건너뜁니다
  • 세로 flex 부모에서는 flex: 0 0 auto가 필수였습니다. flex 아이템은 기본이 flex-shrink: 1이라, 컨테이너가 꽉 차면 바가 10px → 7px로 눌려 음수 마진과 어긋났습니다(실측으로 발견)
  • 제외 대상: 근무 스케줄표(.sched-grid, Table 2)와 급여 시트(.sheet-grid/.sheet-scroll) — 자체 가상 스크롤 정책이 따로 있습니다. 화면별 해제는 data-ds-xscroll="off"

2026-07-20 — BI 적용 범위 결정 기록 (design/apply-BI-UIcomponents)

메모

남은 항목을 훑고 하지 않기로 한 것을 정본에 못박았습니다. 셋 다 「빠진 것」처럼 보여서, 기록해 두지 않으면 다음 사람이 되돌립니다.

  • 로그인 화면은 텍스트 유지 — 가입만 BI 락업이라 비대칭이지만 의도된 것입니다. 로그인은 「어느 앱에 들어가는가」가 먼저라 앱 이름이 큰 제목을 차지하고, 가입은 「어느 서비스에 계정을 만드는가」가 먼저라 상단이 락업입니다
  • hr-system 헤더는 대상이 아닙니다 — 헤더가 없는 것이 의도된 설계입니다(시트 화면을 세로로 최대한 쓰려고 상단바를 걷어냄). 「브랜드가 빠졌다」고 헤더를 새로 만들지 마세요
  • og:image는 썸네일 제작 전이라 보류 — 파비콘과 달리 심볼을 그대로 못 씁니다. 1200×630 가로 캔버스에 락업을 배치해야 해서 배경·크기·앱 이름 표기가 전부 디자인 결정입니다. 시안이 나온 뒤 착수합니다

2026-07-20 — 파비콘 전 앱 적용 + 헤더·LNB 브랜드 정본화 (design/apply-BI-UIcomponents)

추가

  • 파비콘을 9개 앱 전부에 적용 — 그동안 11개 앱 중 1개에만 있어 브라우저 탭에 기본 아이콘이 떴습니다. 자산은 packages/static/brand/favicon-hereby-<패밀리>.svg 하나뿐이고(앱마다 복사하지 않습니다) 심볼만 씁니다 — 파비콘 크기에서는 워드마크가 읽히지 않기 때문입니다. 직원 앱은 Works, 나머지는 Medical
  • 경로는 상대경로여야 합니다. href="static/brand/…" 로 걸어야 CDN(STATIC_URL)이 있으면 플러그인이 리라이트하고, 없으면 앱 base(/앱이름/) 기준으로 해소됩니다. 절대경로 /static/… 는 후자에서 404 납니다 — 실제 빌드로 두 경우를 모두 확인한 뒤 규칙으로 적었습니다

수정

  • team-manager 헤더의 구버전 마스터 락업 교체viewBox="0 0 125 32" 마스터 스우시를 인라인하고 있어, admin(Medical 심볼)에서 「내부 결재」를 단독 접속하면 로고가 바뀌어 보였습니다. 게다가 그 SVG 는 그라디언트에 var() 를 써서, 자산이 CSS 변수 없는 곳으로 나가면 심볼이 검붉게 뭉개지고 워드마크가 사라집니다(재현 확인). 정본(icons.ts)의 Medical 락업을 꺼내 쓰도록 바꿨습니다 — 52줄 인라인 SVG 가 6줄로 줄었습니다
  • LNB 브랜드의 하드코딩 제거workspace-switcher.html(공용)과 admin 자체 인라인이 심볼 키와 서비스명을 문자열로 박고 있었습니다. 「공용 템플릿에서 브랜드를 하드코딩하지 않는다」를 이 PR 이 직접 명문화해 놓고 정작 이 파일이 어기고 있던 상태입니다. 둘 다 $store.hereby.brandFamily · brand 를 읽도록 바꿨습니다
  • 직원 앱 PWA 아이콘 10종 재생성 — 기존 아이콘은 hereby BI 가 아예 아닌 다른 디자인(사원증 모양) 에 구버전 오렌지였습니다. Works 심볼로 다시 뽑고, 마스크 아이콘(maskable)은 어떤 모양으로 잘려도 살아남도록 안전영역 62% 안에 넣었습니다. theme_color 도 구버전 #ea580c → 토큰값 #f66014

2026-07-20 — 가입 화면을 BI 락업으로 + 앱에서 쓰는 락업 자산 등록 (design/apply-BI-UIcomponents)

추가

  • icons.ts에 락업 2종 등록logo-lockup-hereby-medical(865×130) · logo-lockup-hereby-works(790×130). 그동안 앱에는 심볼만 있어 락업을 쓸 수 없었습니다. 워드마크는 저장소 폰트로 path화했고(정본 4종 재현으로 검증된 생성기), viewBox로 비율만 고정해 크기는 쓰는 쪽 CSS가 정합니다
  • 스토어에 BI 자산 선택 축 2개 추가brandFamily(이 앱의 패밀리 키) · linkedBrandFamily(연동되는 상위 서비스). 기존 brand사람이 읽는 표기라면 이쪽은 자산 선택용 식별자입니다

수정

  • 가입 화면 상단을 글자에서 BI 락업으로 교체[상위 서비스 락업] + 이 앱 표기 + 새 계정을 만드세요. 직원 앱은 hereby Medical 락업 아래 hereby Works가 오고, 자기 자신이 상위인 앱(hr-system)은 아래 줄을 생략합니다 — 안 그러면 같은 브랜드가 위아래로 두 번 적힙니다
  • 직원 앱 가입 화면의 hereby Medical 오표기 해소. RegisterForm의 브랜드 문자열 폴백(title: opts.title ?? "hereby Medical")이 인자 없이 쓰인 가입 화면에 그대로 걸려, Works 앱에 Medical이 큰 제목으로 뜨고 있었습니다. 폴백을 없애고 그 자리에 「브랜드를 글자로 박으면 다른 패밀리가 새어 나간다」를 주석으로 남겼습니다
  • 강제 규칙의 「공용 템플릿 하드코딩 금지」를 글자에서 자산까지 확장했습니다

2026-07-20 — 락업 데모의 톤 폴백 버그 수정 (design/apply-BI-UIcomponents)

수정

  • 배경을 다크로 바꾸면 서비스명 톤이 light가 아니라 primary로 튀던 버그. dark는 어두운 배경에서 금지 조합이라 다른 톤으로 옮겨야 하는데, 데모가 무조건 primary로 되돌리고 있었습니다. 패밀리 기본값이 dark인 이유는 「심볼이 이미 그라디언트로 색을 지녀서 서비스명까지 오렌지면 심볼이 묻힌다」 이고 이 이유는 배경과 무관한데, primary로 튕기면 정확히 그 피하려던 상태가 됩니다. 체계 전환(setBrand)에도 같은 문제가 있어 함께 고쳤습니다
  • 데모는 자산을 내려받는 곳이라 영향이 큽니다 — 다크 배경용 락업을 받으려던 사람이 의도치 않게 오렌지 버전을 받고, 파일명(…-primary-on-dark.svg)만으로는 눈치채기 어려웠습니다

추가

  • 브랜드 · BI에 「중립 톤」 개념 명시darklight는 워드마크 hereby가 배경 따라 뒤집히는 것과 같은 한 짝이고, primary만 그 짝 밖에서 의도를 갖고 직접 고르는 톤입니다. 따라서 배경별 기본은 밝은 배경 dark / 어두운 배경 light입니다

2026-07-20 — 브랜드 규칙을 에이전트 가드레일까지 반영 + 이메일 정정 (design/apply-BI-UIcomponents)

추가

  • 「마스터 + 일반명사」 허용을 명문화 — 금지되는 건 서비스명(등록된 패밀리 브랜드)을 붙이는 것이지 뒤에 오는 모든 말이 아닙니다. hereby System Admin · hereby Design System처럼 일반명사로 대상을 가리키는 표현은 정본이고, 구분자(·) 없이 띄어쓰기로 잇습니다. 이 문장이 없으면 다음 사람이 hereby System Admin을 위반으로 보고 되돌립니다
  • design-tokens.md에 BI 그라디언트 hex 예외 추가 — 토큰 규칙만 읽은 에이전트가 BI 그라디언트를 var()로 "고쳐서" 심볼을 검게 만드는 능동적 사고를 막습니다(데모 2종에서 실제로 났던 일). 이메일 예외도 색에만 적용되고 브랜드 표기는 규칙을 따른다는 점을 함께 못박았습니다

수정

  • CLAUDE.md 가드레일에 「표기」 규칙 편입. 그동안 가드레일은 락업(자산) 만 담고 있었고, 정작 이번 사고의 원인인 텍스트 표기 규칙(소문자 강제 · 저작권=마스터 · 앱 이름≠브랜드 · 자리별 기준 · 공용 템플릿 하드코딩 금지)이 통째로 빠져 있었습니다. 자산/표기 두 줄로 분리해 채웠습니다

  • CLAUDE.md 자신의 위반 정정 — 첫 문장이 Hereby is a multi-tenant…였습니다. 이 파일은 매 세션 자동 로드되므로 Hereby가 정상 표기」라는 반례를 매번 주입하고 있었습니다

  • 라우터(design-system-docs.md) 정리 — brand 행이 규칙을 4절이나 복제해 자기 원칙(「중복 = 드리프트」)을 어기고 있었고, 예언대로 이미 낡아 있었습니다(소문자·저작권 규칙 없음). 한 줄 힌트로 줄였습니다. paths: 글롭에 tools/** · docs/ko/design-system/** · docs/.vitepress/theme/components/**를 추가 — DS 문서·데모를 고치는 세션에 라우터가 로드되지 않던 공백을 메웠습니다

  • 이메일 7건 정정 (email.service.ts) — 「app-wide」라던 직전 정정이 이 파일만 통째로 건너뛰어, 가입·초대·비밀번호 메일이 전부 구버전 표기로 나가고 있었습니다. 제목 3종 [Hereby][hereby], 본문 1곳, 푸터 &copy; Hereby Platform ×3 → &copy; 2026 hereby(앱 31곳과 동일 형태). 계정 메일은 앱이 공유하므로 특정 서비스를 가리킬 수 없어 마스터가 맞습니다

  • system-admin 로그인 부제 hereby · System Adminhereby System Admin (구분자 제거)

  • package.json description 19개 정정 — 앱 9개 → hereby Medical, 직원 앱 → hereby Works, 전 서비스 대상 도구(importer·system-admin)와 인프라 패키지 → hereby(마스터). 폐기 조합 Hereby Platform도 함께 제거했습니다. 화면에 뜨지 않는 메타데이터지만, 남겨두면 Hereby 검색이 계속 걸려 매번 「저건 괜찮은 것」을 다시 판단해야 합니다

  • SMS 발신 접두사 2곳 (sms.service.ts) — 임시 비밀번호·로그인 인증번호 문자의 [${organizationName || "Hereby"}] 폴백. 조직명이 없을 때 [Hereby]가 그대로 문자로 발송되고 있었습니다 → [hereby]. 이메일 제목과 같은 부류인데 파일이 달라 P1에서 빠져 있었습니다

  • AI 어시스턴트 시스템 프롬프트 (ai/prompts/system.ts) — 당신은 Hereby의 HR 어시스턴트입니다. Hereby는…. 정적 문자열이 아니라 모델이 답변에서 그대로 따라 쓰는 표기라, 프롬프트가 틀리면 어시스턴트가 사용자에게 계속 Hereby라고 말합니다 → hereby

메모

  • 남은 Hereby는 대부분 코드 식별자(HerebyClient · HerebyTemplate · listHerebyTemplates · X-Hereby-Timestamp · 마이그레이션 클래스명)와 코드 주석이라 표기 규칙 대상이 아닙니다
  • 판단이 남은 3건 — ① 시드 데이터 Hereby Demo Corporation(데모 조직 이름, 참조처는 시드 파일 2곳뿐이라 변경은 안전하지만 브랜드 표기인지 데이터인지 성격 판단 필요), ② CLI 도움말·배너(hereby CLI · === Hereby Excel Importer ===, 개발자 대상), ③ system-admin 연동 탭 placeholder Hereby production(입력 예시)

2026-07-20 — 직원 앱을 hereby Works로 통일 (design/apply-BI-UIcomponents)

추가

  • 공용 템플릿용 브랜드 주입 경로 신설createAlpinePlugin({ brand })$store.hereby.brand(기본값 hereby Medical). packages/components/html/의 템플릿은 8개 앱이 같은 파일을 인라인하므로 브랜드를 박아 두면 한 앱을 고치는 순간 나머지 7개가 깨집니다. 「공용 템플릿에서 브랜드를 하드코딩하지 않는다」를 브랜드 · BI의 강제 규칙(표기 절)에도 추가했습니다

수정

  • 직원 앱 8곳을 hereby Works로 정정 — 로그인 부제 · 공용 로딩 화면 · 「더보기」 버전 표기 · <title> · iOS 홈화면 라벨 · manifest(name/short_name) · Capacitor appName · 푸시 알림 제목 폴백(+ push-sw.test.ts 기대값)
  • 직원 앱은 한 앱이 자기 이름을 세 가지로 말하고 있었습니다 — 홈화면은 Hereby Works, 로딩·로그인은 hereby Medical, 더보기는 다시 Hereby Works. 직전 커밋이 8개 앱 로그인 부제를 일괄로 hereby Medical로 맞췄는데, 직원 앱은 Works 패밀리라 그 정정이 이 앱에서만 오답이었던 것이 원인입니다
  • 로딩 화면은 공용 파일이라 문자열 치환이 불가능해 위 주입 경로로 바꿨습니다. 나머지 7개 앱은 코드 변경 없이 기본값 hereby Medical을 그대로 받습니다(빌드 산출물로 확인)

메모

  • manifest·appName은 앱을 다시 설치해야 반영됩니다 — 홈화면 아이콘 라벨·앱서랍·런처 이름은 설치 시점에 굳습니다. 나머지(로딩·로그인·더보기·탭 제목)는 배포 즉시 반영됩니다
  • 검증: 직원 앱 로그인 화면에 「직원 앱」 + hereby Works + © 2026 hereby, admin 로딩 화면에 hereby Medical — 실제 렌더로 확인. push-sw 테스트 4/4, 전체 빌드 21/21 green

2026-07-20 — hereby Works를 세 번째 패밀리로 등록 (design/apply-BI-UIcomponents)

추가

  • 브랜드 · BI에 패밀리 hereby Works 등록. 직전 항목이 「미결 — Works가 별개 제품명인지 판단 필요」로 남겼던 것의 결론입니다: Works는 패밀리이고, Medical과 연동되는 직원 앱입니다. 이로써 패밀리는 Medical · Sign · Works 셋이 됐고, 「등록된 것은 둘뿐」이라 못박아 둔 서술 3곳(강제 규칙 2곳 + 경고 박스)을 전부 갱신했습니다

  • Works 심볼 확정. 그라디언트가 패밀리 정본(#FE4D43#FF7732#FFAF00)과 stop·offset까지 일치하고, 그라디언트 벡터도 Medical과 동일합니다(박스 오프셋 (1, 6)을 더하면 소수점까지 같은 값). 즉 패밀리 규격을 그대로 따른 자산입니다

  • Figma export의 id를 그대로 쓰지 말라는 규칙 추가. export는 paint0_linear_… · clip0_… 같은 일련번호 id를 다는데, id는 문서 전역이라 다른 BI 자산과 충돌합니다(실제로 났던 버그). 등록할 때 lnb-hereby-<서비스>-grad로 바꾸고, 클립 사각형이 viewBox와 같은 크기면 아무것도 자르지 않으므로 clipPath째 지웁니다

  • 국문 서비스명 「웍스」 확정메디컬 · 사인에 이어 세 번째 국문 정본입니다. 조판 규격표·translate="no" 규칙·서비스명 톤 등 국문 서비스명을 열거하던 자리 6곳에 반영했습니다

  • Works 락업(영문·국문) export 수령 + 검증. 조판이 정본 그리드 위에 있음을 교차검증했습니다 — export의 hereby → 서비스명 간격 146.55 − 40.9497 = 105.6003이, 정본 규격(486 − 134 = 352 @ font-size 100)을 font-size 30으로 환산한 105.6소수점까지 일치합니다

  • 락업 폭 산출 규칙을 문서화(비율은 계산으로 구합니다) — viewBox 폭 = 486 + 서비스명의 커닝 적용 advance(@ font-size 100), 올림. 저장소에 폰트가 있으므로 비율은 계산으로 구하는 값이고 Figma 프레임에서 읽어오는 값이 아닙니다. 이 규칙은 기존 정본 4종(865 · 746 · 702 · 659)을 전부 정확히 재현해 검증했습니다

  • Works 비율 확정hereby Works 790 × 130, hereby 웍스 659 × 130. 국문이 hereby 사인과 같은 659인 것은 한글 글리프 advance가 동일해 2글자 국문 서비스명은 항상 같은 폭이기 때문입니다

  • path 정본도 직접 생성 가능함을 확인·문서화. 같은 절차로 정본 path 5종(hereby · Medical · 메디컬 · Sign · 사인)을 오차 0.00으로 재현해 생성기를 검증했습니다. 생성 시 지킬 값(베이스라인 100.5 · 서비스명 x=486 · 커닝 적용 · Pretendard는 wght=700 인스턴싱)도 함께 적었습니다

  • Works 심볼을 icons.ts에 등록logo-hereby-works. Medical·Sign과 같은 규약(viewBox="1 6 28 28")을 쓰고, Figma export의 일련번호 idlnb-hereby-works-grad로 갈랐습니다. 함께 딸려온 clipPath는 클립 사각형이 심볼 박스와 같아 상단을 0.001 단위만 자르는 no-op(24px 렌더 기준 0.0009px)이라 제거했습니다

  • 데모(DemoBrandLogo.vue)에 Works 락업 2종 추가 — 서비스 선택기에 Works가 붙고, 영문·국문 전환·톤·배경·심볼/워드마크 토글·SVG 내보내기가 모두 동작합니다(파일명 hereby-bi-works-en-dark-on-white.svg 형태로 자동 파생). 심볼은 데모 좌표계(x 2~95 / y 18.5~111.5)로 정렬했습니다

메모

  • 디자인 재요청 없이 완료했습니다. 국문 서비스명 · 비율 · path를 모두 저장소의 폰트로 산출했습니다
  • 생성 path가 디자인 export와 동등함을 픽셀로 확인 — 라이브 텍스트 ↔ path 차이가 Works 1.06%(영문)·1.17%(국문)로 기존 정본 Medical 1.04% · Sign 1.00%와 같은 수준입니다(전부 글자 경계 안티에일리어싱). 대조군 없이 절대값만 보면 오판하므로 기존 정본을 같은 방법으로 함께 재는 것이 핵심이었습니다
  • 남은 것은 자산이 아니라 적용입니다 — 직원 앱이 hereby Medical(로그인 부제·공용 로딩)과 옛 대문자 Hereby Works(<title>·manifest·appName·버전 표기)를 섞어 쓰는 상태. 공용 loading.html은 8개 앱 공유라 앱별 파라미터화가 필요합니다
  • Figma 프레임 폭을 비율로 쓰지 말 것을 danger로 명시 — 받은 export는 영문·국문이 똑같이 258 × 40 인 고정 프레임이라 우측 여백이 각각 26.4 / 59.6 남습니다. 그대로 옮기면 국문 락업에 60 가까운 빈 공간이 붙습니다
  • 커닝을 반드시 적용해야 합니다 — 끄면 Medical384.00(정본 379), hereby338.30(정본 328.20)으로 어긋납니다. 브라우저에서 file://로 woff2를 로드해 재려다 폰트가 폴백되면 값이 통째로 틀리므로, 측정은 폰트 파일에서 직접 해야 합니다(이 작업에서 실제로 한 번 잘못 재고 정정했습니다)
  • 받은 export를 그대로 쓰면 3가지가 깨진다는 점을 문서에 명시 — ① font-family="Pretendard"(등록명은 Pretendard Variable, 정본이 경고해 둔 함정을 그대로 밟음), ② font-weight="bold"(정본 표기는 700), ③ 영문·국문 두 파일이 같은 id(paint0_linear_131_108 · clip0_131_108)를 써서 한 페이지에 둘 다 넣으면 그라디언트가 충돌
  • 직원 앱이 아직 Medical로 표기돼 있습니다. 직전 항목에서 8개 앱 로그인 부제를 일괄로 hereby Medical로 맞췄는데, 직원 앱은 Works이므로 그 정정이 이 앱 한 곳에서는 오답이었습니다. 로딩 화면은 8개 앱이 공유하는 loading.html에 하드코딩돼 있어 문자열만 바꾸면 나머지 7개가 깨집니다 — 앱별 파라미터화가 필요합니다
  • 「아직 정리되지 않은 것」의 사실오류 3건 정정 — ① packages/static/brand/ 자산은 「8개 중 7개 미사용」이 아니라 8개 전부 미사용(랜딩은 .svg를 참조하지 않고 path를 인라인), ② logo-hereby-sign은 「아무 화면도 안 씀」이 아니라 랜딩 데모가 심볼 path를 인라인해 이미 렌더 중(미사용인 것은 icons.ts 엔트리), ③ team-manager 헤더에 남은 125×32 구버전 마스터 락업을 별도 항목으로 분리(화면 주체가 Medical인데 마스터를 쓰는 패밀리 규칙 위반)

2026-07-20 — 브랜드 텍스트 표기 규칙 신설 + 전역 정정 (design/apply-BI-UIcomponents)

추가

  • 브랜드 · BI의 「규칙 (강제)」에 표기 (락업이 아닌 일반 텍스트) 절 신설. 그동안 강제 규칙은 락업(자산) 만 다뤘고, 로그인 제목·푸터처럼 브랜드명을 글자로 적는 자리에는 규칙이 없었습니다. 그 공백에서 Hereby Admin · Hereby Inventory · Hereby Platform 같은 마스터+서비스명 조합이 8개 앱에 퍼졌습니다 — 정작 이 조합은 락업 규칙이 이미 금지한 형태입니다
  • 자리별 표기 기준을 표로 명시 — 제품 화면(로그인·로딩) = hereby Medical / 법인·플랫폼(©·계약서·공문) = hereby / 전 서비스 대상 내부 도구 = hereby. 락업과 같은 기준(그 화면의 주체가 누구인가)을 텍스트에도 적용합니다
  • 「© 문구는 대문자 예외」 통념 폐기. 문서에 근거가 없던 구전 규칙이었고, 저작권자는 개별 서비스가 아니라 플랫폼이므로 © 2026 hereby가 정본입니다

수정

  • 로그인·가입 화면 8개 앱 전부 정정 — 제목은 i18n한 앱 이름(nav.admin → 「관리자」), 부제는 hereby Medical. Hereby Admin · Hereby General Affairs · Hereby Inventory · Hereby Internal Approval이 전부 사라졌습니다. LoginForm.ts · RegisterForm.ts의 폴백 "Hereby"hereby Medical로 교체
  • © 푸터 30곳 → © 2026 hereby (8개 앱의 signin/signup/forgot-password/change-password + 랜딩). 서비스명이던 Platform도 뗐습니다
  • 공용 로딩 화면·재고 대시보드 → hereby Medical / system-admin(전 조직 프로비저닝 내부 도구) → hereby · System Admin / Hereby Signhereby Sign(등록된 패밀리, 대소문자만 정정)
  • i18n 키 3종 추가(ko·en) — nav.admin · nav.employee_app · nav.module_manager. admin 로그인 제목을 하드코딩 대신 i18n으로 넣으려니 키가 없었습니다

메모

  • 남은 미결: 직원 앱 하단의 Hereby Works v1.0.0hereby Works등록된 패밀리가 아니라(Medical·Sign 둘뿐) 별개 제품명인지 판단이 필요해 손대지 않았습니다

2026-07-20 — LNB 브랜드를 패밀리(hereby Medical)로 전환 (design/apply-BI-UIcomponents)

수정

  • LNB 헤더 심볼을 마스터 스우시 → 패밀리 Medical 심볼로 교체iconFor('logo-hereby')iconFor('logo-hereby-medical'). 「그 화면의 주체가 누구인가」 규칙에 따라, admin·총무·경영 대시보드 워크스페이스의 주체는 hereby 제품군 전체가 아니라 Medical 서비스 자신입니다. 적용처는 workspace-switcher.html(공용 파셜 — 총무·경영이 include)과 admin의 인라인 .ws-brand 두 곳
  • 브랜드 라벨 Herebyhereby Medical. 소문자 표기가 정본입니다(hereby는 영문 고유명사). 이에 맞춰 .ws-trigger-labeltext-transform: uppercase를 삭제했습니다 — 남겨두면 HEREBY MEDICAL로 렌더돼 표기 규칙을 깹니다. 대문자 조판용이던 letter-spacing: 0.04em도 함께 제거하고, 두 단어로 길어진 만큼 nowrap + ellipsis 가드를 넣었습니다
  • docs 데모 2종의 인라인 심볼 동기화DemoLnbSidebar.vue · DemoWorkspaceSwitcher.vue. 이 과정에서 두 데모가 그라디언트 stop-colorvar(--color-orange-400/yellow-400)을 쓰던 토큰 규칙 위반도 hex로 정정했습니다(BI 그라디언트는 hex 하드코딩이 정본 — CSS 변수가 없는 곳에서 심볼이 검게 렌더되므로). 그라디언트 id는 문서 전역이라 데모별로 분리(demo-lnb-… / demo-ws-…)

메모

  • 이 자리는 BI 락업이 아닙니다 — 「패밀리 심볼 + 텍스트 서비스명」입니다. 락업은 비율 고정(865×130)이라 최소 높이 24px에서 폭이 160px인데, 208px LNB에서 브랜드 가용 폭은 132px입니다(.ds-lnb 좌우 패딩 24 + 접기 토글 24 + gap 8 + .ws-brand 좌우 패딩 20 차감). 우겨넣으면 높이가 19.8px로 떨어져 최소 높이 규칙 위반이고, 높이를 지키면 토글을 밀어냅니다. 그래서 심볼만 패밀리 정본을 쓰고 서비스명은 일반 텍스트로 뒀습니다. LNB 폭이 넓어지면 락업 승격을 재검토하세요
  • 이로써 logo-hereby-medical첫 실사용처를 얻었습니다. logo-hereby-sign은 여전히 사용처 0이고, 마스터 심볼은 화면 사용처 0(락업 데모·내보내기 전용)이 되었습니다
  • 남은 인접 부채: loading.htmlHereby 대문자 표기, WorkspaceSwitcher.ts triggerLabel"Hereby" 폴백 — 둘 다 LNB 밖이라 이번 범위에서 제외

2026-07-14 — orange 유틸리티 강제 심 제거 (「이름 ≠ 렌더 값」 트랩 해소) (design/landing-update)

수정

  • design-system.css의 orange 유틸 강제 심 32규칙(전부 !important) 삭제 — blue→orange 리맵 시대의 잔재로, .text-orange-500orange-600(#d3440e)으로 렌더되는 등 클래스 이름과 실제 값이 달랐다(랜딩 히어로 타이틀이 챕터 라벨·버튼과 다른 주황으로 보인 원인). 함께 삭제된 "Strong pass" 화석 4규칙 중 .text-orange-600/700 → --ds-color-focus파란색을 가리키던 부비트랩(뒤 블록에 가려 미발현). 이제 Tailwind 유틸리티가 @theme 토큰을 직접 참조 → 이름 = 값
  • 사용처 전수를 실제 렌더 값 기준으로 기계적 리네임(시각 변화 0) — 약 90파일: text-orange-500/700→600 · text-orange-800→700 · text-orange-900→800 · bg-orange-600→500 · bg-orange-700/800/900 → 한 단계씩 하향 · border-orange-600→500 · hover:bg/text-orange-700→600 · focus:ring-orange-400→300. 리네임 제외(심 미적용이라 원래 진짜 값): 슬래시 투명도(bg-orange-600/50) · hover:text-orange-900 · focus:border-orange-500 등 기타 변형 접두사
  • 랜딩의 text-orange-500 12곳은 리네임 대신 text-primary 채택 — 디스플레이 타이틀 강조를 진짜 브랜드 주황(#f66014)으로 복원(의도된 시각 변경, 별도 승인)

문서

  • Buttons · button-system-spec — 레거시 버튼 예시 표기(bg-orange-600 px-3 …)를 새 이름(bg-orange-500 …)으로 동기화

메모

  • 공유 DS의 !important 부채 32건 감소. 남은 인접 부채: .text-red-600/700 → --ds-color-danger · .text-green-600/700 → --ds-color-success · .rounded/.rounded-lg 강제(별도 작업)

2026-07-14 — 패밀리 BI(hereby Medical · hereby Sign) 등록

추가

  • 패밀리 BI 체계 신설. 그동안 BI는 마스터 하나뿐이었고 hereby Medical은 "마스터 심볼 + 오렌지 도메인 워드"인 도메인 락업으로만 정의돼 있었습니다. 이번에 패밀리 사이트는 전용 심볼을 가진다는 것이 확정되어(Medical=십자, Sign=서명 획), 브랜드 · BI에 마스터/패밀리 두 체계를 세웠습니다. 락업은 3종 → 7종(마스터 3 + 패밀리 4)

  • 선택 규칙 — "그 화면의 주체가 누구인가". 같은 hereby Medical을 두 가지로 그릴 수 있게 되었으므로 선택 규칙이 필요합니다: 패밀리 사이트 자신이 주체인 자리(헤더·파비콘·앱 아이콘·로그인) → 패밀리 락업, hereby가 제품군 전체를 거느리는 자리(통합 랜딩·계약서·공문) → 마스터 락업. 도메인 락업은 폐기하지 않고 둘 다 정본으로 유지합니다

  • 패밀리 심볼 2종을 icons.ts에 등록logo-hereby-medical · logo-hereby-sign. 마스터 심볼과 같은 박스(x 2..95 / y 18.5..111.5)에 정렬해, 체계를 바꿔도 워드마크가 제자리에 있습니다. 아직 사용처는 0이며, 패밀리 사이트가 생길 때 붙입니다

  • 패밀리 워드 path 정본 확보Sign(Geist SemiBold 600)·사인·메디컬(Pretendard Bold 700) 아웃라인을 폰트 파일에서 직접 생성. 이로써 7종 전부 path 폴백·path 내보내기가 가능합니다

  • 패밀리는 서비스명이 필수입니다. 패밀리 심볼 + hereby만 쓰는 락업은 없습니다 — 어느 서비스인지 사라져 "마스터의 심볼이 바뀐 것"처럼 읽히기 때문입니다. 서비스명을 뗀 기본 락업(hereby)은 마스터에만 있습니다(심볼만 쓰는 파비콘·앱 아이콘은 예외 — 워드마크 자체가 없으므로)

  • 데모를 체계별로 분리(DemoBrandLogo.vue) — 두 체계는 규칙이 다르므로(특히 서비스명 필수 여부) 한 컨트롤에 섞지 않고 system prop으로 마스터 영역 / 패밀리 영역을 나눠 심었습니다. 마스터 데모는 서비스명 = 없음을 고를 수 있고, 패밀리 데모에는 「없음」이 아예 없습니다. 패밀리 데모에서 MedicalSign을 바꾸면 심볼과 그라디언트가 함께 바뀌고, 내보내는 SVG의 그라디언트도 따라갑니다

  • 심볼만 내보내기 신설 (파비콘·앱 아이콘). 문서는 「24px 미만·파비콘·앱 아이콘 = 심볼만」을 규정해 놓고 정작 그 구성을 만들 수단이 없었습니다 — 심볼 체크박스는 심볼을 끄는 용도라 워드마크만 남았고, 그래서 락업을 받아 디자인 툴에서 잘라 써야 했습니다(자르는 순간 심볼의 크기·여백이 정본에서 벗어납니다). 데모에 「워드마크」 토글을 추가해, 끄면 정사각 심볼(viewBox 2 18.5 93 93)이 그대로 내려받아집니다(hereby-bi-<체계>-symbol.svg). 심볼과 워드마크를 동시에 끌 수는 없고(체크박스가 잠김), 심볼만일 때는 최소 24px 경고를 띄우지 않습니다(그 하한을 벗어나는 방법이 바로 심볼 단독이므로)

  • 내보내는 SVG의 그라디언트 id를 체계별로 분리했습니다(bi-grad-master / -medical / -sign). 종전에는 모든 구성이 bi-grad 하나를 공유해서, 받은 사람이 마스터 락업과 패밀리 락업을 한 페이지에 인라인으로 붙이면 뒤엣것이 앞엣것의 그라디언트로 칠해졌습니다

  • BI 심볼의 그라디언트 stop을 icons.ts에서도 hex로 통일했습니다. 마스터 심볼이 var(--color-orange-400)를 쓰고 있었는데, 이는 이 페이지가 규정한 그라디언트 hex 예외 규칙과 어긋납니다(CSS 변수가 없는 곳에서 심볼이 검게 렌더). 값은 토큰과 동일해 시각 변화는 없습니다

  • 국문 서비스명에도 translate="no". 「메디컬」이 브라우저 자동번역으로 바뀌면 Pretendard Bold 700 자리에 폴백 서체가 들어와 락업이 깨집니다 — 언어별 정본은 우리가 고르는 것이지 브라우저가 바꾸는 것이 아닙니다

  • 내보내는 파일명이 구성을 담습니다(파일명 규칙). 종전에는 배경이 파일명에 없어 내용이 다른 파일이 같은 이름으로 덮어써졌습니다 — 워드마크 색이 배경에서 갈리는데(다크면 흰색, 아니면 검정) hereby-bi-master.svg 하나로 나왔습니다. 이제 hereby-bi-medical-en-dark-on-white.svg처럼 체계·언어·심볼/워드마크 유무·톤·배경이 모두 들어갑니다. 배경에 on-을 붙인 것은 톤(dark)과 배경(white)이 뒤섞여 읽히는 것을 막기 위해서입니다. 라이브 텍스트/path 모드는 일부러 넣지 않았습니다 — 내보내기는 언제나 path라 모드를 바꿔도 파일이 한 바이트도 안 바뀌고(md5 동일 확인), 이름에 livetext를 넣으면 path를 담은 파일이 스스로를 라이브 텍스트라 말하게 됩니다

  • 강제 규칙 정본을 최종 점검했습니다. 본문에만 있고 「규칙 (강제)」 접이 섹션에 빠져 있던 3건을 편입 — ① 국문 서비스명에도 translate="no", ② 파비콘·앱 아이콘은 「워드마크」를 꺼서 심볼만 받고, 락업을 잘라 쓰지 않는다, ③ SVG 그라디언트 id는 문서 전역이라 한 페이지에 BI 자산 둘 이상을 인라인하면 id가 겹쳐선 안 된다(실제로 났던 버그). 라우터(design-system-docs.md)와 CLAUDE.md의 한 줄 가드레일에도 "패밀리는 심볼·그라디언트가 다르다" 를 명시했습니다 — 이번 작업에서 가장 놓치기 쉬웠던 지점입니다

변경 (⚠️ 기존 규칙과 달라진 것)

  • ⚠️ 도메인 락업을 폐기했습니다. 마스터는 hereby 하나뿐입니다. 종전에는 마스터 심볼에 서비스명을 덧붙인 도메인 락업(hereby Medical 오렌지 워드 / hereby 메디컬)이 함께 정본이었으나, 서비스명과 영문/국문 축은 전부 패밀리의 것으로 정리했습니다. hereby는 영문 고유명사라 국문 마스터도 없습니다. 락업은 7종 → 5종(마스터 1 + 패밀리 4). 마스터 데모에서 서비스명·언어 선택기가 사라졌고, 죽은 path 상수(MEDICAL_D·MEDICAL_KO_D, x=473 앵커)도 제거했습니다
  • 이에 따라 서비스명 색 3톤(primary·dark·light)은 패밀리 전용이 되었고, 기본값은 dark 하나입니다(종전에는 "마스터=primary / 패밀리=dark"로 체계마다 달랐습니다)
  • 그라디언트가 두 벌이 되었습니다. 마스터 #F8884F#FF4141#F6BC3D(값이 토큰과 동일, CSS 각도 290°), 패밀리 #FE4D43#FF7732#FFAF00(대응 토큰 없는 전용 색, 314°). 섞어 쓰면 다른 마크가 됩니다. 종래 "BI 그라디언트 = 토큰과 값이 같다"는 서술은 이제 마스터에만 해당합니다
  • 서비스명 색의 기본값이 체계마다 다릅니다 — 마스터 도메인 워드 = primary(오렌지), 패밀리 워드 = dark. 패밀리는 심볼이 이미 그라디언트로 색을 지니므로 워드까지 오렌지면 색이 겹칩니다. 세 톤(primary·dark·light)이 모두 정본인 것은 그대로이고, 배경×톤 금지 조합도 그대로입니다
  • 문서의 「도메인 워드」 표현을 **「서비스명」**으로 통일했습니다(마스터·패밀리 양쪽을 가리키므로)

검증

  • 아웃라인 생성 파이프라인을 먼저 검증했습니다. 이미 정본이 있는 Medical·hereby를 폰트 파일에서 다시 뽑아 기존 값과 대조 → 좌표가 완전 일치(481 · 29.5 · 498.5 · 518.1 …). 그 다음에야 같은 방법으로 Sign·사인을 생성했습니다
  • 생성한 path ↔ 라이브 텍스트 픽셀 비교 → 심볼 0.00%(정확히 일치), 워드는 글자 경계의 안티에일리어싱만. 정본과 바이트 수준으로 같은 hereby가 동일한 오차 패턴을 보이는 것을 기준선으로 삼아 판별했습니다
  • Sign 심볼의 clipPath가 형태를 자르는지 확인 — 래스터 비교로는 902px 차이가 나 "자른다"고 오판할 뻔했으나(clip 레이어가 만든 안티에일리어싱), 경로의 실제 bounds가 (1.0, 6.0002, 29.0, 34.0002)로 clip 사각형과 정확히 일치함을 기하학적으로 확인 → Figma 상용구로 판정하고 단일 path로 구웠습니다
  • 패밀리 락업이 마스터보다 넓은 이유(865 vs 852)를 규명 — 심볼/폰트 비율은 동일(0.930 vs 0.933)하고 hereby와 서비스명 사이 간격만 2.2배(0.108em → 0.238em) 넓습니다. 디자인 의도로 보고 보존했습니다
  • ⚠️ SVG 그라디언트 id는 문서 전역입니다. 데모를 마스터/패밀리 둘로 나누자 두 인스턴스가 같은 id를 정의했고, 브라우저는 문서상 먼저 나온 정의를 써서 패밀리 심볼이 마스터 그라디언트로 칠해졌습니다. 형태(검정/흰색)만 비교하던 검증은 이걸 못 잡았고 — 색까지 비교해서야 드러났습니다. id를 체계별로 분리해 해결했고, 정규화한 패밀리 그라디언트가 전달받은 원본 SVG와 픽셀 색 평균 차이 0.04/255(11만 px)로 일치함을 확인했습니다

2026-07-13 — 브랜드(BI) Foundations 신설 + Geist 브랜드 폰트 도입

규칙 정본 편입

  • BI 페이지를 「docs = 규칙 정본」 체계에 편입. 상단에 규칙 (강제) · 코딩 에이전트용 접이 섹션을 신설해 조판(Geist 600 / Pretendard 700) · 폰트 실패 시 path 폴백 · 번역 금지 · 색과 배경×톤 금지 조합 · 그라디언트 hex 예외 · 형태 제약을 한곳에 모았습니다. 본문의 「하지 말 것」 절은 중복(=드리프트) 이라 제거하고 접이 섹션을 가리키게 했습니다
  • 라우터(design-system-docs.md)에는 한 줄만 추가했습니다(규칙 내용 복제 없음). CLAUDE.md의 Foundations 열거에도 brand/BI를 편입

추가

  • 브랜드 · BI 페이지 신설. 그동안 BI는 문서에 정의 자체가 없었고, 코드에서도 심볼이 icons.ts·랜딩 자산·team-manager 인라인 SVG 세 곳에 중복돼 있었습니다. Figma 정본(Hearby-med Design system)을 근거로 심볼·워드마크·락업 3종(기본 471×130 / 영문 도메인 hereby Medical 852×130 / 국문 도메인 hereby 메디컬 733×130)을 정의하고, 두 가지 정본(path / 라이브 텍스트) 과 사용처 기준을 세웠습니다
  • 조판 규격 확정hereby·MedicalGeist SemiBold 600, 메디컬Pretendard Bold 700, 자간 0. 색은 워드마크 --color-neutral-900(#1B1B1B) · 도메인 워드 --color-orange-500(#F66014) — 세 색 모두 기존 토큰과 정확히 일치해 새 색을 만들지 않았습니다
  • 폰트 로드 실패 시 path로 대체하는 것이 규격. 라이브 텍스트는 "폰트가 도착했을 때만 올라가는 향상"이고 기본값은 항상 path입니다. document.fonts.load() + check()가 통과했을 때만 .brand-font-ready를 붙여 텍스트로 교체하고, 실패하면 path가 그대로 남습니다 — 브랜드 폰트를 못 받은 사용자에게 다른 서체의 워드마크를 보여주는 일은 없어야 하기 때문입니다. 구현 코드는 문서에 포함
  • 구성 변형 2종 정의심볼 on/off(워드마크 단독 사용 / 24px 미만이면 반대로 심볼 단독), 도메인 워드 색 3종(primary --color-orange-500 · dark --color-neutral-900 · light 흰색)을 모두 정본으로 명시. 워드마크 hereby는 배경에 따라 dark/light만 오가고, 세 톤을 오가는 것은 도메인 워드입니다
  • 배경 × 도메인 톤 조합 제약 — 배경에 묻히는 조합은 금지입니다: 밝은 배경(화이트·뉴트럴)에 light ❌, 어두운 배경에 dark ❌. primary는 모든 배경에서 안전합니다. 데모에서도 금지 조합은 버튼이 잠기고, 배경을 바꿔 현재 톤이 금지 조합이 되면 primary자동 보정됩니다
  • SVG 내보내기 — 항상 path. 데모 하단에서 지금 보고 있는 구성(락업 · 심볼 유무 · 톤)을 다운로드/코드 복사할 수 있습니다. 화면에서 라이브 텍스트를 보고 있어도 내보내지는 파일은 언제나 path입니다 — 배포된 자산이 <text>를 품고 있으면 받는 쪽 폰트 환경에 따라 워드마크가 다른 서체로 그려질 수 있기 때문입니다. 색도 hex로 실체화되어 var()가 남지 않습니다(CSS 변수가 없는 곳에서 검게 렌더되는 것을 막음). 즉 라이브 텍스트는 우리 코드 안에서만, 밖으로 나가는 자산은 path로 통일
  • 국문 도메인 락업의 path 정본 확보메디컬을 Pretendard Bold 700 아웃라인으로 생성(Figma와 동일 좌표 x=473, y=100.5, 100px). 레포의 PretendardVariable.woff2 @700 렌더와 픽셀 비교해 강한 차이 0.151%(전부 안티에일리어싱)로 일치 확인. advance width 259.3 → 끝 x가 732.3으로 Figma 캔버스 733과 맞아떨어져 동일 글자꼴임이 교차 검증됨. 이로써 세 락업 모두 path 폴백·path 내보내기가 가능해졌습니다
  • BI 색 정책 — 평면 색은 토큰, 그라디언트는 예외. 워드마크·도메인 워드는 값이 일치하는 기존 토큰(--color-neutral-900 / --color-orange-500)에 맞춥니다. 반면 심볼의 3-stop 그라디언트는 hex 하드코딩을 유지합니다 — BI 자산은 CSS 변수가 없는 컨텍스트(이메일 HTML·단독 SVG 파일·외부 배포본)에서도 열려야 하는데, var()로 두면 그런 곳에서 심볼이 검게 렌더되기 때문입니다. 값 자체는 토큰과 동일하므로, 어긋나서가 아니라 이식성 때문에 두는 예외입니다
  • 워드마크 번역·현지화 금지 규칙. hereby는 영문 고유명사이므로 브라우저 자동번역·접속 지역에 따라 바뀌면 안 됩니다 → 라이브 텍스트로 쓸 때 translate="no" + lang="en" 필수, 제품 UI 기본값은 번역이 원천 불가능한 path
  • 브랜드 폰트 Geist SemiBold 도입(Geist-SemiBold.woff2 · SIL OFL, 46KB). --font-brand 토큰 신설(tokens.css :root + design-system.css @theme → Tailwind font-brand 유틸 자동 생성). @font-facefont-display: block — 기본값 swap이면 폴백 서체가 먼저 보였다 바뀌는데, 워드마크에서 그건 느린 렌더가 아니라 브랜드 사고이기 때문입니다. 폰트는 실제로 워드마크를 그리는 화면에서만 내려받습니다(@font-face는 지연 로드)
  • 라이브 데모(DemoBrandLogo.vue) — 락업 3종 × path ↔ 라이브 텍스트 토글, 폰트 로드 실패 재현, 크기 슬라이더(최소 24px 경고), 보호 영역, 배경 3종

검증

  • path 버전과 라이브 텍스트(Geist SemiBold) 버전을 픽셀 비교 → 형태 차이 0.18%, 전부 글자 경계의 안티에일리어싱. 두 정본은 같아 보인다는 것을 측정으로 확인
  • Figma 그라디언트 3-stop이 기존 토큰과 일치함을 확인 — #F8884F=--color-orange-400, #FF4141=--color-hr-red-400, #F6BC3D=--color-yellow-400. 벡터를 CSS 각도로 환산하면 290° 로, 기존 --gradient-logo 토큰과 같습니다(그동안 사용처 0인 채 방치돼 있던 토큰이 사실 BI 그라디언트였음)
  • Figma의 SVG export를 그대로 쓰면 국문이 깨진다는 점을 확인 — export에는 font-family="Pretendard"로 적히지만 저장소에 등록된 패밀리는 Pretendard Variable이라 폴백됩니다. 문서에 교정 방법을 명시

남은 일 (이번 변경에 포함되지 않음)

  • packages/static/brand/ 자산 8개 중 7개 미사용, 유일 사용처인 랜딩 hereby-wordmark.svg구버전 락업(신규 BI와 비율 상이) → 자산 정본화 필요
  • icons.tslogo-hereby는 stop이 토큰·hex 혼용 상태 → hex로 통일 필요(그라디언트 예외 정책에 맞춤)
  • admin 등 대부분 앱에 파비콘·og:image 부재
  • 다크 배경용 반전 정본 부재 (현재 제공 자산은 라이트 모드 한 벌)

2026-07-13 — 버튼 모서리(radius) 축 신설 (ds-r-* · --ds-btn-radius) (design/btn-radius)

신규

  • --ds-btn-radius 축 + .ds-r-4/-8/-12/-16/-999 모디파이어(design-system.css) — 버튼 모서리를 변형·색·크기와 독립된 축으로 분리. 기본은 종전대로 4px, 클래스를 얹었을 때만 8/12/16/999(알약)로 바뀐다. 굵기 축(--ds-btn-weight · .ds-fw-*)과 동일한 구조: 버튼의 border-radius 선언이 변수를 읽게 만들어 !important 없이 override가 성립한다(기존 4px 강제 3규칙 — button:not([class*="bg-"])… · button[class*="border"] · button[class*="bg-"] — 의 값을 var(--ds-btn-radius, 4px)로 전환). 변수를 읽지 않는 버튼 외 요소는 무영향. 원형 아이콘 버튼(--icon/--circle)은 999px 고정이라 축에서 제외
  • --radius-12: 12px 토큰 — 8↔16 중간 단계. 2배수 progression의 예외로 정식 채택(이전의 "신설 취소" 결정을 뒤집음). rounded-12 유틸도 함께 생성된다. 덕분에 SubTab이 12px 요청을 토큰 부재로 8px로 내려 채택했던 제약도 해소됨(시각 변경 여부는 별도 결정)
  • radius 단일 출처화design-system.css@import하는 tokens.css2·4·6·8·10·12·16이라는 별도 스케일을 갖고 있어 @theme 스케일과 어긋나 있었다. 사용처 0건이던 --radius-6/10/12를 제거해 radius 정의를 @theme 한 곳으로 모았다(6px·10px는 의미 토큰 --ds-radius-sm/--ds-radius-lg로 계속 참조)

문서

  • Buttons — 「모서리(radius)」 섹션 신설(클래스 표·컨테이너 상속·아이콘 버튼 제외), 클래스 API 표에 모서리 축 행 추가. 버튼 탐색기(DemoButtonExplorer.vue)에 모서리 컨트롤(기본 4 · 8 · 12 · 16 · 999) 추가 — 색 × 크기 전 조합에 실시간 적용
  • 모서리 — primitive 표에 --radius-12 행 추가 + 「radius의 단일 출처는 이 스케일 하나」 명시. 트랩 ①의 「커스텀 radius는 <div role="button">으로 우회」 안내를 정식 축(ds-r-*) 안내로 교체(규칙 블록 + 본문), 강제 제외 명단을 실제 셀렉터와 일치하도록 보정(.ds-menu__item·.icon-btn·.ds-btn--icon·.ds-btn--circle·.org-tree__action-btn 누락분 추가), 로드맵·변경 이력 갱신. 버튼 기본 모서리(4px) 유지 여부SubTab 12px 복원 여부를 검토 항목으로 등록
  • Sub-tab — 「12px 토큰 부재로 8px 채택」 메모에 토큰 신설로 선행 조건이 해소됐음을 반영(현재 8px 유지)

결정

  • 버튼 기본 모서리 = 4px 유지 확정 — 탐색기에서 8/12/16/999를 비교한 뒤 내린 결정. 기본값은 그대로 두고, 다른 모서리가 필요한 버튼에만 ds-r-* 축을 얹는다. 기본값 변경 제안은 다시 열지 않는다
  • SubTab radius = 8px 현황 유지 확정--radius-12 신설로 "토큰 부재" 제약은 사라졌으나, 12px 상향은 시각 변경이라 현행 유지. 항목을 닫는다(재개하려면 디자인 재검토 선행)

2026-07-07 — 표·스케줄러·툴팁·필터 DS 정렬 문서화 + 메모 마커 코너 이동 (design/adjust-table)

수정

  • 근무 스케줄표 셀 메모 마커(.memo-corner)를 우상단 → 좌상단으로 이동(admin main.css). 희망근무 보기(prefShow)·출근현황 보기(showAttendance)는 독립 토글이라 함께 켤 수 있는데, 그러면 메모(우상단 삼각형)와 출결 배지(.att-badge, 우상단 원형)가 같은 코너에서 겹쳤음 → 메모를 좌상단으로 옮겨 세 표식이 각기 다른 코너(좌상=메모 / 우상=출결 / 우하=희망근무 .pref-corner)를 차지. 삼각형 방향(빗변 우상→좌하)·주석도 갱신. admin 빌드 green
  • 툴팁 top placement도 상단 잘림 시 아래로 flip 추가(tooltip.ts · design-system.css .ds-tooltip[data-placement="top"][data-flip="down"]). 짧은 임베드 iframe(admin 안 team-manager/hr-system) 상단의 아이콘 버튼에서 툴팁이 프레임 위로 잘리던 문제 해결. 싱글톤 재사용 요소라 show()가 매번 data-flip을 선제거 → 직전 flip 잔재(뒤집힌 화살표·오프셋) 누수도 함께 수정

문서

  • Tooltiptop도 flip 반영(placement 표·동작 원리 flip 초기화·collision flip 항목·로드맵·변경이력)
  • Data table — 「숫자·식별자 표기」(정렬 열 tabular-nums · 식별자 font-mono 금지, 근거는 타이포그래피) + 「빈 상태(<tbody>colspan 행)」 스펙 신설. 자격유형 표(admin 설정)·공지 표(총무)·문서관리(team-manager)를 공유 스켈레톤으로 마이그레이션함을 사용처에 반영
  • 근무 스케줄표 — 「관리자 네이티브 스케줄러 확장」 섹션 신설: 셀 코너 표식 3종(좌상=메모/우상=출결/우하=희망근무 코너 분리) · 희망근무 팔레트(.pref-corner--*·.pref-tint-*·.pref-shift--*를 근무표 D/E/N 공유 토큰 yellow-500·hr-emerald-600·blue-500으로 정렬) · 환자수 행(고정폭 라벨 슬롯 열 정렬 + 접기/펼치기) · sticky 범례 밴드. admin이 .sched-grid 복제본을 갖는다는 점 단일 출처에 명시. 라이브 데모(DemoScheduleTable.vue + ds-sched-demo.css)에 셀 좌상단 메모 세모(출결 배지와 코너 분리 시연)와 「근무 변경」 피커(기존 인라인 순환을 실제 admin 동작과 일치하는 셀 위치 팝오버로 교체 — 더블클릭·드래그로 2칸+ 선택 완료 두 경로 모두 지원, 다중 선택이면 선택 전체 적용) 반영
  • Form controls — 「날짜 범위 필터(프리셋 버튼)」 패턴 신설: dsDatePicker 2개(시작·종료, x-effect 교차 clamp) + 프리셋 줄(이번주/지난주/이번달/지난달/최근 3개월 | 전체) + --icon --muted 초기화. 공유 헬퍼 resolveDatePresetRange/datePresetBtnClass 재사용
  • Buttons — 사용 규칙에 「리스트 행 상세·보기 = line · normal · sm」 추가 + 즉석 색 pill 금지·--line은 톤 필수 tip
  • 모달 — 「상세(읽기전용) 모달」 섹션 신설(공지 상세: 넓은 셸·이전/다음 --icon·닫기 --white-bk·메타 ds-label·x-text 전용). §1에 임베드 앱 백드롭 오프셋(ga-embedded/tm-embedded padding-bottom: 10vh), §4에 모달 안 탭 바 tabnav-flush-left 규칙 추가

메모

  • 이 브랜치의 실제 토큰화(10/11/12px → text-c2/c1/b6, tabular-nums, font-mono 제거)는 이미 타이포그래피·Data table에 규정된 규칙을 적용한 것이라 신규 토큰·컴포넌트는 없음 — 문서는 사용처·예시·근거 링크만 보강. 가짜 데이터(전자서명·공지)·푸시 설정 이관 등은 DS 문서 대상 아님(코드 리뷰 이슈로 별도 처리)

2026-07-04 — 전역(시스템) 팝업 z-index 티어 신설 (--ds-z-modal-system · .ds-modal-backdrop--system) (design/adjust-modal-employee-management)

신규

  • --ds-z-modal-system: 75 + .ds-modal-backdrop--system — 권한 요청(permission-denied-modal)처럼 SDK/전역 이벤트로 어떤 화면·모달 스택 위에서든 뜨는 전역 팝업 전용 티어(design-system.css). alert(70)와 toast(80) 사이. 쌓임 원칙: 팝업은 최신 표시 순서대로 위에 쌓인다 — 전역 팝업은 항상 마지막에 뜨므로 최상단 모달 티어를 가진다. 기본 티어(z-50)로 두면 첨부파일 미리보기 뷰어(--top, z-70) 위에서 발생한 403 권한 팝업이 뷰어 아래에 깔려 보이지 않던 문제(직원 수정 팝업 자격증/면허 탭에서 발견) 해결. permission-denied-modal에 첫 적용

문서

  • 모달 §단일 출처·규칙 — z 토큰 표에 alert·system 행 추가, 쌓임 원칙 명시, --top 낡은 z-60 표기를 실제 값(z-70)으로 교정

2026-07-03 — 상태 배지(.ds-status-badge) 색을 등록 라벨 팔레트로 통일 (design/adjust-modal-employee-management)

신규

  • .ds-status-badge--success — 누락돼 있던 성공(활성) 톤 모디파이어 추가(design-system.css). --success 정의가 없어 활성·재직·복직 배지가 base 스타일(투명 배경)로 떨어져 회색 평문처럼 보이던 문제 해결(직원 수정 팝업 자격증/면허 탭에서 노출)

변경

  • 상태 배지 4종(--success·--warning·--danger·--neutral) 색을 등록 라벨(.ds-label) secondary 팔레트로 통일(design-system.css). 기존 진한 --color-*-200(bg)/-300(border)/-800(text) 톤은 등록 라벨(연한 bg + 밝은 글자, solid는 테두리 없음)과 드리프트가 있었음 → 라벨 solid와 동일 색으로: success=--ds-secondary-green-bg/--ds-secondary-green, warning=--ds-secondary-yellow-bg/--color-yellow-500, danger=--ds-secondary-red-bg/--ds-color-danger, neutral=.ds-label--gray 값 미러링. base의 1px solid transparent 테두리를 유지(모디파이어가 border-color 미지정)해 라벨 solid처럼 테두리 없이 렌더. 앱 전역 상태 배지 11곳(활성·휴직·퇴직·비활성 등)에 일괄 반영

문서

2026-07-03 — 패널 모달 최소 높이 720px 변형 (.ds-modal--panel-tall) (design/adjust-modal-employee-management)

신규

  • .ds-modal--panel-tall — 패널 모달(.ds-modal--panel)에 얹는 최소 높이 변형(design-system.css). 기본 min-height: 300px를 **720px**로만 재정의 — 입력 필드가 많아 세로 공간이 넉넉해야 하는 폼형 패널에 사용. 낮은 뷰포트에서 height(뷰포트−여백)가 720px 밑으로 줄어도 최소 720px 확보(그 아래로는 본문 스크롤). 높이 축만 재정의하므로 -narrow(폭)·-loose(상하 여백)와 그대로 조합. 직원 추가·직원 수정 팝업에 첫 적용(둘 다 -loose·-narrow와 병용) — 유형·기준 통합 관리는 기존 min-height 300 유지

문서

2026-07-02 — 패널 모달 좌우 여백 2배 변형 (.ds-modal--panel-narrow) (design/adjust-modal-employee-management)

신규

  • .ds-modal--panel-narrow — 패널 모달(.ds-modal--panel)에 얹는 좌우 여백 2배(100 → 200px) 변형(design-system.css). width: min(calc(100vw - 400px), 1280px)만 재정의하고 상하·최소 규격(min-width 768 · min-height 300)은 기본 패널과 동일 — 폭이 넓을 필요가 없는 폼형 패널에 사용. 뷰포트 1680px 이상에서는 최대 폭 1280 캡이 먼저 걸려 기본 패널과 동일. -loose(상하축)와 축이 달라 함께 얹을 수 있음. 직원 추가(loose와 병용)·직원 수정 팝업에 첫 적용 — 유형·기준 통합 관리는 기존 좌우 100px 유지

문서

2026-07-02 — 패널 모달 상하 여백 3배 변형 (.ds-modal--panel-loose) (design/adjust-modal-employee-management)

신규

  • .ds-modal--panel-loose — 패널 모달(.ds-modal--panel)에 얹는 상하 여백 3배(60 → 180px) 변형(design-system.css). height: calc(100vh - 360px)만 재정의하고 폭·최소 규격(min-width 768 · min-height 300)은 기본 패널과 동일 — 뷰포트 대비 콘텐츠가 가벼운 폼형 패널에 사용. 직원 추가 팝업에 첫 적용

문서

2026-07-02 — LNB 배지를 DS .ds-label로 렌더 + 다크 서피스 tone 적용 (design/lnb-disabled-menu)

변경

  • LNB 배지가 전용 .ds-lnb__badge 일회성 스타일 대신 DS .ds-label로 렌더(LnbSidebar.ts). LnbItem.badgestring \| number(알림 카운트 필, primary·round) 또는 LnbBadge 객체(tone·line·shape·size로 DS 라벨 변형 지정)를 받는다. .ds-lnb__badge는 접힘 레일 숨김 훅으로만 남김
  • 다크 레일용 tone 적용 — LNB 레일이 어두워(neutral-900) 준비중 라벨에 밝은 배경용 light-gray(어두운 글자)를 쓰면 안 보였음 → 다크 서피스 tone light-gray-dark(solid·square·xs)로 교체. LnbBadgeTonegray-dark·light-gray-dark 추가. admin 총무 준비 메뉴(대시보드·총무·관리)에 "준비중" 배지 + disabled 적용

문서

  • LnbSidebar — 배지 설명을 DS 라벨 기반으로 갱신, LnbBadge 타입·다크 레일 tone 주의 추가, 라이브 미리보기(<DemoLnbSidebar>)의 준비중 배지를 light-gray-dark로 렌더

2026-07-02 — 다크 배경용 라벨 색 변형 신설 (--gray-dark · --light-gray-dark) (design/ds-label-dark-tone)

신규

  • 다크 bg 위에서 쓰는 라벨 색 변형 --gray-dark · --light-gray-dark 추가(design-system.css). 기존 --gray/--light-gray는 밝은 배경용(어두운 텍스트)이라 어두운 배경 위에 얹을 중립 라벨이 없었음. 두 변형은 그 반대로 밝은 텍스트 + 흰색 반투명 채움(glass)--gray-dark는 글자 --ds-label-white + 배경 --ds-glass-18(라인 테두리 --ds-glass-30), --light-gray-dark는 글자 --ds-glass-70(라이트 그레이) + 배경 --ds-glass-08(라인 테두리 --ds-glass-18). solid·--line 모두 지원

문서

  • Label §색 변형 표에 --gray-dark·--light-gray-dark 행 추가(색 10종 → 12종). 라이브 미리보기(<DemoLabelExplorer>)에 두 컬럼 추가 — light 라벨이라 진짜 어두운 바탕(--color-neutral-800)에 얹어 표시하고, 설명란에 "다크 bg 위에서 쓰는 라벨" 주석 추가

2026-07-01 — 패널 모달(.ds-modal--panel) 문서 등록 + LNB 인-모달 변형 (design/adjust-modal-attendence)

문서

  • 모달 → 패널 모달 섹션 신규 — 근태 「유형·기준 통합 관리」에 첫 적용된 좌측 메뉴 + 우측 현황 2단 큰 팝업 변형. 페이지 목차(우측 아웃라인)에 "패널 모달" 항목으로 노출. 인터랙티브 라이브 예시(<DemoPanelModal> — 좌측 메뉴로 5개 섹션 전환·닫기/다시 열기), 사이즈 규격(width min(100vw-200px,1280) · min-width 768 · height 100vh-120), 구조(좌측 220px 메뉴·우측 헤더/툴바/현황 표), 중첩 자식 ESC 가드(typeManagerChildOpen·stopImmediatePropagation·500ms 유예) 정리. "어디서 쓰이나"에 type-manager.html 추가
  • LnbSidebar → 패널 모달 내비게이션 섹션 신규 — 패널 모달 좌측 메뉴를 LNB 패턴의 인-모달 경량 변형으로 등록. 앱 LNB(.ds-lnb·다크 레일) vs 패널 메뉴(밝은 배경·플랫·현황 전환) 비교표 + 모달 문서로 바로가기 링크. 아이콘 일치 규칙(출처 페이지 정체성 아이콘 재사용) 공유 명시. "사용처"에도 패널 메뉴 항목 추가
  • <DemoPanelModal> 데모 컴포넌트 신규(theme/components) — 실제 .ds-modal--panel/.ds-modal__close/.type-manager__nav-icon/.ds-label/.demo-admin 표 CSS로 렌더, 백드롭을 stage 안에 가두는 데모 보정만 스코프 스타일로 추가(하드코딩 hex·새 !important 0)

2026-06-30 — 근태 현황 날짜 필터 커스텀 달력(dsDatePicker) 일괄 전환 + --32 사이즈 (design/adjust-modal-attendence)

신규

  • .ds-datepicker--32 사이즈 모디파이어 — 트리거 32px. 필터 줄의 ds-select--32/ds-input--32와 높이를 정렬하기 위한 중간 크기(기존: 기본 40px · --sm 28px). design-system.css .ds-datepicker--32

변경

  • 근태 현황 페이지의 모든 날짜 필터를 네이티브 <input type="date"> → 커스텀 달력(dsDatePicker)으로 일괄 전환 — 근무(출퇴근 현황)·연차(연차/휴가 결재)·연장근무·외근(결재) 탭의 기간 from~to 입력 10개. 모두 ds-datepicker--32로 필터 줄과 높이 정렬, onChange로 기존 @change와 동일하게 즉시 재조회
  • 연장근무 탭 '기간 드롭다운(초안→적용)'을 인라인 from~to 커스텀 피커(즉시 적용)로 통일 — 커스텀 달력 팝오버는 body로 teleport되어 ms-dropdown(바깥 클릭 닫힘) 안에 중첩하면 달력 클릭 시 드롭다운이 닫히는 충돌이 있어, 다른 탭과 동일한 인라인 방식으로 정리. 자주 쓰는 기간(이번주/지난주/…) 프리셋 버튼은 유지

문서

  • Form controls — 컴팩트 트리거 항목에 ds-datepicker--32(32px, 필터 줄 정렬) 추가(--sm 28px와 병기)

2026-06-30 — SubTab(낱개 하위 탭) 신규 (design/adjust-modal-attendence)

신규

  • SubTab 컴포넌트 신규 — 상위 TabNav(밑줄형) 아래에서 쓰는 낱개 하위 탭. 낱개 탭이 간격을 두고 나열되고 선택(active) 탭만 강조, 비선택은 공통 neutral 라인 테두리. .ds-segment(회색 통 안에서 흰 pill 이동)와 구분되는 별도 형태. CSS .ds-sub-tab / .ds-sub-tab__item(design-system.css) + Alpine subTab(SubTab.ts · sub-tab.html)
  • 축(axis) 4종 — size md(36px·b5 13px) / ml(44px·b4 14px) · radius 4/8/16(--radius-*, 기본 8) · color dark/primary · fill solid/line. color×fill로 선택 강조 4종(dark·solid 기본 = 검정 채움+흰 글자). 모두 subTab({ size, radius, color, fill }) 옵션 → subTabGroupClass 모디파이어로 반영
  • 토큰만 사용 — color/radius/typography/geometry 모두 DS 토큰. 하드코딩 hex·arbitrary [...]·새 !important 0

문서

  • SubTab 페이지 신규 — 인터랙티브 라이브 미리보기(<DemoSubTab controls> — size·radius·color·fill 4축 클릭 토글), 축 4종 표·미리보기, API/토큰. Components — Navigation 사이드바에 SubTab 추가
  • 상위 TabNav와의 계층 데모(<DemoSubTabHierarchy>) — 상위(밑줄) → 하위를 한 컨테이너에 쌓고, 동일 배경색 공유 + 상·하위 탭 사이 간격 24px로 위계를 시각화
  • 검토 필요: radius 중간값은 요청 12였으나 시스템에 12px radius 토큰이 없어 인접한 --radius-8(8px) 사용 — 12px 필요 시 --radius-12 토큰 신규 추가 선행

2026-06-30 — 탭 크기 2단계 변형(md/ml) + 하단 구분선 on/off (design/adjust-modal-attendence)

신규

  • TabNav에 크기축(size) 추가 — 기존 단일 사이즈를 md(기본, 현행: 40px · padding 10/12 · b5 13px) / ml(48px · padding 12/16 · b4 14px) 2단계로 변형. CSS .ds-tab-item--ml 모디파이어 신설(design-system.css, .ds-tab-*). size축은 padding·높이·라벨 폰트만 바꾸고 색·언더라인 두께(2px)·hover는 공통
  • 활성 하단바 오프셋 크기별 재계산 — 라벨이 세로 중앙이라 오프셋 = (탭높이 − 라벨높이)/2. md -11px(기존) / ml -14px
  • 컴포넌트 옵션 sizetabNav({ size: 'md' | 'ml' })(기본 md) + tabNavSizeClass getter가 각 .ds-tab-item에 모디파이어를 반영(TabNav.ts · tab-nav.html). 기존 호출부는 옵션 생략 시 그대로 md — 비파괴

변경

  • 탭 바 하단 구분선(회색 1px)을 DS 소유로 이전 + on/off — 기존엔 partial markup의 Tailwind border-b border-neutral-200였던 것을 nav.ds-tab-nav { border-bottom: 1px solid var(--color-neutral-200) }로 DS가 소유. 끄는 모디파이어 .ds-tab-nav--flush(border-bottom: 0) 신설. 컴포넌트 옵션 tabNav({ divider: boolean })(기본 true) + tabNavDividerClass getter. 기본값은 기존과 동일 1px — 비파괴
  • 활성 탭 언더라인을 하단 구분선과 overlap 정렬 — 활성 2px 검정바(.ds-tab-underline)가 회색 1px 구분선 바로 위에 붙어 "2줄"로 보이던 것을, 구분선을 덮도록 오프셋을 보더 1px만큼 더 내림(md -11px→-12px / ml -14px→-15px). 활성 탭은 검정 한 줄, 비활성 탭은 회색 한 줄

문서

  • TabNav "크기 (size)" 섹션 신설 — md/ml 표 + 라이브 미리보기(<DemoTabNav size="ml">). API에 size 옵션·tabNavSizeClass 추가, 사용된 토큰(Typography b4·Geometry per-size) 갱신
  • TabNav — 하단 구분선 "하단 구분선 (divider)" 섹션 신설 — on/off 라이브 미리보기(<DemoTabNav :divider="false">) + 활성 바(2px)와 구분선(1px)이 별개임을 명시. API에 divider 옵션·tabNavDividerClass 추가
  • <DemoTabNav> 인터랙티브 컨트롤controls prop 추가로 미리보기 상단에서 체크박스로 ml 크기·divider를 토글(시각 미리보기 §0). 데모 탭 둘레 20px 여백으로 하단 구분선 가시성 개선

2026-06-26 — 모달 텍스트 색 위계 + 인풋 타이틀·간격 표준 (design/adjust-modal-addmember)

변경

  • 모달 안 텍스트 색을 label-* 시맨틱 토큰 3단 위계로 통일 — 모달 타이틀·그룹 타이틀 = label-normal(neutral-900), 모달 디스크립션·인풋 타이틀 = label-assistive(neutral-500), 기타 보조 설명 = label-muted(neutral-400). raw neutral-* 대신 시맨틱 토큰 사용(테마 대응)
  • 인풋(필드) 타이틀 색 text-neutral-600text-label-assistive 전앱 통일 — 221곳(admin·team-manager·general-affairs·management-dashboard·inventory + 공유 컴포넌트/레퍼런스 위저드). neutral-600(204)·neutral-700(10)로 혼재하던 것을 한 토큰으로. (모바일 employee-app·급여 hr-system·시스템툴 제외)
  • 그룹(섹션) 타이틀 14px(b4) → 16px(b3) — 인풋 타이틀(13px)과 위계 분리
  • 좌우로 놓인 인풋 사이 간격 = 12px(gap-3) 명문화 — 직원추가 모달이 이미 쓰던 값, gap-2(8px)였던 ward-settings 등 정렬

문서

  • 모달 패턴 §4 — 타이포·간격 표에 모달 디스크립션·그룹 타이틀·좌우 인풋 간격 행 추가 + "텍스트 색 위계" 표 신설(normal/assistive/muted). 예제 라벨을 text-label-assistive·보조설명 text-label-muted로 갱신

2026-06-26 — 숫자 입력 변형 (ds-input--number) 신설

신규

  • <input type="number">용 공유 변형 ds-input--number 추가(design-system.css). 네이티브 증감 화살표(스피너)를 평소엔 숨기고 hover/focus 시에만 노출 — 평소엔 가운데 정렬 숫자가 화살표에 밀리지 않고 깔끔하게, 조작 시엔 step 단위 증감(▲▼)이 가능(키보드 /는 항상 동작). 숫자 자리수 정렬용 font-variant-numeric: tabular-nums 포함
  • 조합형 변형 — 숫자 고유 처리(스피너 + tabular-nums)만 담당. 크기 ds-input--XX · 너비 w-full · 가운데 정렬 text-center 유틸과 조합(정렬은 선택)
  • 흩어진 핵을 대체할 단일 기준 — 기존엔 admin .spinner-on-hover, .patient-count-input·.sched-grid input[type=number](항상 숨김), team-manager 인라인 style="-moz-appearance:textfield"로 제각각이었음. 그중 검증된 hover/focus 노출 동작(admin .spinner-on-hover)을 공유 DS로 승격

적용

  • scheduler 근무지 설정(ward-settings) 의 간호사·조무사·간병인 비율 입력칸(admin · team-manager) — 기존 ds-input--40 w-full text-center(스피너 처리 없음 → 화살표가 가운데 숫자를 밀던 상태)에 ds-input--number 추가

문서

  • Form controls "숫자 입력 (ds-input--number)" 섹션 신설 + 라이브 미리보기(<DemoFormControls>)에 숫자 토글 추가

2026-06-26 — 시간 입력 피커 = teleport 드롭다운으로 전환 (dsTimePicker)

변경

  • 시간 입력 피커를 dim 중앙 다이얼로그 → 인풋 아래 드롭다운으로 전환(날짜 피커와 동작 통일). 기존 te* 인라인 로직(.ds-modal-backdrop--top + .ds-mini-modal 중앙 다이얼로그)을 공유 컴포넌트 dsTimePicker(Alpine 팩토리 DsTimePicker.ts + 마크업 partial ds-time-picker.html + .ds-timepicker* CSS)로 추출. 사용처는 호스트 div + include 한 줄, x-modelable="value" x-model="..."HH:MM(24h) 바인딩
  • 동작: x-teleport="body" + position:fixed로 트리거 아래 드롭다운 — 모달 overflow·filter 탈출해 안 잘림, dim 없음, z = 모달 위 1레이어. Esc·바깥클릭·취소 = 피커만 닫힘(모달 유지). 12h 표시 / 24h 저장 · 17→오후5 정규화 · 확인은 유효+변경 시만 활성 (te* 동작 보존)
  • 적용: admin shift-form(근무 설정 시작/종료시간) · general-affairs notice-form(공지 만료일시 = 네이티브 날짜 + 커스텀 시간 드롭다운). 닫기 이벤트 ds-picker-close로 날짜·시간 피커 통일(동시 1개만 열림)
  • Form controls "구성 (커스텀 — dsTimePicker)"로 갱신 + ## 피커 인트로 "시간 = 커스텀 다이얼로그" → "커스텀 드롭다운"

메모

  • docs 라이브 데모(<DemoTimeInput>/<DemoDateTime>)는 아직 중앙 다이얼로그 형태(미리보기 전용) — 후속으로 드롭다운 형태에 맞춰 갱신 예정

2026-06-26 — 커스텀 날짜 달력 컴포넌트(dsDatePicker) 신설 + 앱 적용 (design/adjust-modal-addmember)

신규

  • Form controls "날짜 선택기 구성 (커스텀 — dsDatePicker)" 신설 — 네이티브 type=date를 대체하는 공유 커스텀 달력을 실제 컴포넌트로 등록. Alpine 컴포넌트 DsDatePicker.ts(@hereby/components, createAuthComponentsPlugin 등록 → 전 앱 사용 가능) + 공유 마크업 partial ds-date-picker.html + .ds-datepicker* CSS(design-system.css). 사용처는 호스트 div + include 한 줄(로직·외형·마크업 모두 공유, 중복 없음)
  • 팝오버 = x-teleport="body" + position:fixed 드롭다운 — 트리거 바로 아래에 뜨되 모달의 overflow-y-auto·filter탈출해 절대 안 잘림(모달 밖으로 넘어가도 OK). dim 없음, z = 모달 위 1레이어(--ds-z-modal-top). 닫기 = Esc · 바깥 클릭 · 날짜 선택 → 피커만 닫힘(모달 유지). 좌우 clamp·위로 flip·동시 1개만 열림 보정 포함
  • 옵션·확장: dsDatePicker({ min, max, placeholder, onChange })(범위 밖 날짜 비활성) · 비활성은 호스트에 x-effect="disabled = (조건)" · 선택 시 파생 로직은 onChange 콜백(유저 pick/clear/today에만 발화 — 네이티브 @change 동등, 프로그램적 로드엔 안 뜸). 핵심 로직은 단위 테스트로 커버(DsDatePicker.test.ts·DsTimePicker.test.ts)

적용 (12필드 — 데스크톱 폼 전수)

  • general-affairs 이수기록(이수일·만료일)·실행기록(예정일) · admin 자격증(발급일·최근신고일·차기신고기간 — max·비활성·신고일→차기기한 자동계산 보존)·스케줄러 희망일(item.date) · hr-system 연장근무·치료세션(근무일) · inventory 품목(유효기간) · team-manager 치료 인센티브(날짜)·희망일(item.date)
  • 컴팩트 변형 ds-datepicker--sm(트리거 28px) 신설 — 스케줄러/팀 희망일 목록의 좁은 인라인 행(x-for, text-xs 형제와 같은 높이)에 적용
  • 보류: 데스크톱 범위필터(From/To, 카테고리 B) · 모바일 employee-app(Capacitor 네이티브 휠 의도 — x-clamp-year)

정정

  • 기존 "단일 날짜는 그대로 네이티브 type=date(커스텀 달력 안 만듦)" 서술을 정정 — 커스텀 달력 그리드는 단독·결합 모두 사용(단독 = teleport 드롭다운 / 결합 = 한 팝업 인라인). ## 피커 인트로의 "날짜 = 네이티브"도 "날짜 = 커스텀 달력(dsDatePicker)"으로 수정

2026-06-25 — 시간 입력 피커 — Form controls 섹션 + Modal 내부 피커 등록 (design/adjust-modal-addmember)

신규

  • Form controls 문서를 Input · Select · Picker 3패밀리로 정리 — 제목에 Picker 추가, ## 피커 (Picker) 상위 섹션 신설하고 달력(날짜·월)·시간 입력을 그 아래로 그룹화(#calendar/#time-input 앵커 유지). 피커 = 모달·페이지 공용 패밀리(모달 전용 아님)
  • Form controls "시간 입력 (Time input)" 섹션 + 라이브 데모(<DemoTimeInput>) 신설 — 안내형 12시간 입력 다이얼로그를 DS 패턴으로 등록. 오전/오후 드롭다운(ds-select--40) + 시/분 키보드 입력(ds-input--40) + 위·아래 스텝 아이콘 버튼(간격 6px) + 취소/확인(ds-btn--solid white-bk/dark --md), .ds-mini-modal on .ds-modal-backdrop--top(z60). 표시 = 12시간제 · 저장 = 24시간제 HH:MM
  • 값 규칙 명문화: 시 칸은 24시간제 입력 허용(17 → 오후 5시 자동 정규화), 자정 = 오전 12 · 정오 = 오후 12. 확인 버튼은 시 0–23·분 0–59 순수 숫자 AND 원래 값에서 변경일 때만 활성(숫자 아님/무변경이면 비활성)
  • 날짜 = 커스텀 달력 우선 (정책 전환) + 단독 커스텀 날짜 피커 데모(<DemoCustomDate>) 신설 — 결합 피커와 동일한 커스텀 달력(연도 드롭다운 · 월 이동 ‹ › · 삭제 · 오늘 · 오늘=채움/다른 날=테두리 · 바깥클릭 닫기)을 단독 날짜 선택에도 추가. 앞으로 날짜는 커스텀으로 통일·우선하고 네이티브 type=date는 대안으로 유지(점진 전환). 기존 "날짜=시스템 기본, 커스텀 안 만듦" 정책을 뒤집음
  • 날짜 + 시간 결합 정책 + 라이브 데모(<DemoDateTime>) 신설({#datetime}) — 네이티브 datetime-local(OS마다 외형/동작 상이, 공지 만료일시가 이 케이스)을 한 팝업에 [커스텀 달력 그리드(좌) + 커스텀 시간 폼(우)] 결합 뷰로 대체. ⚠️ 달력 커스텀은 결합 케이스 한정(단일 날짜는 그대로 네이티브 type=date — 네이티브 달력은 우리 시간 폼과 한 팝업에 못 합치기 때문). 마이그레이션 대상: 공지 작성 만료일시 등 datetime-local은 이 결합 뷰로 교체 권장
  • Modal "§7 모달 안의 피커" — 짧은 참조만 추가. 피커(날짜·월·시간)는 공용 폼 컨트롤이라 home은 Form controls(모달 전용 아님). 모달 문서엔 모달 안에서 쓸 때만 더해지는 규칙(다이얼로그형 = .ds-modal-backdrop--top 모달 위 · 팝오버형 = 바디 overflow-y-auto 비잘림 · Esc/바깥클릭은 피커만 닫고 모달 유지)만 명시

메모

  • 달력(날짜 = 네이티브 type=date · 월 = 커스텀 월 선택기)은 기존 "달력(날짜·월 선택)" 섹션 유지 — 이번엔 시간 입력만 추가(사용자 결정: 달력은 기존 기본 사용 + 커스텀 뷰 조합)
  • 원본 구현: shift-form.html 근무 설정 시작/종료시간 (teOpenFor/te24/teNormHour/teStep*/teCanConfirm)

2026-06-25 — 모달·얼럿 푸터 버튼 강조 규칙 (위치 → 맥락·중요도, weight 3티어) (design/adjust-modal-addmember)

신규

  • Modal "§6 푸터 버튼 — 강조는 위치가 아니라 맥락·중요도로" 섹션 신설. 그동안 문서가 정의하지 않던(라이브 데모로만 암묵 표준이던) 푸터 확정 버튼 강조를 2축으로 명문화: ① 위계(가장 무거운 버튼 = 권장 액션 — "우측이라서"가 아니라 "권장 액션이라서") × ② weight 3티어(중요도로 무게 결정). 통합 버튼 체계ds-btn 조합 그대로 — 새 클래스 없음
  • weight 3티어: 기본/권장 컨펌 = solid · dark(대부분) · 크리티컬 컨펌(삭제·비가역) = solid · lightor(연주황 틴트, 드물게) · 취소·닫기 등 물러나는 동작 = solid · white-bk(흰 모달 위 텍스트만). 위계 dark > lightor > white-bk. "가장 우측이니 orange/dark 채움" 관성 폐기
  • 위험 반전: 데이터 손실 확인(변경 폐기·삭제 직전)은 안전한 선택을 권장 버튼(dark)으로 올리고 파괴 동작은 약화(white-bk)/lightor, 자동 포커스·기본 Enter는 취소에. 라이브 푸터 강조 갤러리(일반/크리티컬/위험반전 3종, 실제 ds-btn 렌더) 추가

변경

  • Modal 라이브 데모를 새 규칙에 동기화 — 저장(권장) = dark, 취소 = white-bk, 저장확인 팝업은 계속편집(안전) = dark · 닫기 = white-bk로 위험 반전 적용, "모달 다시 열기" 유틸도 dark(데모에서 orange 제거). "목표 & 로드맵"에 푸터 버튼 강조 정렬 행 추가
  • Buttons "사용 규칙(맥락→조합)" 표 정정 — 모달·얼럿 푸터 행을 권장 = solid · dark · 크리티컬 = solid · lightor · 취소·닫기 = solid · white-bk 3티어로 교체(기존 solid · primary/line · dark), 모달 푸터 강조 규칙으로 연결하는 안내 추가. 의미색 green/red는 모달 밖 인라인/툴바/워크플로 컨텍스트로 한정 명시

수정

  • ds-btn light 변형 hover/pressed 오버레이 색 매칭 (design-system.css) — lightred/lightgreen/lightblue/lightpurple/lightyellow/lightaquablue가 hover overlay 그룹에서 빠져 default --color-label-muted(회색)를 써, 거의 흰 -50 채움 위에서 hover 시 회색으로 바래던 문제 수정. 각자 의미색(-600/yellow -700)으로 --ds-btn-ov-hf/--ds-btn-ov-pr 매칭 — lightor--color-primary(orange)로 매칭하던 것과 동일 패턴. 문서 결재(doc-preview) 반려(lightred) hover가 빨강으로 진해짐. Buttons "상태" 설명 동기화

메모

  • 모달 내 워크플로 승인/반려는 예외로 확정 — 문서 결재(doc-preview) 승인=green·반려=red(연한 시작 lightred)는 의미가 강해 의미색 유지하되 DS 통합 버튼(ds-btn--solid)으로 구현(기존 bg-green-600/bg-red-* 유틸 교체), 같은 푸터 닫기·취소는 white-bk. §6 "현황"에 예외 명문화
  • 앱 마이그레이션은 버튼 체계 마이그레이션과 함께 점진 진행. 급여(hr-system)는 표준 적용 제외

2026-06-24 — Notice danger 변형 + 문서 신설 (design/adjust-modal-addmember)

신규

  • Notice 컴포넌트 문서 신설 — .ds-notice 베이스 + 변형(--info/--warning/--success/--danger)의 라이브 미리보기·규격·토큰·적용 현황. 등록은 돼 있었으나 전용 문서가 0이던 인라인 알림 callout(피드백 그룹에 추가)
  • .ds-notice--danger 변형 신설(오류·반려: red-50/200/700, 아이콘 red-500) — 기존 info/warning/success와 동일 규격

변경

  • 문서 결재 미리보기(team-manager doc-preview) 반려 사유 + PermissionDeniedModal 요청 실패 에러의 임시 bg-red-50 border-red-200 text-red-* 조합을 .ds-notice--danger 표준 변형으로 정규화 — 반려 사유엔 경고 아이콘(.ds-notice__icon) 추가, notice 아래 footer 구분선 제거(빨간 박스가 시각 구분 제공)
  • 문서관리 목록(team-manager document-management)의 구분·상태 배지를 앱 로컬 .badge/임시 bg-purple-100·bg-orange-100에서 DS ds-label(--blue/--primary/--yellow/--green/--red/--gray)로 통일 — 같은 데이터를 띄우는 미리보기 팝업과 배지 컴포넌트·색 매핑 일치(취소=gray 분리 포함)
  • Modal 라이브 예시 닫기(.modal-demo-x)에 hover 상태 추가 — doc 전용 클래스라 hover 규칙이 빠져 마우스를 올려도 변화가 없던 문제. 실제 앱 모달(admin/ga/md)의 표준 닫기 버튼 .ds-modal__close 와 동일하게 재현: 24×24 + 16px SVG ✕, 투명 배경 → hover 시 neutral-100이 ✕ 뒤에 차오름(공유 button{border-radius:4px!important} 강제 규칙으로 라운드 사각형 렌더, 앱과 동일)·focus-visible 윤곽선. 입력·드롭다운은 전역 input/.ds-select 규칙으로 이미 hover·focus 반영됨

2026-06-23 — 모달 데모 타이틀·마진 표준 동기화 (design/adjust-modal-addmember)

변경

  • Modal 라이브 예시 모달 타이틀(.modal-demo-title)을 16px/700 → 20px/600으로 수정 — 실제 ds-modal__title과 어긋나 있던 데모 값을 표준에 동기화(line-height 1.4·letter-spacing -0.2px 포함)
  • Modal 라이브 예시 카드 패딩 24px → 44·40·36(상/좌우/하), X 닫기 위치 14px → 20px — ds-modal(header·body·footer) 표준 마진과 일치
  • Modal "내부 타이포·간격" 표에 카드 마진(44/40/36)·모달 타이틀(20/600) 행 추가 — 데모 CSS에만 있던 값을 명시 문서화
  • 모달 텍스트 가드(.ds-modal-backdrop p.text-*)에 text-h5·text-h4 추가 — <p class="text-h5"> 모달 타이틀(20px)이 design-system.css를 Tailwind에 @import하지 않는 앱(team-manager·employee-app 등, 런타임 로드만)에서도 동일 적용. 그 앱들은 text-h5 유틸이 생성되지 않아 헤딩/타이틀 크기가 누락되던 문제 수정

2026-06-19 — 입력 컴포넌트 문서 보강 (design/apply-ds-input-components)

신규

  • Form controls "메뉴 패널(ds-menu)" 섹션 신설 — 공유 DS의 커스텀 드롭다운 패널(__item/__item--selected/__item--row/__divider/__label/__hint) 라이브 인라인 미리보기 + 클래스/토큰 표. 등록은 돼 있었으나 문서가 0이던 컴포넌트(실제 직원 수정 모달 팀/역할 선택에 사용)
  • Form controls "달력(날짜·월 선택)" 섹션 신설 + 라이브 데모(<DemoDatePicker>) — DS 정책 명시: 날짜=시스템 기본 type="date"(네이티브 지원 충분, 커스텀 달력 미제작), 월=커스텀 월 선택기(type="month" 지원 약함 → .ds-menu 팝오버 + 연도 .ds-select + 월 그리드). 팝오버는 트리거 버튼 바로 아래 6px 간격·우측 정렬. 시각 확인도 네이티브 인풋을 직접 보게 구성

변경

  • Form controls "사용처" → **"적용 범위 & 도입 현황"**으로 재구성: 전역 기본(자동·전 서비스) vs 명시적 사이즈 전환(도입 진행)으로 2층 분리, 모달 마이그레이션(직원 위저드·근태 모달 → --40) 진행상황을 표로 반영. 곧 낡는 "사용처 카탈로그" 대신 진행상황 기록으로 전환
  • Form controls ms-dropdown 정정 — 공유 design-system.css가 아닌 admin·team-manager src/styles/main.css앱 로컬 패턴임을 명시(공유 표준은 ds-menu)
  • Form controls 라이브 미리보기(<DemoFormControls>)에 textarea·드롭다운 채움 변형(--fill-gray/--fill-white) 추가 — 등록된 변수를 더 시각화(같은 상태가 input·select·textarea에 동시 반영)
  • Form controls 검토 필요 사항 정리 — --ds-textfield-*/--ds-dropdown-* primitive 연결 항목을 runtime-safe literal 트레이드오프로 명시(보류: --ds-*는 의도적 literal 세트라 @theme tree-shaking으로 일부 앱서 primitive가 :root서 제거됨 — 실측 --color-label-white@module-manager·inventory). chevron(값 on-palette #8f8f8f=neutral-400·data-uri 한계)·24px(일관 literal px)·!important-free(정상) 항목은 검토 완료로 제거
  • 라이브 미리보기 = 페이지 상단(데모 우선) 통일 — 데모가 있는 7개 페이지(Form controls · Data table · Tooltip · TabNav · WorkspaceSwitcher · PageHeader · LnbSidebar)에서 미리보기 블록을 H1·인트로 바로 아래로 이동(컴포넌트를 먼저 보고 → 세부 읽기). 본문/캡션은 그대로 유지
  • Form controls 상단 라이브 미리보기를 3종 컴포넌트 갤러리(텍스트필드·드롭다운·textarea / 메뉴 패널 / 달력)로 재구성 — 각 항목에 "설명 더보기 →" 앵커로 상세 섹션({#size-scale}·{#menu-panel}·{#calendar}) 연결, 상세 섹션의 중복 미리보기는 제거(미리보기는 상단 한 곳에 모음)

메모

  • 입력 컴포넌트(크기 스케일·토큰) 자체는 이전 작업(PR #247 등, 커밋 ab7675c8/d99c3fce)에서 이미 DS 등록·develop 머지 완료 — 이 작업은 문서 반영 전용(새 CSS 없음)

2026-06-18 — 토대 문서 누락분 보강 (foundations)

변경

  • 색상 Label 표에 누락돼 있던 --color-label-black-normal(= --color-neutral-900 별칭) · --color-label-black-strong(#000000) 2개 토큰 추가 — design-system.css 정의와 표 동기화
  • 모서리 placeholder → 실문서: primitive --radius-2~512/full(@theme → Tailwind rounded-*) + 컴포넌트 --ds-radius-sm/md/lg/label-sm/label-md 토큰표·미리보기, 전역 버튼 radius 4px !important 강제 트랩 명시
  • 그림자 placeholder → 실문서: --ds-shadow-xs~xl recipe + 칩(--ds-shadow-chip-*)·drop-shadow(--ds-drop-shadow-*) 토큰표·미리보기, box-shadow recipe의 Figma 비전파 사유 명시
  • 타이포그래피 "숫자 표기(Numeric figures)" 섹션 신설 — 데이터 숫자는 Inter 기본, 자릿수 정렬이 필요한 표/열에만 tabular-nums 적용(단독 인라인 숫자엔 미적용), 사번·코드 등 식별자에 font-mono 금지(코드/키캡 전용). admin 근태관리 모달 사번 font-monotabular-nums 정리에서 도출
  • 타이포그래피 토큰 중복 정리 + 문서 정정: ① --ds-font-familyvar(--font-app) 별칭으로 변경(literal 단일화·동기화) ② --typo-*--text-*Tailwind tree-shaking 때문에 병합 불가임을 빌드 산출물로 검증(미사용 --text-h1:root에서 제거됨) → "단일화 대상" 표현을 "메커니즘 분리 유지"로 정정 ③ "font-weight 토큰 미정의"는 사실과 달라 정정(이미 정의·사용 중)

메모

  • 세 페이지 모두 기재값을 design-system.css 실제 토큰과 1:1 대조 후 작성 (수기 표 ↔ CSS 일치 검증)
  • 타이포 정정은 admin dist CSS(빌드 산출물) 직접 확인으로 검증 — --text-b5/--text-c2 존재, --text-h1 부재

2026-06-12 — LNB 개편 + 헤더 프로필 (design/update-lnb-structure-and-merge-pages)

신규

  • PageHeader: Toolbar 우측 끝 프로필 아바타(showProfile opt-in, radius 8, 벨과 간격 24) + 프로필 팝오버(썸네일 64 원형 → 메일 → 이름 → 내 정보·로그아웃, onLogout 호스트 콜백). LNB 하단 프로필 대체
  • LnbSidebar 접힘 레일: 플라이아웃(.ds-lnb__flyout — 흰 바탕·라인·아이콘/타이틀 행, fixed) · 커스텀 툴팁(.ds-lnb__tip — 네이티브 title 대체, 클릭 즉시 숨김, 열린 메뉴만 억제) · is-child-active(활성 하위를 가진 부모 강조)
  • .ws-brand: 비클릭 정적 브랜드 변형 — workspace(admin)는 WorkspaceSwitcher 드롭다운 대신 사용
  • 아이콘 13종: ic-id-badge ic-file-text ic-table ic-clock ic-sliders ic-calendar-clock ic-calendar-check ic-clock-plus ic-chart-bar ic-tag ic-map-pin ic-gauge ic-list-check — LNB 아이콘 중복 제거용 (LNB·헤더·embed 레지스트리 아이콘 일치 규칙 도입)

변경

  • workspace(admin) LNB: 접기 토글 하단 → 브랜드 우측(헤더), 하단 profile 영역 제거, 레일 스크롤바 비표시(스크롤 유지). 접힘 토글이 우측 sticky 헤더에 가려 잘리던 문제 z 조정으로 해소
  • NotificationBell 팝업 색 토큰화: Tailwind 기본 gray → neutral-*, shadow-xlshadow-popover, rounded-xlrounded-16 — 전역 버튼 강제 보정 의존 제거. 두 팝업(알림·프로필)이 동일한 라인(neutral-100)·radius 16 체계로 통일
  • 전역 버튼 radius 강제(4px !important) 제외 명단에 .ds-page-header__avatar 추가 — 규칙 자체 정리는 별도 브랜치 예정

메모

  • radius 12 토큰 신설은 검토 후 취소 — 기존 --radius-8/16 스케일 사용 (토큰 변경 없음)
  • docs 토큰 브리지 신설 (.vitepress/theme/ds-token-bridge.css): @theme 블록 토큰이 앱 빌드(Tailwind)에서만 :root로 처리되어 docs 라이브 데모에서 비어 있던 문제 수정. design-system.css의 모든 @theme 토큰(색 팔레트·의미별칭·폰트·타이포·radius·그림자·spacing)을 scripts/gen-docs-bridge.mjs자동 생성(docs predev/prebuild 시 실행)해 수기 사본 없이 단일 출처에서 동기화 — 전 데모가 프로덕션과 동일하게 렌더링
  • 라이브 데모 최신화: PageHeader(프로필 아바타·팝오버 추가, .vp-doc h1 타이틀 간섭 보정) · LnbSidebar(workspace 패턴 — 브랜드+헤더 토글·하단 profile 제거·접힘 플라이아웃/툴팁/child-active·새 아이콘) · WorkspaceSwitcher 문서에 "워크스페이스 미사용" 상태 라벨 명시

2026-06 — 모달 표준 v1 (design/modal-dim-display-standard)

신규

  • 토큰: --ds-dim-40(모달 딤 40%), z-index 스케일 --ds-z-* (base 0 / raised 10 / nav 20 / dropdown 40 / modal 50 / modal-top 60 / toast 80 / tooltip 90)
  • 컴포넌트: .ds-modal-backdrop — 딤 40% + 가운데 정렬 + z 토큰 + 스크롤 잠금 내장 (overscroll-behavior: none). 변형 --top(z-60)
  • 패턴 문서: 모달 v1 — 표시 · 닫기 · 스크롤 잠금 · 내부 타이포 · 예외 목록 · 라이브 예시

표준 적용 (전 모달)

  • dim을 화면별로 제어하지 않는다 — 공통 dim opacity 40%(공용 .ds-modal-backdrop) 적용, 항상 표시, 바깥 클릭으로 닫히지 않음. team-manager · admin(native/탭 인라인/scheduler) · general-affairs(4005) · management-dashboard(4006) · 공유 위자드/설정 모달 적용
  • 저장확인 + pristine 가드: 폼 모달의 X/취소/Esc는 변경이 있을 때만 확인 팝업 경유, 변경 없으면 바로 닫힘
  • 모달 내부 타이포: 필드 타이틀 13px(text-b5) + 간격 6px(mb-1.5), 콘텐츠 그룹 간격 16px(space-y-4)
  • 상태(활성/비활성) 선택을 드롭다운으로 통일 (pill 토글 · 스위치 5곳 변환)