<!-- task=m-jenkins-credential/01_web_login_entry plan=0 tag=WEB_LOGIN -->

# Plan - WEB_LOGIN

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 구현 후 검증을 실행하고 실제 변경 내용, 설계 결정, 명령 출력은 review stub에 남긴다. active 파일은 그대로 두고 리뷰 준비 상태를 보고하며, archive/log rename/complete.log 작성은 code-review 전용이다. 사용자만 결정할 수 있는 blocker가 생기면 review stub의 `사용자 리뷰 요청`에 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 `USER_REVIEW.md`, `complete.log`를 직접 만들지 않는다.

## 배경

현재 Settings의 `Web Login` segment는 선택만 되어 있고 Jenkins 로그인 WebView를 열지 않는다. `web-login` Epic의 첫 작업은 사용자가 Jenkins URL을 입력하고 WebView에서 로그인 완료를 감지하는 얇은 진입점을 만드는 것이다. API token 발급과 Keychain 저장은 같은 세션이 필요하지만 인증/저장 위험이 커서 다음 종속 plan으로 분리한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자 입력을 요청하지 않고, code-review가 사용자 리뷰 필요 여부와 `USER_REVIEW.md` 작성을 판단한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/usable-mvp/milestones/jenkins-credential.md`
- Task ids:
  - `login-webview`: Jenkins 로그인 URL을 WebView로 열고 로그인 완료를 감지한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/usable-mvp/PHASE.md`
- `agent-roadmap/phase/usable-mvp/milestones/jenkins-credential.md`
- `agent-ops/rules/project/domain/artifact-flow/rules.md`
- `agent-ops/rules/project/domain/macos-platform/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/artifact-flow-smoke.md`
- `agent-test/local/macos-platform-smoke.md`
- `pubspec.yaml`
- `/config/.pub-cache/hosted/pub.dev/webview_flutter-4.13.1/pubspec.yaml`
- `/config/.pub-cache/hosted/pub.dev/webview_flutter-4.13.1/README.md`
- `/config/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.25.1/pubspec.yaml`
- `lib/src/app.dart`
- `lib/src/features/app_shell.dart`
- `lib/src/features/settings/settings_page.dart`
- `lib/src/services/jenkins_client.dart`
- `lib/src/services/token_store.dart`
- `test/builds_page_test.dart`
- `test/jenkins_client_test.dart`
- `test/token_store_test.dart`
- `test/widget_test.dart`

### 테스트 환경 규칙

- 선택 환경: `local`.
- `agent-test/local/rules.md`를 읽었고, artifact-flow smoke와 macos-platform smoke profile을 읽었다.
- 적용 명령: remote runner 기준 `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'`, `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'`.
- WebView macOS plugin 사용을 컴파일로 확인해야 하므로 `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter build macos'`를 최종 검증에 포함한다.
- 현재 checkout에서는 `flutter test test/jenkins_client_test.dart test/builds_page_test.dart`가 redaction 변경 후 통과했다. remote runner evidence는 이 plan 구현 완료 때 새로 수집한다.

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

- Settings의 Jenkins base URL 입력, Web Login 버튼 활성화, launcher 호출은 기존 테스트가 없다. 새 focused widget test를 작성한다.
- 실제 `WebViewWidget`은 widget test에서 플랫폼 view 의존성이 있으므로, SettingsPage는 injectable launcher로 테스트하고 WebView completion 감지는 pure probe/adapter 단위 테스트로 검증한다.
- macOS WKWebView plugin 등록과 빌드는 기존 unit test가 보장하지 않으므로 `flutter build macos`로 보완한다.

### 심볼 참조

- renamed/removed symbol: none.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 공유 task group: `m-jenkins-credential`.
- `01_web_login_entry`: 독립 작업. WebView 진입, URL validation, 로그인 완료 감지만 담당한다.
- `02+01_crumb_token_store`: `01_web_login_entry`의 complete.log가 필요한 종속 작업. crumb/token 발급과 Keychain 저장은 auth/security 위험이 커서 분리한다.
- 이 plan은 predecessor가 없다.

### 범위 결정 근거

- 포함: Settings Jenkins 영역, WebView login page/adapter, login completion result model, focused tests.
- 제외: crumb fetch, API token generation, TokenStore 저장 wiring, logout/reauth flow, Jenkins endpoint별 실제 수동 로그인 검증.
- macOS entitlement 파일은 수정하지 않는다. 현재 Jenkins network client entitlement가 필요하지만 이 plan은 새 entitlement를 추가하지 않는다.

### 빌드 등급

- `local-G05`: UI entry와 adapter 경계는 명확하고 테스트를 injection으로 고정할 수 있으나 WebView/macOS build 확인이 필요하다.

## 구현 체크리스트

- [ ] Settings Jenkins 영역에 base URL controller/validation과 Web Login 실행 버튼을 추가하고, invalid URL에서는 실행하지 않는다.
- [ ] WebView login page 또는 launcher adapter를 추가해 Jenkins login URL을 열고 same-origin `/whoAmI/api/json` probe 성공 시 `login-webview` 완료 결과를 반환한다.
- [ ] SettingsPage focused widget test와 completion probe unit test를 추가한다.
- [ ] `flutter test test/settings_page_test.dart` 또는 추가한 focused test 명령을 통과시킨다.
- [ ] 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [WEB_LOGIN-1] Settings Web Login Entry

#### 문제

