# 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.
