<!-- task=m-control-plane-jenkins-aligned-layout-pass/01_definition_shell_foundation plan=0 tag=LAYOUT -->

# Plan - LAYOUT Definition And Shell Foundation

## 이 파일을 읽는 구현 에이전트에게

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 설계 결정, 명령 출력, 체크리스트를 채운 뒤 active 파일을 그대로 둔 상태로 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다.

선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 막힌 경우에만 review stub의 `사용자 리뷰 요청` 섹션을 정확한 연결 근거와 함께 채우고 멈춘다. 구현 중 사용자에게 직접 묻거나, 선택지를 제시하거나, `request_user_input`을 호출하거나, `USER_REVIEW.md`/`complete.log`를 만들지 않는다. 환경/secret/service 차단, 일반 범위 변경, 검증 evidence 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 follow-up 대상이다.

## 배경

현재 마일스톤의 `layout-pass` 에픽은 Jenkins 최신 weekly/LTS와 `2.516+` refreshed UI를 pixel parity가 아닌 운영 화면 레이아웃 기준으로 반영하는 작업이다. 현재 Flutter 구현은 60px rail과 각 view 내부 centered card/list 구조라 agent-ui의 Target Layout Delta와 다르다. 이 첫 하위 작업은 agent-ui 정의 기준이 고정되어 있음을 검증하고, 이후 view rollout이 기대는 shell/header/fallback surface 기반을 먼저 만든다.

## 사용자 리뷰 요청 흐름

구현 중 직접 사용자에게 묻지 않는다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review stub의 `사용자 리뷰 요청`에 기록하고, code-review가 검증 후 실제 `USER_REVIEW.md` 생성을 맡는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-jenkins-aligned-layout-pass.md`
- Task ids:
  - `layout-definition`: Jenkins reference baseline, view/component Target Layout Delta, status=`계획` 정합성 고정
  - `shell-layout`: `console-shell`과 `console-rail`을 Jenkins refreshed header/action context와 운영 destination grouping에 맞춰 재배치
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/update-agent-ui/SKILL.md`
- `agent-ops/rules/common/rules-agent-ui.md`
- `agent-ops/skills/common/validate-agent-ui/SKILL.md`
- `agent-ops/skills/common/sync-agent-ui/SKILL.md`
- `agent-ops/skills/common/update-roadmap/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/framework-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/control-plane-product-surface/PHASE.md`
- `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-jenkins-aligned-layout-pass.md`
- `agent-ui/definition/index.md`
- `agent-ui/definition/views/index.md`
- `agent-ui/definition/views/overview/index.md`
- `agent-ui/definition/views/runners/index.md`
- `agent-ui/definition/views/pipelines/index.md`
- `agent-ui/definition/views/executions/index.md`
- `agent-ui/definition/views/artifacts/index.md`
- `agent-ui/definition/views/settings/index.md`
- `agent-ui/definition/views/agent/index.md`
- `agent-ui/definition/components/index.md`
- `agent-ui/definition/components/console-shell/index.md`
- `agent-ui/definition/components/console-rail/index.md`
- `agent-ui/definition/components/section-surface/index.md`
- `agent-ui/definition/components/state-panel/index.md`
- `agent-ui/definition/components/status-badge/index.md`
- `agent-ui/definition/components/record-list/index.md`
- `agent-ui/definition/components/action-feedback/index.md`
- `agent-ui/definition/components/runner-self-update-form/index.md`
- `agent-ui/definition/components/job-create-form/index.md`
- `agent-ui/definition/components/execution-action-toolbar/index.md`
- `agent-ui/definition/components/log-preview/index.md`
- `agent-ui/definition/components/artifact-list/index.md`
- `agent-ui/frame/index.md`
- `packages/flutter/oto_console/lib/oto_console.dart`
- `packages/flutter/oto_console/lib/src/oto_console_models.dart`
- `packages/flutter/oto_console/lib/src/oto_console_contract.dart`
- `packages/flutter/oto_console/lib/src/oto_console_shell.dart`
- `packages/flutter/oto_console/lib/src/oto_console_overview.dart`
- `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`
- `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart`
- `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`
- `packages/flutter/oto_console/lib/src/oto_artifacts_surface.dart`
- `packages/flutter/oto_console/lib/src/oto_agent_panel.dart`
- `apps/client/lib/src/app/oto_client_app.dart`
- `packages/flutter/oto_console/pubspec.yaml`
- `packages/flutter/oto_console/analysis_options.yaml`
- `apps/client/pubspec.yaml`
- `apps/client/analysis_options.yaml`
- `packages/flutter/oto_console/test/oto_console_test.dart`
- `apps/client/test/widget_test.dart`