[settings_page.dart](/config/workspace/appsok/lib/src/features/settings/settings_page.dart:5)의 `SettingsPage`는 `adbDiagnosticsLoader`만 받는다. Jenkins 영역은 [settings_page.dart](/config/workspace/appsok/lib/src/features/settings/settings_page.dart:67)의 base URL field와 [settings_page.dart](/config/workspace/appsok/lib/src/features/settings/settings_page.dart:89)의 `SegmentedButton`만 있고, [settings_page.dart](/config/workspace/appsok/lib/src/features/settings/settings_page.dart:103)의 handler는 비어 있다.

Before:

```dart
class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key, this.adbDiagnosticsLoader});

  final AdbDiagnosticsLoader? adbDiagnosticsLoader;
```

```dart
SegmentedButton<String>(
  segments: const [
    ButtonSegment(
      value: 'manual',
      label: Text('Token'),
      icon: Icon(Icons.key),
    ),
    ButtonSegment(
      value: 'webview',
      label: Text('Web Login'),
      icon: Icon(Icons.web_asset),
    ),
  ],
  selected: const {'webview'},
  onSelectionChanged: (_) {},
),
```

#### 해결 방법

- `JenkinsWebLoginLauncher` typedef와 `JenkinsWebLoginResult` model을 Settings feature에 둔다.
- `SettingsPage`에 `webLoginLauncher`와 `onWebLoginCompleted`를 주입한다.
- `_baseUrlController`, `_webLoginError`, `_webLoginLoading`, `_lastLoginResult` state를 추가한다.
- `https?` scheme과 host가 있는 URL만 허용하고, Web Login 버튼은 `ValueKey('jenkins-web-login-button')`를 둔다.
- launcher가 null이면 default launcher가 WebView page를 `showDialog` 또는 route로 연다.

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

- [ ] `lib/src/features/settings/settings_page.dart`: controller lifecycle, URL validation, Web Login button, result/error UI.
- [ ] `lib/src/features/app_shell.dart`: optional web login callback을 SettingsPage로 전달.
- [ ] `lib/src/app.dart`: 후속 plan에서 쓸 수 있도록 optional callback surface를 보존하되 token 저장은 하지 않는다.
- [ ] `test/settings_page_test.dart`: invalid URL, valid URL launcher 호출, result 표시 테스트.

#### 테스트 작성

- 작성: `test/settings_page_test.dart`.
- 테스트 이름:
  - `SettingsPage disables web login for invalid Jenkins URL`
  - `SettingsPage calls web login launcher with normalized base URL`
  - `SettingsPage shows completed login user without token text`

#### 중간 검증

```bash
flutter test test/settings_page_test.dart
```

예상: SettingsPage focused tests pass.

### [WEB_LOGIN-2] WebView Login Completion Probe

#### 문제

현재 코드에는 `webview_flutter` dependency가 있지만 WebView UI나 login completion probe가 없다. `pubspec.yaml`에는 `webview_flutter: ^4.13.1`가 있으며, 로컬 package metadata는 macOS default package가 `webview_flutter_wkwebview`임을 보여준다. [app_shell.dart](/config/workspace/appsok/lib/src/features/app_shell.dart:183)는 SettingsPage를 diagnostics loader만 전달해 생성한다.

Before:

```dart
_ShellPage(
  label: '설정',
  icon: Icons.tune_outlined,
  selectedIcon: Icons.tune,
  child: SettingsPage(adbDiagnosticsLoader: widget.adbDiagnosticsLoader),
),
```

#### 해결 방법

- `lib/src/features/settings/jenkins_web_login_page.dart`를 추가한다.
- `WebViewController`를 생성하고 `setJavaScriptMode(JavaScriptMode.unrestricted)`, `setNavigationDelegate(...)`, `loadRequest(loginUri)` 순서로 구성한다.
- login URL은 base URL의 path를 유지하되 기본은 `/login` 또는 Jenkins root load 후 redirect를 허용한다. 구현 중 Jenkins URL 구조가 불명확하면 root를 먼저 열고 `/whoAmI/api/json` probe 성공으로 완료를 감지한다.
- completion probe는 `runJavaScriptReturningResult`로 `fetch('/whoAmI/api/json', {credentials: 'same-origin'})`를 실행해 200이면 username/displayName을 반환한다.
- probe/parsing은 platform view 없이 테스트 가능한 pure helper로 분리한다.

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

- [ ] `lib/src/features/settings/jenkins_web_login_page.dart`: WebView page, loading/error/cancel UI, completion callback.
- [ ] `lib/src/features/settings/settings_page.dart`: default launcher가 새 page를 연다.
- [ ] `test/settings_page_test.dart`: launcher injection 테스트.
- [ ] `test/jenkins_web_login_page_test.dart`: probe JS result parsing과 success/failure 판정 테스트.

#### 테스트 작성

- 작성: `test/jenkins_web_login_page_test.dart`.
- 실제 WebViewWidget 렌더링은 macOS build로 확인하고, unit test는 probe/parsing과 launcher wiring만 검증한다.

#### 중간 검증

```bash
flutter test test/settings_page_test.dart test/jenkins_web_login_page_test.dart
```

예상: focused tests pass.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/src/features/settings/settings_page.dart` | WEB_LOGIN-1, WEB_LOGIN-2 |
| `lib/src/features/settings/jenkins_web_login_page.dart` | WEB_LOGIN-2 |
| `lib/src/features/app_shell.dart` | WEB_LOGIN-1 |
| `lib/src/app.dart` | WEB_LOGIN-1 |
| `test/settings_page_test.dart` | WEB_LOGIN-1 |
| `test/jenkins_web_login_page_test.dart` | WEB_LOGIN-2 |

## 최종 검증

```bash
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter build macos'
```

예상: analyzer issue 없음, 모든 테스트 통과, macOS build 성공.

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