feat: clean review-instrument restyle with bundled Pretendard font
- Bundle Pretendard Variable woff2 locally (air-gapped safe, no CDN) and switch UI/stamp font stacks to it; preload in index.html - Replace the forensic-dossier paper theme with a flat neutral cool palette: single teal accent, white cards, no noise texture, and zero linear/radial gradients (per design contract) - Restore the product-purpose top-bar block and its CSS, drop the unused global search form, and strip the stray UTF-8 BOM - Re-skin queue hover/selection, eyebrows, nav rail, chips, and empty states to the neutral palette; tabular numerals for numbers - Regenerate ui-overhaul final audit artifacts: zero horizontal overflow across 8 views at 1440x900 and 390x844, Pretendard active Design spec: docs/superpowers/specs/2026-06-11-operator-console-clean-review-ui-design.md Plan: docs/plans/2026-06-11-001-feat-operator-console-clean-review-ui-plan.md Tests: 358 passed (full suite incl. browser smoke)
This commit is contained in:
parent
3f7b3a9cf2
commit
ed701bd436
6 changed files with 1065 additions and 492 deletions
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
status: active
|
||||||
|
created: 2026-06-11
|
||||||
|
type: quality
|
||||||
|
title: Operator console clean review UI + Pretendard font plan
|
||||||
|
spec: docs/superpowers/specs/2026-06-11-operator-console-clean-review-ui-design.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Operator Console Clean Review UI Plan
|
||||||
|
|
||||||
|
목표: 전체적 UI 점검 및 폰트 변경, 깔끔하게 잘 떨어지는 심사 프로그램의 목적에 맞게 개선.
|
||||||
|
|
||||||
|
## Phase 0 — Baseline (done)
|
||||||
|
|
||||||
|
- [x] 전 뷰 데스크톱/모바일 스크린샷 + 오버플로 감사 (`data/logs/ui-font-baseline-*`)
|
||||||
|
- [x] 정적 테스트 베이스라인: `test_workbench_shell_exposes_all_internal_operator_views` 실패
|
||||||
|
(product-purpose 블록/CSS 삭제됨), index.html BOM 유입 확인
|
||||||
|
- [x] Pretendard Variable woff2 v1.3.9 오프라인 번들 확보
|
||||||
|
(`web/operator-gui/assets/fonts/PretendardVariable.woff2`)
|
||||||
|
|
||||||
|
## Phase 1 — Font integration
|
||||||
|
|
||||||
|
- [ ] `styles.css` 상단에 `@font-face` (Pretendard Variable, weight 45 920, swap)
|
||||||
|
- [ ] `--font-ui`, `--font-stamp` 토큰을 Pretendard 스택으로 교체 (mono 유지)
|
||||||
|
- [ ] `index.html`에 woff2 preload 링크 추가, BOM 제거
|
||||||
|
- [ ] 점수/쿼터/시간 표기에 `font-variant-numeric: tabular-nums`
|
||||||
|
|
||||||
|
## Phase 2 — Clean re-skin (token-driven)
|
||||||
|
|
||||||
|
- [ ] `:root` 팔레트를 종이톤 → 중성 쿨톤으로 교체 (spec 3.2)
|
||||||
|
- [ ] body 노이즈 텍스처 + radial-gradient 제거 (플랫 서피스)
|
||||||
|
- [ ] :root 밖 하드코딩 색상(37 hex, 36 rgba) 중 종이톤 잔재 일괄 정리
|
||||||
|
- [ ] 그림자/포커스 링을 쿨톤으로 정돈
|
||||||
|
- [ ] 모든 테스트 계약 문자열 보존 (spec 3.4 목록)
|
||||||
|
|
||||||
|
## Phase 3 — Contract restoration
|
||||||
|
|
||||||
|
- [ ] top-bar에 `product-purpose` 블록 복원 (HTML + CSS)
|
||||||
|
- [ ] 큐 오버플로 미세 정리: 데스크톱 `strong`, 모바일 `provider-chip`
|
||||||
|
|
||||||
|
## Phase 4 — Verification (사용자 반응성 검토)
|
||||||
|
|
||||||
|
- [ ] `pytest tests/operator_gui` 전체 통과
|
||||||
|
- [ ] 라이브 서버 Playwright 감사: 8개 뷰 × 데스크톱/모바일, docW <= vw
|
||||||
|
- [ ] `data/logs/ui-overhaul-final-results.json` + 계약 스크린샷 8종 재생성
|
||||||
|
- [ ] Pretendard 적용 여부 런타임 확인 (document.fonts)
|
||||||
|
- [ ] 오프라인 검증: GUI 내 외부 fetch 참조 0건
|
||||||
|
- [ ] `pytest` 전체 회귀
|
||||||
|
|
||||||
|
## Phase 5 — Commit
|
||||||
|
|
||||||
|
- [ ] 디자인 문서 + 구현 + 산출물 커밋 (feat/chore 분리 없이 단일 feat 커밋)
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
# Operator Console Clean Review UI — Design Spec
|
||||||
|
|
||||||
|
- Date: 2026-06-11
|
||||||
|
- Status: approved (autonomous goal session)
|
||||||
|
- Goal: 전체적 UI 점검 및 폰트 변경, 깔끔하게 잘 떨어지는 심사 프로그램의 목적에 맞게 개선
|
||||||
|
|
||||||
|
## 1. Problem
|
||||||
|
|
||||||
|
The current working-tree restyle ("Forensic Dossier") gives the operator console a
|
||||||
|
vintage paper-document look: beige paper background with an SVG noise texture,
|
||||||
|
ochre/teal dual accents, a "stamp" display font (Bahnschrift), and warm hairlines.
|
||||||
|
That direction conflicts with the product's purpose — a fast, trustworthy,
|
||||||
|
**clean review instrument** an operator stares at for hours.
|
||||||
|
|
||||||
|
Additional defects found during the baseline audit (2026-06-11):
|
||||||
|
|
||||||
|
- `tests/operator_gui/test_static_workbench.py::test_workbench_shell_exposes_all_internal_operator_views`
|
||||||
|
fails: the uncommitted change deleted the `product-purpose` top-bar block and its CSS.
|
||||||
|
- `index.html` gained a UTF-8 BOM (``).
|
||||||
|
- Typography relies on Malgun Gothic, which renders Korean UI text loosely and
|
||||||
|
inconsistently across weights.
|
||||||
|
- Minor horizontal overflow offenders: `strong` inside desktop queue rows,
|
||||||
|
`span.provider-chip` on mobile queue (inside the intentionally scrollable table shell).
|
||||||
|
|
||||||
|
Baseline artifacts: `data/logs/ui-font-baseline-*.png`, `data/logs/ui-font-baseline-results.json`.
|
||||||
|
|
||||||
|
## 2. Approaches Considered
|
||||||
|
|
||||||
|
1. **Token re-skin (chosen).** Keep the stylesheet's structure, selectors, and all
|
||||||
|
test-contract strings; replace the design tokens (`:root`), the body texture,
|
||||||
|
the font stack, and sweep hardcoded decorative colors. Lowest risk: every
|
||||||
|
class hook and fixed contract string (queue grid template, audit widths,
|
||||||
|
floating panel geometry, media queries) survives untouched.
|
||||||
|
2. Full stylesheet rewrite. Cleanest end state but high risk of dropping one of
|
||||||
|
the ~30 fixed contract strings the static tests pin; harder to review.
|
||||||
|
3. Revert to the committed styles and start over. Discards legitimate layout
|
||||||
|
work already in the working tree (queue grid, floating decision panel).
|
||||||
|
|
||||||
|
## 3. Design Decisions
|
||||||
|
|
||||||
|
### 3.1 Typography (폰트 변경)
|
||||||
|
|
||||||
|
- Bundle **Pretendard Variable** (`web/operator-gui/assets/fonts/PretendardVariable.woff2`,
|
||||||
|
v1.3.9, 2.06 MB) — downloaded at build time, served locally. **No CDN reference**
|
||||||
|
at runtime (air-gapped rule).
|
||||||
|
- `@font-face` with `font-display: swap`, weight range 45–920.
|
||||||
|
- Token changes:
|
||||||
|
- `--font-ui: "Pretendard Variable", Pretendard, "Malgun Gothic", "Apple SD Gothic Neo", "Segoe UI", system-ui, sans-serif`
|
||||||
|
- `--font-stamp` (labels, IDs, numeric stamps): same Pretendard stack —
|
||||||
|
differentiation now comes from weight (650–750), size, and `letter-spacing`,
|
||||||
|
not from a second display face. Bahnschrift is dropped.
|
||||||
|
- `--font-mono` unchanged (Cascadia Mono / Consolas) for hashes, IDs, code.
|
||||||
|
- `index.html` gets `<link rel="preload" as="font" type="font/woff2" crossorigin>`
|
||||||
|
for the woff2.
|
||||||
|
- Numeric UI (scores, quotas, timestamps) uses `font-variant-numeric: tabular-nums`.
|
||||||
|
|
||||||
|
### 3.2 Surface and color (깔끔하게 잘 떨어지는 톤)
|
||||||
|
|
||||||
|
- Remove the SVG noise texture and both radial gradient washes from `body` —
|
||||||
|
flat, calm surface.
|
||||||
|
- Palette shifts from warm paper to neutral cool:
|
||||||
|
- `--paper` `#f3f5f7` (workspace background), `--paper-2` `#e9edf0`
|
||||||
|
- `--card` `#ffffff`, `--card-raised` `#ffffff`, `--card-sunk` `#f6f8fa`
|
||||||
|
- Ink scale: `--ink #1a2128`, `--ink-soft #51606b`, `--ink-faint #8794a1`
|
||||||
|
- Hairlines: cool grays (`#dde3e8` / `#e7ecf0` / `#b9c4cd`)
|
||||||
|
- Single primary accent: deep teal-blue (`--teal #1f6f8b` family). The ochre
|
||||||
|
family stays defined (components reference it) but is re-tuned toward a
|
||||||
|
restrained amber used only for "hold/attention" semantics, not branding.
|
||||||
|
- Risk semantics keep their class names and stay high-contrast:
|
||||||
|
red=high, amber=medium, green=low/approve, plus failed/pending neutrals.
|
||||||
|
- Shadows get smaller and cooler; radii stay (6/9/14px). No `linear-gradient`
|
||||||
|
(test contract), and the remaining radial gradients are removed for flatness.
|
||||||
|
- The dark nav rail stays dark (orientation anchor) but moves to a neutral
|
||||||
|
slate so the single accent color reads clearly.
|
||||||
|
|
||||||
|
### 3.3 Contract restorations
|
||||||
|
|
||||||
|
- Re-add the `product-purpose` block to the top bar (`class="product-purpose"`,
|
||||||
|
`aria-label="제품 목적"`, copy: "이미지 저작권 위험 심사" /
|
||||||
|
"제출 이미지, 외부 검색 근거, 내부 기준 DB를 한 화면에서 검토합니다.") and its
|
||||||
|
`.product-purpose` CSS, styled to fit the clean top bar.
|
||||||
|
- Remove the BOM from `index.html`.
|
||||||
|
- The deleted global-search form and operator chip stay deleted (no test
|
||||||
|
references them; the queue has its own search field).
|
||||||
|
|
||||||
|
### 3.4 Explicitly preserved test-contract strings in `styles.css`
|
||||||
|
|
||||||
|
`grid-template-columns: 28px 64px minmax(104px, 0.68fr) 72px minmax(126px, 0.58fr) minmax(360px, 1.5fr) 82px 76px 90px`,
|
||||||
|
`--audit-object-width: 24%`, `.audit-table th:nth-child(4)/(5)` with
|
||||||
|
`width: var(--audit-object-width)`, `.floating-decision-panel` with
|
||||||
|
`position: fixed` / `bottom: 24px` / `padding-right: 334px` /
|
||||||
|
`padding-bottom: 260px`, `data-workbench-panel="evidence"` selector,
|
||||||
|
`@media (max-width: 980px)`, `@media (max-width: 680px)`, `:focus-visible`,
|
||||||
|
`.risk-high/.risk-medium/.risk-low/.risk-failed`,
|
||||||
|
`.source-naver/.source-google/.source-llm/.source-internal`,
|
||||||
|
`.queue-row td:nth-child(5)/(6)`, `white-space: nowrap`, `flex-wrap: nowrap`,
|
||||||
|
`.queue-provider-strip`, `.coverage-tabs`/`.coverage-tab`, no `linear-gradient`,
|
||||||
|
no `orb`, obsolete selectors (`.coverage-main`, `.coverage-badges`,
|
||||||
|
`.coverage-provider-grid`, `.coverage-mini-line`, `.queue-table th:nth-child`)
|
||||||
|
stay absent.
|
||||||
|
|
||||||
|
## 4. Verification Plan (사용자 반응성 검토)
|
||||||
|
|
||||||
|
1. `node --check` on touched JS (none expected) and full
|
||||||
|
`pytest tests/operator_gui` static contracts.
|
||||||
|
2. Live-server Playwright audit at 1440×900 and 390×844 across all 8 views:
|
||||||
|
zero horizontal document overflow (`docW <= vw`), regenerate
|
||||||
|
`data/logs/ui-overhaul-final-results.json` and the 8 contract screenshots.
|
||||||
|
3. Interaction smoke: existing `test_browser_smoke.py` (suggested-query fill,
|
||||||
|
real upload flow) must pass against the restyled DOM.
|
||||||
|
4. Visual review of screenshots: font rendering (Pretendard active), flat
|
||||||
|
surfaces, risk colors legible, floating decision panel unobstructed.
|
||||||
|
5. Offline check: grep the GUI for `http://`/`https://` font or CSS fetches —
|
||||||
|
none allowed.
|
||||||
|
|
@ -3239,36 +3239,6 @@ async function emergencyDisableExternalProviders() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function handleGlobalSearch(event) {
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const query = document.getElementById("global-search-input").value.trim().toLowerCase();
|
|
||||||
|
|
||||||
if (!query) return;
|
|
||||||
|
|
||||||
const match = submissions.find(
|
|
||||||
|
|
||||||
(submission) => submission.id.toLowerCase().includes(query) || submission.title.toLowerCase().includes(query),
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
|
|
||||||
selectCase(match.id);
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
switchView("knowledge");
|
|
||||||
|
|
||||||
renderAll();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
|
|
||||||
document.querySelectorAll(".nav-button").forEach((button) => {
|
document.querySelectorAll(".nav-button").forEach((button) => {
|
||||||
|
|
@ -3411,8 +3381,6 @@ function bindEvents() {
|
||||||
|
|
||||||
document.getElementById("knowledge-image").addEventListener("change", updateKnowledgeImageName);
|
document.getElementById("knowledge-image").addEventListener("change", updateKnowledgeImageName);
|
||||||
|
|
||||||
document.getElementById("global-search-form").addEventListener("submit", handleGlobalSearch);
|
|
||||||
|
|
||||||
document.getElementById("reload-submissions").addEventListener("click", reloadSubmissions);
|
document.getElementById("reload-submissions").addEventListener("click", reloadSubmissions);
|
||||||
document.getElementById("submission-image").addEventListener("change", updateSubmissionImageName);
|
document.getElementById("submission-image").addEventListener("change", updateSubmissionImageName);
|
||||||
document.getElementById("upload-submission-image").addEventListener("click", uploadSubmissionImage);
|
document.getElementById("upload-submission-image").addEventListener("click", uploadSubmissionImage);
|
||||||
|
|
|
||||||
BIN
web/operator-gui/assets/fonts/PretendardVariable.woff2
Normal file
BIN
web/operator-gui/assets/fonts/PretendardVariable.woff2
Normal file
Binary file not shown.
|
|
@ -1,9 +1,10 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="ko">
|
<html lang="ko">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>권리 검수 콘솔</title>
|
<title>권리 검수 콘솔</title>
|
||||||
|
<link rel="preload" href="assets/fonts/PretendardVariable.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<script src="operator-labels.js" defer></script>
|
<script src="operator-labels.js" defer></script>
|
||||||
<script src="submission-import.js" defer></script>
|
<script src="submission-import.js" defer></script>
|
||||||
|
|
@ -58,21 +59,13 @@
|
||||||
|
|
||||||
<div class="workspace">
|
<div class="workspace">
|
||||||
<header class="top-bar">
|
<header class="top-bar">
|
||||||
<form class="global-search" id="global-search-form" role="search">
|
|
||||||
<label class="sr-only" for="global-search-input">전체 검색</label>
|
|
||||||
<input id="global-search-input" type="search" autocomplete="off" placeholder="제출 ID, 엔티티, 도메인 검색">
|
|
||||||
</form>
|
|
||||||
<div class="product-purpose" aria-label="제품 목적">
|
<div class="product-purpose" aria-label="제품 목적">
|
||||||
<strong>이미지 저작권 위험 심사</strong>
|
<strong>이미지 저작권 위험 심사</strong>
|
||||||
<span>제출 이미지, 외부 검색 근거, 내부 기준 DB를 한 화면에서 검토합니다.</span>
|
<span>제출 이미지, 외부 검색 근거, 내부 기준 DB를 한 화면에서 검토합니다.</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="queue-health" id="queue-health" aria-live="polite"></div>
|
<div class="queue-health" id="queue-health" aria-live="polite"></div>
|
||||||
<div class="coverage-tabs" id="coverage-tabs" aria-label="검색 근거 필터" aria-live="polite"></div>
|
|
||||||
<div class="provider-pulse" id="provider-pulse" aria-label="외부 검색 tool 활용 상태"></div>
|
<div class="provider-pulse" id="provider-pulse" aria-label="외부 검색 tool 활용 상태"></div>
|
||||||
<div class="operator-chip">
|
<div class="coverage-tabs" id="coverage-tabs" aria-label="검색 근거 필터" aria-live="polite"></div>
|
||||||
<span>운영자</span>
|
|
||||||
<strong>rights.ops</strong>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="main-surface" id="app-main">
|
<main class="main-surface" id="app-main">
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue