diff --git a/.claude/plans/2026-07-31-ui-overhaul-SPEC.md b/.claude/plans/2026-07-31-ui-overhaul-SPEC.md new file mode 100644 index 0000000..adcff1e --- /dev/null +++ b/.claude/plans/2026-07-31-ui-overhaul-SPEC.md @@ -0,0 +1,275 @@ +# 개발 스펙 — UI 고도화 (Codex 작업 지시서) + +작성 2026-07-31. **구현 담당: Codex.** 판단이 끝난 확정 스펙이다. + +목표: 화면을 **금융권 서버 관리 콘솔 수준**으로 끌어올린다. + +- 대상 브랜치: `dashboard-impl` 위에 이어서 작업 +- 선행: [README.md](README.md) 의 지시서들(기능은 이미 구현 완료) + +--- + +## 0. 진단 — 무엇이 "엉성한가" + +코드를 실제로 확인한 결과, **색·폰트·간격 체계는 이미 잘 잡혀 있다.** +`main.rs` 의 `setup_theme()` 은 네이비 다크 + 블루 액센트에 텍스트 5단계, 코너 6px, +그림자까지 정의돼 있고 주석에도 "금융권 대시보드 톤" 이라고 적혀 있다. +**테마를 갈아엎을 필요가 없다.** 문제는 아래 4가지다. + +| # | 문제 | 결과 | +|---|---|---| +| 1 | **표를 `egui::Grid` 로 그린다** (`egui_extras` 미사용) | 열 너비가 내용에 따라 들쭉날쭉하고, 스크롤하면 헤더가 사라지며, 정렬(클릭 sort)이 안 되고, 97행을 매 프레임 전부 그린다 | +| 2 | **컴포넌트 레이어가 없다** — 공통 헬퍼가 `card()` 하나뿐 | 배지·수치·빈 상태·구분선을 화면마다 직접 조립해 생김새가 제각각이다 | +| 3 | **밀도가 터치 기준** (`interact_size 44x34`, `button_padding 12x8`) | 한 화면에 정보가 적게 들어간다. 관리 콘솔은 밀도가 높아야 한다 | +| 4 | **숫자가 좌측 정렬·가변폭** | 사용률·용량·건수가 세로로 안 맞아 비교가 어렵다 | + +**1번이 가장 크다.** 표가 흔들리면 나머지가 아무리 정돈돼도 엉성해 보인다. + +### 참고한 것 + +- [`egui_extras::TableBuilder`](https://docs.rs/egui_extras) — 열 리사이즈·헤더 고정·가상 스크롤 +- [Rerun Viewer](https://github.com/rerun-io/rerun) — egui 로 만든 전문가급 앱. + [`re_ui`](https://docs.rs/re_ui) 라는 **디자인 토큰 + 컴포넌트 레이어**를 따로 두는 구조를 참고했다 +- [egui 공식](https://github.com/emilk/egui) · [catppuccin-egui](https://github.com/catppuccin/egui) · + [egui-aesthetix](https://github.com/thebashpotato/egui-aesthetix) (테마 프리셋 — 이번엔 쓰지 않는다) + +--- + +## 1. 작업 규칙 + +- **UI-A → UI-B → UI-C → UI-D 순서로, 각각 커밋을 나눈다.** +- 기능 동작을 바꾸지 않는다. **표시 방식만** 바꾼다. +- 주석·UI 문자열·커밋 메시지는 한국어. + +### 절대 규칙 + +1. **`egui` 버전을 올리지 말 것.** `=0.31.1` 고정이다. + 0.29.x 에 리눅스 IME(한글 입력) 회귀 버그가 있어 고정한 것이고, 올리면 **한글 입력이 깨진다** + (`README.md` §폰트/한글 입력). `egui_extras` 도 반드시 **`=0.31.1`** 로 맞춘다: + + ```toml + egui_extras = "=0.31.1" + ``` + + 최신은 0.35 지만 egui 와 짝이 맞아야 하므로 쓰면 안 된다. +2. **`setup_theme()` 의 색 팔레트를 바꾸지 말 것.** 이미 의도된 톤이다. + 토큰으로 **정리**는 하되 색값 자체는 유지한다. +3. 기능 로직(`domain_issue`, 등급 산정, 스크립트)을 건드리지 않는다. +4. 자격증명을 소스·테스트에 넣지 않는다. + +--- + +## 2. UI-A — 디자인 토큰과 공통 컴포넌트 + +지금은 화면마다 색과 간격을 직접 써서 같은 의미가 다르게 보인다. **한 곳에 모은다.** + +### 2-1. 토큰 (`src/ui.rs` 신설) + +```rust +//! 디자인 토큰과 공통 위젯. 화면 코드는 여기 있는 것만 쓴다. +//! 색·간격을 화면에서 직접 지정하지 않는다 — 그러면 같은 의미가 화면마다 달라진다. + +/// 의미 기반 색. setup_theme() 의 팔레트에서 가져온다(값을 새로 만들지 말 것). +pub mod color { + use egui::Color32; + pub const OK: Color32 = Color32::from_rgb(0x2E, 0x9E, 0x5B); + pub const WARN: Color32 = Color32::from_rgb(0xF5, 0xB5, 0x4B); + pub const DANGER: Color32 = Color32::from_rgb(0xF2, 0x6D, 0x6D); + pub const ACCENT: Color32 = Color32::from_rgb(0x3B, 0x82, 0xF6); + pub const MUTED: Color32 = Color32::from_rgb(0x93, 0xA1, 0xBC); + pub const BORDER: Color32 = Color32::from_rgb(0x39, 0x4A, 0x6D); +} + +/// 간격은 4의 배수만 쓴다. 화면에서 7.0 같은 값을 직접 넣지 말 것. +pub mod space { + pub const XS: f32 = 4.0; + pub const SM: f32 = 8.0; + pub const MD: f32 = 12.0; + pub const LG: f32 = 16.0; + pub const XL: f32 = 24.0; +} +``` + +기존 `C_GREEN`/`C_RED` 는 `color::OK`/`color::DANGER` 로 대체하고 **중복 정의를 남기지 않는다.** + +### 2-2. 공통 위젯 + +```rust +/// 상태 배지 — "OK"/"FAIL" 같은 맨 텍스트 대신 쓴다. +pub fn badge(ui: &mut egui::Ui, text: &str, c: egui::Color32); + +/// 신호등 점. 등급·상태를 표 왼쪽에 찍는다. +pub fn dot(ui: &mut egui::Ui, c: egui::Color32); + +/// 큰 수치 + 라벨 (대시보드 지표 카드용) +pub fn stat(ui: &mut egui::Ui, value: &str, label: &str, c: Option); + +/// 섹션 제목 + 우측 액션 영역 +pub fn section(ui: &mut egui::Ui, title: &str, actions: impl FnOnce(&mut egui::Ui), + body: impl FnOnce(&mut egui::Ui) -> R) -> R; + +/// 빈 상태 — 아이콘 + 설명 + (선택) 다음 행동 버튼 +pub fn empty_state(ui: &mut egui::Ui, icon: &str, msg: &str, hint: Option<&str>); + +/// 숫자 셀 — 우측 정렬 + 고정폭(Monospace). 표에서 숫자는 전부 이걸 쓴다. +pub fn num(ui: &mut egui::Ui, text: &str); +``` + +`badge` 는 배경을 옅게(`c.linear_multiply(0.25)`) 깔고 테두리 없이, 모서리 4px, 좌우 여백 6px. + +### 2-3. 적용 + +대시보드·계정 관리·전체 사이트 화면에서 **직접 쓰던 색/간격을 토큰으로 교체**한다. +`ui.colored_label(C_RED, ...)` → `badge(ui, "...", color::DANGER)` 같은 식. + +--- + +## 3. UI-B — 표를 진짜 테이블로 (가장 중요) + +`egui::Grid` → `egui_extras::TableBuilder` 로 바꾼다. + +### 3-1. 대상 + +| 화면 | 현재 | 행 수 | +|---|---|---| +| 대시보드 · 도메인 헬스 | `Grid` | 최대 97 | +| 대시보드 · 확인 권장 사이트 | `Grid` | 수십 | +| 대시보드 · 디스크 목록 | 직접 배치 | 3 | +| 전체 사이트(`all_sites_page`) | `Grid`/직접 | **97+** ← 효과가 가장 크다 | +| 계정 관리 · 사이트/모듈 | `Grid` | 수십 | + +### 3-2. 기본형 + +```rust +use egui_extras::{Column, TableBuilder}; + +TableBuilder::new(ui) + .id_salt("domain_health_table") // 화면마다 고유 + .striped(true) + .resizable(true) // 열 너비를 사용자가 조절 + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::auto().at_least(180.0)) // 도메인 + .column(Column::remainder()) // 사유 — 남는 폭 전부 + .column(Column::exact(90.0)) // 상태 + .column(Column::exact(80.0)) // 숫자 + .header(24.0, |mut h| { + h.col(|ui| { ui.strong("도메인"); }); + // ... + }) + .body(|body| { + body.rows(22.0, rows.len(), |mut row| { + let r = &rows[row.index()]; + row.col(|ui| { ui.label(&r.domain); }); + // ... + }); + }); +``` + +**요구사항** + +- `body.rows(...)` 를 쓴다 — 보이는 행만 그리는 **가상 스크롤**이다. + `body.row()` 를 루프로 돌리면 97행을 전부 그려 지금과 같아진다. +- 행 높이는 **22.0 고정**(밀도). 헤더 24.0. +- 숫자 열은 `Column::exact` + `ui::num()` 으로 **우측 정렬**. +- `resizable(true)` 로 열 폭 조절 허용. +- 헤더는 스크롤해도 고정된다(TableBuilder 기본 동작). + +### 3-3. 정렬(sort) + +헤더 클릭으로 정렬한다. `TableBuilder` 에 내장 정렬이 없으므로 **직접 구현**한다: + +```rust +/// 표 정렬 상태. 화면별로 App 에 하나씩 둔다. +#[derive(Clone, Copy, PartialEq)] +pub struct SortState { pub col: usize, pub desc: bool } +``` + +- 헤더 셀을 `ui.button()` 으로 만들고 클릭 시 `col` 을 바꾸거나 같은 열이면 `desc` 토글 +- 현재 정렬 열에 `▲`/`▼` 표시 +- **정렬은 표시용 사본에만 적용**한다. 원본 `Vec` 의 순서를 바꾸면 선택 상태(`sel`)가 어긋난다 +- 기본 정렬: 도메인 헬스는 **이상 있는 것 먼저**, 전체 사이트는 도메인 오름차순 + +### 3-4. 선택 열 + +계정 관리 사이트 탭처럼 체크박스가 있는 표는 첫 열을 `Column::exact(28.0)` 로 두고 +`ui.checkbox(&mut r.sel, "")` 를 넣는다. 행 클릭으로 토글하던 기존 동작은 유지한다. + +--- + +## 4. UI-C — 밀도와 정렬 + +관리 콘솔은 한 화면에 많이 보여야 한다. 현재 값은 터치 기준에 가깝다. + +### 4-1. `setup_theme()` 조정 + +```rust +style.spacing.item_spacing = egui::vec2(8.0, 4.0); // 세로 6 → 4 +style.spacing.button_padding = egui::vec2(10.0, 5.0); // 12x8 → 10x5 +style.spacing.interact_size = egui::vec2(40.0, 28.0); // 44x34 → 40x28 +``` + +**색·폰트는 건드리지 않는다.** 간격만 줄인다. + +이 변경은 **모든 화면에 영향**을 주므로 UI-C 는 반드시 **독립 커밋**으로 하고, 커밋 메시지에 +되돌리는 법을 적어둔다(값 3개만 원복하면 된다). + +### 4-2. 숫자 표기 규칙 + +- 표 안의 모든 숫자는 `ui::num()` — 우측 정렬 + `Monospace` +- 퍼센트는 정수(`69%`), 용량은 소수 1자리(`3.2T`), 건수는 정수 +- 날짜/시각은 `2026-07-31 09:11` 고정 형식, 상대시간은 괄호로 병기 (`(3시간 전)`) + +### 4-3. 창 폭 대응 + +이미 `dashboard_is_narrow(900.0)` 이 있다. 같은 기준을 **전체 사이트·계정 관리**에도 적용해 +좁을 때 열을 숨긴다(우선순위 낮은 열부터: 생성일 → 권한 → 용량). + +--- + +## 5. UI-D — 상태 표현 표준화 + +같은 의미가 화면마다 다르게 보인다. **하나로 통일**한다. + +| 의미 | 표기 | 색 | +|---|---|---| +| 정상/성공 | `● 정상` 또는 배지 `OK` | `color::OK` | +| 주의 | 배지 `주의` | `color::WARN` | +| 위험/실패 | 배지 `위험` / `실패` | `color::DANGER` | +| 미조회/알 수 없음 | 배지 `미조회` | `color::MUTED` | +| 진행 중 | `ui.spinner()` + 회색 텍스트 | `color::MUTED` | + +- **등급(A~E)** 은 이미 색이 있다. `ui::dot()` + 등급 글자로 통일한다. +- **"-" 를 그대로 노출하지 말 것.** `조회불가` 또는 `해당 없음` 으로 적는다. +- 위험 표시에 색만 쓰지 말고 **글자도 함께** 넣는다(색각 이상 대응). + +--- + +## 6. 함정 + +1. **`egui` 버전을 올리면 한글 입력이 깨진다.** `egui_extras` 는 `=0.31.1`. +2. `body.rows()` 대신 `body.row()` 루프를 쓰면 가상 스크롤이 사라져 **지금과 똑같이 느리다.** +3. `TableBuilder` 를 `ScrollArea` **안에 중첩하지 말 것.** 테이블이 자체 스크롤을 갖는다. + 대시보드 전체 스크롤(`dashboard_scroll`) 안에 넣을 때는 테이블에 `.max_scroll_height()` 를 + 주어 높이를 제한한다. 안 그러면 스크롤이 두 겹으로 겹쳐 조작이 이상해진다. +4. **정렬 시 원본 순서를 바꾸지 말 것** — 체크박스 선택이 다른 행으로 옮겨간다. +5. `id_salt` 를 표마다 다르게 준다. 같으면 열 폭이 서로 공유돼 튄다. +6. 밀도 변경(UI-C)은 모달·입력 폼에도 적용된다. 폼이 너무 빽빽해지면 **입력 위젯에만** + `interact_size` 를 개별 지정해 되돌린다. +7. 색 상수를 새로 만들지 말 것. `setup_theme()` 팔레트에서 가져와 `ui::color` 에 모은다. + +--- + +## 7. DoD + +- [ ] `cargo test` 전부 통과 / `cargo build --release` 경고 없음 +- [ ] `Cargo.toml` 에 `egui_extras = "=0.31.1"`, `egui` 는 `=0.31.1` 그대로 +- [ ] `src/ui.rs` 가 생기고, 화면 코드에서 **직접 쓰는 색·간격 리터럴이 사라짐** + (`grep -n "from_rgb" src/app.rs` 가 거의 비어야 한다) +- [ ] 전체 사이트·도메인 헬스가 `TableBuilder` 로 그려지고 **헤더가 스크롤에 고정**된다 +- [ ] 헤더 클릭으로 정렬되고 `▲`/`▼` 가 표시된다 +- [ ] 97행에서 스크롤이 버벅이지 않는다(가상 스크롤 확인) +- [ ] 열 너비를 드래그로 조절할 수 있다 +- [ ] 표의 숫자가 우측 정렬·고정폭으로 세로가 맞는다 +- [ ] 상태 표기가 §5 표대로 통일된다 +- [ ] 한글 입력이 여전히 동작한다(**실행해서 직접 확인** — 버전 사고 방지) +- [ ] `docs/` 에 `ui-guide.md` 추가: 토큰·컴포넌트 사용법, 표 만드는 법, 밀도 되돌리는 법 diff --git a/.claude/skills/ui-console/SKILL.md b/.claude/skills/ui-console/SKILL.md new file mode 100644 index 0000000..ba5819a --- /dev/null +++ b/.claude/skills/ui-console/SKILL.md @@ -0,0 +1,193 @@ +--- +name: ui-console +description: hostmover 의 화면(egui)을 만들거나 고칠 때 따르는 UI 규칙. 서버 관리 콘솔 톤(IBM Carbon·Grafana 계열 데이터 밀도형)을 유지한다. 표·수치·상태 표기·색·간격·밀도를 다룰 때, 그리고 새 화면이나 카드를 추가할 때 사용한다. +--- + +# hostmover UI 규칙 — 서버 관리 콘솔 + +이 앱은 **금융권 서버 관리 콘솔**을 지향한다. 소비자 앱이 아니라 **운영자가 하루 종일 들여다보는 +도구**다. 기준은 IBM Carbon·Grafana 계열이다: 여백보다 **정보 밀도**, 장식보다 **정렬**. + +Apple HIG 같은 터치 기준(44pt 타겟, 넉넉한 여백)을 적용하지 않는다. 방향이 반대다. + +--- + +## 0. 절대 제약 (어기면 앱이 깨진다) + +1. **`egui` 버전을 올리지 않는다.** `Cargo.toml` 의 `egui = "=0.31.1"`, `eframe = "=0.31.1"` 고정. + 0.29.x 에 리눅스 IME 회귀가 있어 고정한 것이고, 올리면 **한글 입력이 안 된다.** + `egui_extras` 등 부속 크레이트도 반드시 **`=0.31.1`** 로 맞춘다(최신은 0.35 지만 쓰면 안 된다). +2. **폰트를 바꾸지 않는다.** 한글이 깨진다. 굵은 글씨는 `FontFamily::Name("bold".into())` 를 쓴다 + (`main.rs` 에서 나눔고딕 계열로 등록해 둔 패밀리다). `strong()` 은 이 패밀리를 타지 않으므로 + 제목에는 `RichText::new(x).family(bold)` 를 쓴다. +3. **`setup_theme()` 의 색 팔레트를 새로 만들지 않는다.** 색이 필요하면 아래 §1 토큰에서 가져온다. + +--- + +## 1. 색 — 의미에만 쓴다 + +장식용 색을 쓰지 않는다. 색은 **상태를 알리는 신호**로만 존재한다. + +| 토큰 | 값 | 언제 | +|---|---|---| +| `OK` | `#2E9E5B` | 정상, 성공, 이상 없음 | +| `WARN` | `#F5B54B` | 주의, 임계 근접, 확인 권장 | +| `DANGER` | `#F26D6D` | 위험, 실패, 즉시 조치 | +| `ACCENT` | `#3B82F6` | 선택, 링크, 기본 동작 버튼 | +| `MUTED` | `#93A1BC` | 보조 설명, 미조회, 비활성 | +| `BORDER` | `#394A6D` | 경계선, 구분 | + +규칙: + +- **색만으로 의미를 전달하지 않는다.** 반드시 글자를 함께 둔다(색각 이상 대응). + `● 위험` 은 되고, 빨간 점만 찍는 것은 안 된다. +- 한 화면에 강한 색(`DANGER`)이 5개 넘게 보이면 그 화면은 이미 실패다. 집계해서 줄인다. +- 배지 배경은 `c.linear_multiply(0.25)`, 글자는 `c` 원색. + +--- + +## 2. 타이포 계층 + +`main.rs` 에 정의된 5단계를 쓴다. 새 크기를 만들지 않는다. + +| 용도 | 스타일 | 크기 | +|---|---|---| +| 화면 제목 | `Heading` + bold 패밀리 | 18 | +| 본문·표 셀 | `Body` | 14 | +| 버튼 | `Button` | 14 | +| 수치·경로·로그 | `Monospace` | 12.5 | +| 보조 설명·단위 | `Small` | 11.5 | + +- 카드 제목은 `ui.strong()` 대신 `Heading` 을 쓰지 않는다 — 카드 제목은 **본문 굵게**다. + 화면 제목만 `Heading`. +- 보조 설명은 `ui.weak()` 또는 `Small` + `MUTED`. + +--- + +## 3. 간격 — 4의 배수만 + +`4 / 8 / 12 / 16 / 24` 만 쓴다. `6.0`, `7.0`, `10.0` 같은 값을 직접 넣지 않는다. + +- 카드 안 요소 사이: `4` +- 카드 안 섹션 사이: `8` +- 카드 사이: `8` +- 화면 여백: `12`~`16` + +--- + +## 4. 표 — 이 앱에서 가장 중요하다 + +**`egui::Grid` 로 데이터 표를 만들지 않는다.** 반드시 `egui_extras::TableBuilder` 를 쓴다. + +`Grid` 는 열 너비가 내용에 따라 흔들리고, 헤더가 스크롤에 고정되지 않으며, 행을 전부 그린다. +표가 흔들리면 나머지가 아무리 정돈돼도 화면 전체가 엉성해 보인다. + +```rust +use egui_extras::{Column, TableBuilder}; + +TableBuilder::new(ui) + .id_salt("도메인헬스") // 표마다 고유. 같으면 열 폭이 서로 튄다 + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .max_scroll_height(320.0) // 바깥 ScrollArea 안에 넣을 때 필수 + .column(Column::auto().at_least(180.0)) // 이름 열 + .column(Column::remainder()) // 설명 — 남는 폭 전부 + .column(Column::exact(80.0)) // 숫자 열은 exact + .header(24.0, |mut h| { /* ... */ }) + .body(|body| { + body.rows(22.0, rows.len(), |mut row| { // rows() — 가상 스크롤 + let r = &rows[row.index()]; + row.col(|ui| { ui.label(&r.name); }); + }); + }); +``` + +**규칙** + +- **행 22.0 / 헤더 24.0 고정.** 화면마다 다르게 하지 않는다. +- **`body.rows()` 를 쓴다.** `body.row()` 를 루프로 돌리면 가상 스크롤이 사라져 100행에서 버벅인다. +- 숫자 열은 `Column::exact`, 설명 열은 `Column::remainder()` 하나만. +- `resizable(true)` — 운영자가 열 폭을 조절할 수 있어야 한다. +- 정렬(sort)이 필요하면 헤더를 버튼으로 만들고 `▲`/`▼` 를 표시한다. + **정렬은 표시용 사본에만 적용한다.** 원본 `Vec` 순서를 바꾸면 체크박스 선택이 다른 행으로 옮겨간다. +- `TableBuilder` 를 `ScrollArea` 안에 넣을 때는 `max_scroll_height()` 로 높이를 제한한다. + 안 하면 스크롤이 두 겹이 되어 조작이 이상해진다. + +--- + +## 5. 수치 표기 + +운영자는 숫자를 **비교**한다. 세로로 안 맞으면 비교가 안 된다. + +- 표 안 숫자는 **우측 정렬 + `Monospace`**. 자릿수가 달라도 세로가 맞는다. +- 퍼센트는 정수(`69%`), 용량은 소수 1자리(`3.2T`, `142G`), 건수는 정수. +- 시각은 `2026-07-31 09:11` 고정 형식. 상대시간은 괄호로 병기(`(3시간 전)`). +- **단위를 빼먹지 않는다.** `142` 가 아니라 `142G`. +- 값이 없으면 빈칸으로 두지 말고 `조회불가` 또는 `해당 없음` 이라고 쓴다. + **빈칸은 "정상"으로 오해된다.** + +--- + +## 6. 상태 표기 — 하나로 통일 + +| 의미 | 표기 | 색 | +|---|---|---| +| 정상 | 배지 `정상` 또는 `● 정상` | `OK` | +| 주의 | 배지 `주의` | `WARN` | +| 위험·실패 | 배지 `위험` / `실패` | `DANGER` | +| 미조회 | 배지 `미조회` | `MUTED` | +| 진행 중 | `ui.spinner()` + `MUTED` 텍스트 | `MUTED` | + +- **"OK"/"FAIL" 맨 텍스트를 그대로 쓰지 않는다.** 배지로 감싼다. +- 등급(A~E)은 `● A` 처럼 점 + 글자. +- **모르는 것을 좋은 상태로 표시하지 않는다.** 조회하지 않았으면 `미조회`이지 `정상`이 아니다. + +--- + +## 7. 밀도 + +관리 콘솔은 한 화면에 많이 보여야 한다. 스크롤로 찾게 만들지 않는다. + +- 컨트롤 높이 `28`, 버튼 패딩 `10x5`, 세로 간격 `4` 기준. +- 카드 하나에 지표를 3~6개 담는다. 하나만 크게 넣지 않는다. +- 창이 좁으면(`< 900px`) 3열 → 세로 배치. 우선순위 낮은 열부터 숨긴다. +- 여백을 늘려 "고급스럽게" 만들려 하지 않는다. 이 도구에서 고급스러움은 **정렬과 일관성**이다. + +--- + +## 8. 빈 상태·로딩 + +- 빈 화면에 아무것도 없이 두지 않는다. **왜 비었는지 + 다음에 뭘 하면 되는지**를 쓴다. + - 나쁨: `데이터 없음` + - 좋음: `아직 스캔하지 않았습니다 — [전체 사이트]에서 스캔하세요` +- 오래 걸리는 작업은 예상 시간을 미리 알린다(`도메인 100개면 약 2분`). +- 조회 결과에는 **언제 기준인지** 반드시 표시한다(`3시간 전 기준`). + +--- + +## 9. 하지 말 것 + +- `egui`/`egui_extras` 버전 올리기 (한글 입력이 깨진다) +- 폰트 교체 +- 데이터 표를 `egui::Grid` 로 만들기 +- `body.row()` 루프 (가상 스크롤 상실) +- 색 리터럴을 화면 코드에 직접 쓰기 (`Color32::from_rgb(...)`) +- 4의 배수가 아닌 간격 +- 색만으로 상태 전달 +- 정렬할 때 원본 `Vec` 순서 바꾸기 +- 값이 없을 때 빈칸으로 두기 +- 미조회를 정상으로 표시하기 + +--- + +## 10. 화면을 만들거나 고친 뒤 확인 + +- [ ] 표가 `TableBuilder` 이고 헤더가 스크롤에 고정되는가 +- [ ] 숫자가 우측 정렬·고정폭으로 세로가 맞는가 +- [ ] 색을 쓴 곳마다 글자도 함께 있는가 +- [ ] 값이 없는 칸에 `조회불가`가 쓰여 있는가 +- [ ] 조회 결과에 기준 시각이 있는가 +- [ ] 빈 상태에 다음 행동이 적혀 있는가 +- [ ] `cargo build --release` 경고 없음 +- [ ] **한글 입력이 되는가** (버전 사고 방지 — 실행해서 직접 확인) diff --git a/.gitignore b/.gitignore index 82e1817..c324771 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,11 @@ store.json # 대용량 패키지 아티팩트 *.zip + +# Claude Code — plans/ 와 skills/ 만 공유한다. +# settings.local.json 은 승인한 명령 전문이 저장되므로(비밀번호·토큰이 섞일 수 있다) +# 절대 커밋하지 않는다. worktrees/ 는 작업용 체크아웃 사본이다. +.claude/* +!.claude/plans/ +!.claude/skills/ +.omc/