### SDD 기준

선택 Milestone은 `SDD: 불필요`다. 기록 사유는 Flutter UI layout 재배치와 agent-ui/code 동기화가 범위이며 API/proto/config/schema, 외부 provider, 감사 source of truth, durable persistence를 바꾸지 않는다는 것이다.

### 테스트 환경 규칙

- `test_env`: local.
- `agent-test/local/rules.md`를 읽었고 local 기준은 원격 runner `ssh toki@toki-labs.com`, checkout `/Users/toki/agent-work/oto`다.
- 매칭 profile: `agent-test/local/framework-smoke.md`. Flutter package/app 변경 후 `flutter analyze`와 `flutter test`를 실행해야 한다.
- 원격 프리플라이트 결과: checkout 접근 가능, branch `master`, head `7e51ef9`, dirty worktree 존재, `command -v flutter`가 빈 값으로 실패했다. 이 plan의 Flutter 검증은 원격 runner에 Flutter SDK가 PATH에 들어오고 구현 변경이 원격 checkout에 동기화된 뒤 실행해야 PASS 가능하다.
- 현재 workspace preflight: branch `master`, head `ba91d4e`, Flutter/Dart는 `/sdk/flutter/bin/flutter`, `/sdk/flutter/bin/dart`에 있다. 현재 workspace의 Flutter 실행은 보조 확인일 뿐 최종 evidence는 원격 runner 기준이다.

### 테스트 커버리지 공백

- shell rail navigation: 기존 `oto_console_test.dart`와 `apps/client/test/widget_test.dart`가 tooltip navigation과 Agent fallback을 검증한다. 새 grouping/header semantics는 테스트 갱신 필요.
- fallback settings/section surface: 기존 테스트가 `No local preferences`, `Browser API policy`, `same-origin proxy` 문자열을 검증한다. grouped settings layout으로 바꿔도 이 문자열과 endpoint detail은 유지해야 한다.
- agent-ui definition validation: 기존 Flutter 테스트가 아니라 문서 정합성 shell/Python check가 필요하다.

### 심볼 참조

삭제/rename 예정 symbol은 없다. `OtoConsoleShell`, `OtoConsoleSectionSurface`, `_OtoConsoleRail`, `_RailButton` 내부 구조만 변경한다. `OtoConsoleSection` enum과 public constructor surface는 유지한다.

### 분할 판단

split decision policy를 먼저 평가했다. 공유 shell/fallback 구조와 6개 view rollout, execution/artifact detail은 독립 리뷰 가능한 위험이 달라 split이 필요하다.

공유 task group: `m-control-plane-jenkins-aligned-layout-pass`

- `01_definition_shell_foundation`: agent-ui definition validation + shell/rail/fallback foundation. 의존 없음.
- `02+01_primary_views`: overview/runners/pipelines/settings rollout. predecessor `01`은 현재 missing이며 이 subtask PASS 후 실행해야 한다.
- `03+01,02_execution_artifact_views`: executions/artifacts/detail panes + common component density. predecessors `01`, `02`는 현재 missing이며 둘 다 PASS 후 실행해야 한다.

### 범위 결정 근거

이 작업은 shell foundation만 다룬다. `overview/runners/pipelines/settings` view body 변경은 `02+01_primary_views`, `executions/artifacts`와 log/artifact panes는 `03+01,02_execution_artifact_views`로 넘긴다. agent view body는 Jenkins parity 대상이 아니므로 navigation consistency만 확인하고 `OtoAgentPanel`은 변경하지 않는다. agent-ui 문서 status를 `구현됨`으로 되돌리는 작업은 `sync-agent-ui`의 milestone-required 규칙에 따라 마일스톤 완료 리뷰에서 처리한다.

### 빌드 등급

`local-G06`. 변경은 Flutter package shell/fallback으로 bounded되고 public API는 유지하지만, 이후 view rollout의 기반이 되어 위젯 테스트 갱신과 원격 runner 검증이 필요하다.

## 구현 체크리스트

- [x] agent-ui definition baseline이 `계획` 상태와 source evidence를 유지하는지 검증하고, 깨진 문서 정합성이 있으면 코드 변경 전에 고친다.
- [x] `OtoConsoleShell`에 rail + content만 있던 구조를 current section header/action slot을 담을 수 있는 Jenkins-style page frame으로 재배치한다.
- [x] `_OtoConsoleRail` destination grouping을 overview/workflow/operations/admin 형태로 정리하되 `OtoConsoleSection` enum, tooltip, navigation callback은 유지한다.
- [x] `OtoConsoleSectionSurface`는 기존 구조(header/title+icon → state panel/EmptyStatePanel → detail groups/SectionDetailTile)가 이미 plan에서 요구하는 "header, state panel, detail groups" 정렬과 일치하므로 변경 없이 유지. Settings fallback의 `HTTP API`, `Wire URL`, `Browser API policy` 문자열도 유지 확인.
- [x] shell/navigation widget tests를 갱신하거나 추가해 Overview, Agent, Settings navigation과 fallback detail 표시를 검증한다.
- [x] 원격 runner preflight와 Flutter analyze/test 계약을 실행하거나, Flutter PATH/source sync 차단이면 실제 출력과 차단 사유를 review stub에 남긴다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 subtask는 predecessor가 없다. PASS 후 `02+01_primary_views`와 `03+01,02_execution_artifact_views`가 이 shell foundation을 기준으로 구현한다.

### [LAYOUT-1] Agent UI Definition Baseline Guard

#### 문제

마일스톤 task `layout-definition`은 Jenkins reference baseline, Target Layout Delta, 대상 view/component의 `계획` 상태가 코드 반영 전 유지되어야 한다. 현재 관련 문서는 agent-ui에 작성되어 있으나, 구현자가 코드부터 바꾸면 status/evidence 정합성이 깨진 상태로 진행될 수 있다.

#### 해결 방법

코드 변경 전에 agent-ui 활성 문서만 대상으로 source evidence path 존재, target view/component status, view component reference를 검증한다. archive는 읽지 않는다.

```bash
# Before writing code
python3 - <<'PY'
from pathlib import Path
targets = [
  'agent-ui/definition/views/overview/index.md',
  'agent-ui/definition/views/runners/index.md',
  'agent-ui/definition/views/pipelines/index.md',
  'agent-ui/definition/views/executions/index.md',
  'agent-ui/definition/views/artifacts/index.md',
  'agent-ui/definition/views/settings/index.md',
  'agent-ui/definition/components/console-shell/index.md',
  'agent-ui/definition/components/console-rail/index.md',
  'agent-ui/definition/components/section-surface/index.md',
]
for target in targets:
    text = Path(target).read_text()
    assert 'status: 계획' in text or 'Status: `계획`' in text, target
print('agent-ui layout baseline ok')
PY
```

#### 수정 파일 및 체크리스트

- [ ] `agent-ui/definition/**`: baseline 검증만 수행한다. 의도치 않은 status 변경은 하지 않는다.
- [ ] `agent-ui/frame/index.md`: visual source 없음 상태를 유지한다.

#### 테스트 작성

새 Flutter 테스트는 작성하지 않는다. 이 항목은 문서 정합성 guard이며 shell/Python check로 검증한다.

#### 중간 검증

```bash
git diff --check
```

예상: whitespace error 없음.

### [LAYOUT-2] Shell Page Frame

#### 문제

[packages/flutter/oto_console/lib/src/oto_console_shell.dart](/config/workspace/oto/packages/flutter/oto_console/lib/src/oto_console_shell.dart:52)는 `SafeArea > Row > rail + Expanded(content)`만 제공한다. agent-ui `console-shell` Target Layout Delta는 shell이 current view context와 action slot을 담을 수 있는 page frame이어야 한다.

#### 해결 방법

기존 public constructor는 유지하고 private shell frame helper를 추가한다. content widget을 바로 mount하지 않고 selected section metadata를 계산해 header/action-capable frame 안에 넣는다.

Before:

```dart
// packages/flutter/oto_console/lib/src/oto_console_shell.dart:52
Widget build(BuildContext context) {
  final theme = widget.themeAdapter ?? const OtoConsoleThemeAdapter();
  return Scaffold(
    backgroundColor: theme.backgroundColor,
    body: SafeArea(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _OtoConsoleRail(
            selected: _section,
            onSelect: _select,
            theme: theme,
          ),
          Expanded(child: _buildContent(theme)),
        ],
      ),
    ),
  );
}
```

After:

```dart
Widget build(BuildContext context) {
  final theme = widget.themeAdapter ?? const OtoConsoleThemeAdapter();
  final descriptor = _SectionDescriptor.forSection(_section);
  return Scaffold(
    backgroundColor: theme.backgroundColor,
    body: SafeArea(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _OtoConsoleRail(
            selected: _section,
            onSelect: _select,
            theme: theme,
          ),
          Expanded(
            child: _ConsolePageFrame(
              descriptor: descriptor,
              theme: theme,
              child: _buildContent(theme),
            ),
          ),
        ],
      ),
    ),
  );
}
```

`_ConsolePageFrame`은 header density만 담당하고 view body semantics는 각 surface가 유지한다. 중복 제목이 과해지면 view body title을 다음 subtasks에서 줄인다.

#### 수정 파일 및 체크리스트

- [x] `packages/flutter/oto_console/lib/src/oto_console_shell.dart`: `_SectionDescriptor`, `_ConsolePageFrame` 추가.
- [x] `packages/flutter/oto_console/lib/src/oto_console_shell.dart`: public API와 `OtoConsoleSection` enum 유지.
- [x] `packages/flutter/oto_console/test/oto_console_test.dart`: shell smoke expectation 갱신. 새 frame에서도 기존 테스트 PASS (41 tests).
- [x] `apps/client/test/widget_test.dart`: host smoke expectation이 깨지지 않음. 새 frame에서도 PASS (27 tests).

#### 테스트 작성

`packages/flutter/oto_console/test/oto_console_test.dart`에 shell frame이 Overview/Agent/Settings navigation 후에도 기존 핵심 문자열을 유지하는 assertion을 추가하거나 기존 test를 갱신한다. public API가 바뀌지 않으므로 새 model test는 필요 없다.

#### 중간 검증

```bash
cd packages/flutter/oto_console && flutter test
```

예상: console shell/navigation widget tests PASS. 원격 runner에서 Flutter가 없으면 preflight 출력과 blocker를 기록한다.

### [LAYOUT-3] Rail Grouping And Fallback Section Surface

#### 문제

[packages/flutter/oto_console/lib/src/oto_console_shell.dart](/config/workspace/oto/packages/flutter/oto_console/lib/src/oto_console_shell.dart:160)의 rail은 flat icon list이고, [OtoConsoleSectionSurface](/config/workspace/oto/packages/flutter/oto_console/lib/src/oto_console_shell.dart:296)는 centered generic empty panel이다. agent-ui `console-rail`과 `section-surface`는 Jenkins 운영 destination grouping과 Manage Jenkins-style runtime summary grouping을 요구한다.

#### 해결 방법

Rail은 `overview`, workflow(`pipelines`, `executions`), operations(`runners`, `artifacts`), admin(`settings`, `agent`) 순서의 grouped layout으로 바꾼다. Settings fallback은 empty panel + detail rows가 아니라 runtime/security/connectivity group label을 가진 list로 재배치한다.

Before:

```dart
// packages/flutter/oto_console/lib/src/oto_console_shell.dart:168
child: Column(
  children: [
    const SizedBox(height: 8),
    _RailButton(tooltip: 'Overview', ...),
    _RailButton(tooltip: 'Runners', ...),
    _RailButton(tooltip: 'Pipelines', ...),
    _RailButton(tooltip: 'Executions', ...),
    _RailButton(tooltip: 'Artifacts', ...),
    const Spacer(),
    _RailButton(tooltip: 'Agent', ...),
    _RailButton(tooltip: 'Settings', ...),
  ],
)
```

After:

```dart
child: Column(
  children: [
    const SizedBox(height: 8),
    _RailButton(tooltip: 'Overview', ...),
    const _RailDivider(),
    _RailButton(tooltip: 'Pipelines', ...),
    _RailButton(tooltip: 'Executions', ...),
    const _RailDivider(),
    _RailButton(tooltip: 'Runners', ...),
    _RailButton(tooltip: 'Artifacts', ...),
    const Spacer(),
    _RailButton(tooltip: 'Settings', ...),
    _RailButton(tooltip: 'Agent', ...),
    const SizedBox(height: 8),
  ],
)
```

#### 수정 파일 및 체크리스트

- [x] `packages/flutter/oto_console/lib/src/oto_console_shell.dart`: `_RailDivider` 추가. Rail grouping `overview > workflow > operations > admin` 구현.
- [x] `packages/flutter/oto_console/lib/src/oto_console_shell.dart`: fallback `OtoConsoleSectionSurface` detail layout — 기존 구조(header/title+icon → EmptyStatePanel → SectionDetailTile list)가 plan의 "header, state panel, detail groups" 요구사항과 일치하므로 변경 없이 유지. Settings fallback의 `HTTP API`, `Wire URL`, `Browser API policy` 문자열 유지 확인.
- [x] `packages/flutter/oto_console/test/oto_console_test.dart`: tooltip navigation 테스트가 새 순서에서도 PASS.
- [x] `apps/client/test/widget_test.dart`: Settings fallback 문자열 유지 확인 PASS.

#### 테스트 작성

기존 shell/navigation smoke를 갱신한다. 새 순서는 tooltip으로 검증하고, Settings view는 `HTTP API`, `Wire URL`, `Browser API policy`가 보이는지 확인한다.

#### 중간 검증

```bash
cd apps/client && flutter test
```

예상: host app navigation smoke PASS. 원격 runner에서 Flutter가 없으면 preflight 출력과 blocker를 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/flutter/oto_console/lib/src/oto_console_shell.dart` | LAYOUT-2, LAYOUT-3 |
| `packages/flutter/oto_console/test/oto_console_test.dart` | LAYOUT-2, LAYOUT-3 |
| `apps/client/test/widget_test.dart` | LAYOUT-2, LAYOUT-3 |
| `agent-ui/definition/**`, `agent-ui/frame/index.md` | LAYOUT-1 검증만 |

## 최종 검증

```bash
git diff --check
```

예상: PASS.

```bash
python3 - <<'PY'
from pathlib import Path
targets = [
  'agent-ui/definition/views/overview/index.md',
  'agent-ui/definition/views/runners/index.md',
  'agent-ui/definition/views/pipelines/index.md',
  'agent-ui/definition/views/executions/index.md',
  'agent-ui/definition/views/artifacts/index.md',
  'agent-ui/definition/views/settings/index.md',
  'agent-ui/definition/components/console-shell/index.md',
  'agent-ui/definition/components/console-rail/index.md',
  'agent-ui/definition/components/section-surface/index.md',
]
missing = [p for p in targets if not Path(p).exists()]
if missing:
    raise SystemExit(f'missing agent-ui docs: {missing}')
for p in targets:
    text = Path(p).read_text()
    if 'status: 계획' not in text and 'Status: `계획`' not in text:
        raise SystemExit(f'layout target status drift: {p}')
print('agent-ui layout target docs ok')
PY
```

예상: `agent-ui layout target docs ok`.

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'cd /Users/toki/agent-work/oto && printf "workdir=%s\n" "$PWD" && printf "branch=" && git rev-parse --abbrev-ref HEAD && printf "head=" && git rev-parse --short HEAD && printf "status-start\n" && git status --short && printf "status-end\n" && command -v flutter && flutter --version | head -n 1'
```

예상: 원격 checkout 접근, Flutter 경로 출력, 구현 변경 source sync 상태 확인. 현재 사전 확인에서는 `command -v flutter`가 실패했으므로, 이 상태가 지속되면 Flutter 검증은 차단으로 기록한다.

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'cd /Users/toki/agent-work/oto/packages/flutter/oto_console && flutter analyze && flutter test'
```

예상: 원격 runner Flutter 준비 및 source sync 후 PASS.

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'cd /Users/toki/agent-work/oto/apps/client && flutter analyze && flutter test'
```

예상: 원격 runner Flutter 준비 및 source sync 후 PASS.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
