AgentDeck · Design Proposal · v1
Surface — Android E-ink (B&W + Color Kaleido 3)
유지보수 가능한 e-ink 레이아웃 제안

# 한 모델,
한 화면,
두 종이.

iOS/Android와의 외형 동기화는 e-ink에서 실패한다.
공통화 대상은 의미 모델이지 픽셀 위치가 아니다.
Targets
Crema S · Onyx · Kobo · Pantone 6 (Kaleido 3)
Scope
EinkMonitorScreen · projection · refresh policy
Related
docs/android-ui.md · MonitorScreen (tablet)
진단

## 왜 LCD/LED 레이아웃을 e-ink에 그대로 얹으면 깨지는가

iOS/Android tablet의 MonitorScreen은 오버레이와 색상으로 정보를 압축한다. 두 가지 모두 e-ink에서 비싸다. 화면을 통일하려는 시도는 결국 같은 4가지 충돌을 만든다.

01
PHYSICS

### 오버레이는 고스팅을 만든다

반투명 HUD 패널은 LCD에서 자연스럽지만, EPD에서는 alpha 합성이 부분 갱신과 결합되며 잔상을 누적시킨다. GC16 full refresh로 지우는 순간 사용자는 200–500ms의 검은 깜박임을 본다.

02
PALETTE

### 색상은 의미를 전달하지 못한다

B&W 16-grey, Kaleido 3 4096-color는 LCD 채도와 다르다. 빨강=거부, 초록=승인 같은 순수 색 의존 코드는 색약 사용자뿐 아니라 e-ink에서도 무너진다. 글리프와 텍스트가 권위를 가져야 한다.

03
DENSITY

### 14sp 미만의 글자는 읽히지 않는다

tablet HUD는 12sp 라벨이 흔하다. e-ink는 dither 한계 + 저대비로 작은 글자가 무너진다. “같은 픽셀 위치”를 강제하면 정보가 가독성에 양보된다.

04
REFRESH

### 실시간 영역과 정적 영역이 섞인다

크리처 애니메이션은 A2(10fps), 게이지는 DU(2s), 세션 패널은 거의 정적이다. 이를 한 레이어에서 그리면 가장 빠른 zone의 잔상이 가장 느린 zone으로 번진다.

05
MAINTENANCE

### 두 트리가 하나처럼 보이려고 한다

MonitorScreen과 EinkMonitorScreen이 같은 외형을 흉내내려 할수록 공유 코드가 줄어든다. e-ink는 자기 형태를 가진 viewer일 때 가장 적은 코드로 정확하다.

06
DEVICE FAN-OUT

### 벤더 EPD API가 다르다

Rockchip EinkManager, Onyx BaseDevice, KOReader RK35xxEPDController — refresh mode 호출 표면이 셋이다. UI가 직접 호출하면 디바이스 분기가 UI 코드로 새어 나온다.

원칙

## E-ink는 모방하지 않는다.
읽힌다.

네 가지 원칙을 코드 경계로 강제한다. 이것이 깨지는 코드 변경은 review에서 reject한다.

01

#### Shared semantics, separate pixels

DashboardState · FocusAction · AttentionRequest는 공통. 레이아웃은 surface마다 자유.

02

#### Slots over overlays

반투명 오버레이 금지. 화면은 명시적 zone으로만 분할. 각 zone은 자기 refresh policy를 갖는다.

03

#### Glyph + text first, color last

승인/거부/대기/연결 모두 글리프와 라벨로 1차 표현. 색은 color e-ink에서 추가 강조용일 뿐.

04

#### Vendor EPD via one port

UI는 EpdRefreshController만 호출. Rockchip/Onyx/Kobo/Color 분기는 driver 모듈 내부에 갇혀 있다.

아키텍처

## Refresh-Zone Layered Projection

#### 레이어 구조

┌────────────────────────────────────────────────────┐ │ shared / state-model (공통) │ │ DashboardState · FocusAction · AttentionRequest │ │ Timeline · UsageGauge · AgentRow │ └────────────────────────────────────────────────────┘ ▲ ▲ │ │ ┌─────────────┴──────────┐ ┌──────────┴──────────────┐ │ MonitorScreen (LCD) │ │ EinkMonitorScreen │ │ - free overlays │ │ - fixed slot grid │ │ - 60fps motion │ │ - per-zone refresh │ │ - color = primary │ │ - glyph = primary │ └────────────────────────┘ └────────┬────────────────┘ │ uses ┌────────▼────────────┐ │ EpdRefreshController│ port ├─────────────────────┤ │ RockchipEpdDriver │ │ OnyxEpdDriver │ │ KoreaderEpdDriver │ │ ColorKaleidoDriver │ └─────────────────────┘

#### Zone 종류

Zone | Refresh | 역할
--- | --- | ---
CHROME | DU · 2 s | 로고, 세션 목록, settings 진입. 거의 안 변함.
FOCUS | A2 · 200 ms | 현재 선택한 세션의 model · state · project.
TERRARIUM | A2/anim · 10 fps | 크리처 애니메이션. 상태 전환에서만 GC16 cleanup.
GAUGES | DU · 2 s | 5h/7d 게이지, OAuth/Ollama 모델 카탈로그.
TIMELINE | A2 · 300 ms | 이벤트 로그. append-only이므로 부분 갱신 친화적.
ATTENTION | GC16 once | awaiting_* 진입 시 한 번 full refresh, 답변 옵션 명료화.

각 zone은 RefreshHint를 emit하고 EpdRefreshController가 디바이스별로 번역한다. UI 코드는 벤더 분기 0줄.

레이아웃

## 화면은 슬롯이다.
오버레이는 없다.

Chrome — Title bar
AgentDeck · timestamp · battery · Settings · rotate
DU · static
Chrome — Session List (28%)
DU · static
agent rows (primary + siblings)
tap → focus_session
attention pip (◆) when sibling awaits
Focus + Terrarium (Body)
A2 · live
• Focus card (top, pinned) — model · project · state · attention
• Terrarium frame (bottom) — 크리처는 여기서만 움직인다
• ATTENTION 발생 시 focus card가 이 영역을 22→34%로 확장
Gauges + Models
DU · 2s
5h · 7d 블록 게이지 · OAuth catalog · Ollama running
Timeline
A2 · append-only
이벤트 한 줄씩 push. 8줄 도달 시 위에서 한 줄 drop.
목업 — B&W

## Crema S · Onyx · Kobo

16-grey · landscape 1404×1872 dp 등가 · GC16 + DU + A2 혼합

AgentDeck 14:32
● 84% ↻ ⚙
Sessions · 4
✱ claude · web PROC
✱ claude · cli IDLE
◎ codex IDLE
⬢ openclaw ROUTE
Topology
↑ hooks · gateway
↓ SD+ · D200H · TC001
PROJECT · agentdeck
claude-code · web
opus-4.1 ● PROCESSING session 14:18 · 14m
claude·web
Limits · 5 h
48%
↻ 1h 02m
Limits · 7 d
23%
↻ 4d 10h
Models
OAuth: opus-4.1 · sonnet-4 · haiku-4.5
Ollama: qwen3:30b · bge-m3
14:31▶Read apme/runner.ts
14:31·model call opus-4.1
14:32·IDLE → PROCESSING
14:32★eval result · coding · 0.78
State · IDLE/PROCESSING Refresh budget · ~1.4 fps avg
AgentDeck 14:33
◆ ATTENTION ● 84% ⚙
Sessions · 4
✱ claude · web IDLE
◆ claude · cli AWAIT
◎ codex PROC
⬢ openclaw SIT
PROJECT · scratch-render
claude-code · cli
sonnet-4 ◆ AWAITING permission prompt
PERMISSION REQUEST · 3 OPTIONS
Allow Bash(rm -rf .zig-cache) in this project?
[Y] Approve once [A] Always for this project [N] Deny
claude·cli ◆
5 h
48%
7 d
23%
Models · active 3
Claude · OpenClaw Gateway · qwen3:30b
14:32·PROCESSING → AWAITING
14:33◆permission · Bash(rm -rf …)
14:33·focus → claude·cli
State · AWAITING (single GC16 on entry)Glyph + label · 색 의존 0%
목업 — Color e-ink

## Pantone 6 · Kaleido 3

4096 색 · A2 animation mode · 같은 레이아웃, 의미 매핑은 색을 강조로만 사용. B&W 화면을 그대로 켰을 때 정보가 손실되지 않는 것이 ground truth.

AgentDeck 14:32
● 84% ↻ ⚙
Sessions · 4
✱ claude · web PROC
✱ claude · cli IDLE
◎ codex IDLE
⬢ openclaw ROUTE
PROJECT · agentdeck
claude-code · web
opus-4.1 ● PROCESSING 14m
claude·web
Limits · 5 h
48%
↻ 1h 02m
Limits · 7 d
23%
↻ 4d 10h
Models
OAuth: opus-4.1 · sonnet-4 · haiku-4.5
Ollama: qwen3:30b · bge-m3
14:31▶Read apme/runner.ts
14:32·IDLE → PROCESSING
14:32★eval · coding · 0.78
Color = enhancement only Same layout · same glyphs
AgentDeck 14:33
◆ ATTENTION ● 84% ⚙
Sessions · 4
✱ claude · web IDLE
◆ claude · cli AWAIT
◎ codex PROC
⬢ openclaw SIT
PROJECT · scratch-render
claude-code · cli
sonnet-4 ◆ AWAITING permission
PERMISSION REQUEST · 3 OPTIONS
Allow Bash(rm -rf .zig-cache) in this project?
[Y] Approve once [A] Always [N] Deny
claude·cli ◆
5 h
48%
7 d
23%
Active
Claude · OpenClaw · qwen3:30b
14:33◆permission · Bash(rm -rf …)
Color = enhancement only Glyph still works in B&W
유지보수성

## UI는 hint를 쏘고,
드라이버가 번역한다.

#### Before — UI에 벤더 분기가 새어 나옴

    // EinkAquariumFrame.kt — 현재 형태
    if (manufacturer == "rockchip") {
      einkManager.setMode("12") // A2
      einkManager.sendOneFullFrame()
    } else if (manufacturer == "onyx") {
      device.setViewDefaultUpdateMode(A2_MODE)
    } else if (isColorEink) {
      // skip dither, use animation mode
      ...
    }

UI 컴포넌트가 디바이스 SPI를 직접 부른다. 새 e-ink 기기 추가 = UI 코드 수정.

#### After — Zone이 hint를 emit, controller가 routing

    // EinkMonitorScreen.kt
    EinkZone(id = "terrarium", hint = RefreshHint.Anim(fps = 10)) {
      TerrariumProjection(state)
    }
    EinkZone(id = "attention", hint = RefreshHint.FullOnce) {
      AttentionProjection(state.attention)
    }

    // EpdRefreshController.kt — driver port
    interface EpdRefreshController {
      fun apply(zone: ZoneId, hint: RefreshHint)
    }
    // RockchipEpdDriver · OnyxEpdDriver · ColorKaleidoDriver
    // 새 기기 추가 = driver 한 파일 추가

#### 공유되는 것 / 분리되는 것

레이어 | LCD/LED와 공유? | 위치 | 설명
--- | --- | --- | ---
DashboardState · FocusAction · AttentionRequest | SHARED | shared/state-model | WS 프로토콜에서 직접 derived. 모든 surface가 같은 store를 읽는다.
Creature sprite paths · Timeline parser | SHARED | shared/render | SVG path → Compose path / Canvas drawRect fallback. 한 source.
Layout · zones · refresh hints | EINK ONLY | ui/eink/projection | tablet/iOS와 모양이 달라야 한다. 흉내내지 않는다.
EpdRefreshController · vendor drivers | EINK ONLY | ui/eink/epd | Rockchip · Onyx · Kaleido 분기는 여기서 끝난다. UI는 모른다.
Color palette · glyph table | SHARED w/ fallback | shared/tokens | 같은 의미 키, B&W 매핑 + color e-ink 매핑이 한 테이블에 있음.

실행 계획

## 점진적으로 옮긴다.
한 번에 갈아엎지 않는다.

01

#### Extract EpdRefreshController

현재 EinkRenderer·EinkAnimatedRefreshZone 안에 흩어진 벤더 호출을 driver port로 모은다. UI 변경 없음. 위험 낮음.

~1 sprint
02

#### Introduce EinkZone primitive

공통 wrapper로 zone을 감싼다. 기존 EinkMonitorScreen의 5 영역을 순차적으로 zone으로 마이그레이션. 화면은 그대로.

~1 sprint
03

#### Reflow → fixed slot grid

오버레이를 모두 slot으로 변환. Attention zone을 GC16 once 정책으로 도입. Crema/Pantone QA를 같이 돌려 잔상 회귀 확인.

~1.5 sprint
04

#### Glyph-first state coding

색에만 의존하는 state 표현 제거. Settings에서 “색 강조 끄기” 토글로 회귀 테스트.

~0.5 sprint
05

#### Color enhancement layer

Kaleido 3에서만 활성화되는 token override layer. B&W 화면을 켜면 자동으로 빠진다.

~0.5 sprint
06

#### Driver fan-out

Onyx · Kobo 드라이버 추가. 각 1 PR. UI/projection 변경 없이 새 디바이스 지원.

ongoing
결과적으로
UI 한 트리, EPD 분기 0줄. e-ink는 자기 형태로, 의미는 한 모델로.
Proposal · ready for review