Ubisam-MobileGit/docs/development-log.md
choiminyeong 7da04a77ae Add TASK-023: tolerate a literal JSON null list response
Gitea's Go handlers serialize a nil slice as the JSON literal `null`
rather than `[]` for some list endpoints when there are no items
(e.g. a repository with no branches). parseList treated that as a
parse failure and threw ApiFailure.InvalidResponse, surfacing as a
misleading "server error" on a completely successful 200 OK.

Diagnosed live against the human product owner's real Gitea instance
via wireless adb (repositories CSOT-T8-iPTN-AMA / CSOT-T8-MACRO_SA):
confirmed via the Gitea container's own request log that the call
was a genuine 200 OK, then used temporary debug-only logging to
narrow the cause to the literal null body. Fixed at the shared
parseList helper so every sibling list endpoint benefits, not just
the one that was reported.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 23:04:43 +09:00

62 KiB

UBGitea development log

This is the append-only implementation and handoff record shared by Codex and Claude. Only the current task owner or assigned reviewer may append an entry, and agents never edit this file concurrently.

2026-08-25 — TASK-001

  • Agent and role: Codex, implementer and integrator
  • Commit/diff: 75aa5f8
  • Features implemented: Android Compose project bootstrap, Korean placeholder home screen, deterministic Gradle wrapper, unit-test and lint baseline.
  • How it was implemented: One application module using Kotlin, Compose, Material 3, API 28 minimum, API 36 target, JDK 17, and version-catalog-managed dependencies.
  • Files changed: Root Gradle configuration, app/, ADR-001, product and task documentation.
  • Checks and results: Debug APK build passed; 1 unit test passed; lint passed; sensitive-pattern scan found no assigned secret.
  • Review findings and resolutions: Independent review found no P0-P3 findings.
  • Known limitations: No device/emulator launch; final icon and authentication were out of scope.
  • Next recommended task: Repository browsing foundations.
  • Next owner: Codex.

2026-08-25 — TASK-003

  • Agent and role: Codex, implementer and integrator
  • Commit/diff: a2cc7d4
  • Features implemented: Immutable repository, branch, tree, file, commit and paging domain contracts; safe coordinate/ref/path validation; read-only repository interface and browse use cases.
  • How it was implemented: Pure Kotlin domain models, defensive byte/list copies, shared pagination validation, Korean read-only placeholder state, and fake-adapter unit tests.
  • Files changed: Domain model/repository/use-case packages, focused tests, home placeholder, product roadmap and TASK-003 through TASK-007 documents.
  • Checks and results: Build passed; 22 unit tests passed; lint passed; sensitive-pattern and diff checks passed.
  • Review findings and resolutions: Two P2 findings covering page output mutability and missing use-case boundary tests were fixed and independently re-reviewed; no findings remain.
  • Known limitations: Live HTTP, authentication, navigation, content rendering, and device testing were out of scope.
  • Next recommended task: OAuth protocol core followed by Android OAuth integration.
  • Next owner: Codex.

2026-08-25 — TASK-002A

  • Agent and role: Codex, implementer and integrator
  • Commit/diff: 6da016c
  • Features implemented: OAuth client configuration, approved read-only scopes, fresh PKCE S256 verifier/state generation, authorization URL construction, exact redirect validation, transaction expiry, denial/success modeling, and strict callback query validation.
  • How it was implemented: Pure Kotlin/JDK code with SecureRandom, SHA-256, unpadded Base64 URL encoding, RFC 7636 verifier grammar, deterministic query component encoding, constant-time state comparison, strict UTF-8 percent decoding, duplicate/ambiguous response rejection, saturating time boundaries, and redacted string representations for sensitive values.
  • Files changed: core/auth/protocol/, matching protocol tests, ADR-002, TASK-002 umbrella, TASK-002A, and TASK-002B.
  • Checks and results: Focused OAuth tests 21/21 passed; full unit tests 43/43 passed; debug APK build passed; Android lint passed; diff check passed; sensitive-pattern scan found no assigned secret/token/password/private key.
  • Review findings and resolutions: P2 verifier grammar fixed with strict RFC character/length validation; P2 parameter pollution and malformed UTF-8 fixed with fail-closed parsing; P3 timestamp overflow fixed with saturating boundaries. Independent re-review found no remaining P0-P3 findings.
  • Known limitations: System browser launch, App Link dispatch, code exchange, Keystore token vault, user identity, and real-server E2E are intentionally deferred to TASK-002B. E2E remains blocked by signing fingerprints and hosted assetlinks.json.
  • Next recommended task: TASK-002B Android OAuth integration, completing locally testable browser/storage/exchange boundaries before the external E2E gate.
  • Next owner: Codex.

2026-08-25 — TASK-011

  • Agent and role: Codex, implementer and integrator
  • Commit/diff: current TASK-011 working-tree diff; user-owned assetlinks.json excluded
  • Features implemented: System-browser Gitea OAuth login, verified HTTPS App Link callback, PKCE code exchange without a client secret, Keystore-backed token storage and logout, authenticated identity check, and a paginated list of every repository visible to the account.
  • How it was implemented: Exact callback validation reuses TASK-002A; pending verifier/state is consumed once; OkHttp runs asynchronously with cancellation/error propagation; Gson DTOs fail closed into typed API errors; /api/v1/user precedes bounded/deduplicated /api/v1/user/repos; Compose exposes localized signed-out, browser-waiting/cancel, loading, content, empty, offline, permission, expired-auth, callback, and server-error states.
  • Files changed: Android manifest and extraction rules, network/auth/repository data layers, repository ViewModel/Compose screen, dependencies/resources, focused tests, and TASK-002B/004/005/011 status documentation.
  • Checks and results: Debug APK build passed; 57 unit tests across 16 suites passed; Android lint passed; diff and sensitive-pattern checks passed; no WebView or production client secret exists.
  • Review findings and resolutions: Browser cancellation dead-end, HTTP response-read exception handling, signed-in error recovery/logout, nullable DTO validation, immutable UI snapshots, and bounded pagination coverage were fixed. Final independent re-review reported no remaining P0/P1.
  • Known limitations: No Android device is connected, so the hosted App Link, system-browser return, live account login, repository result, and device Keystore behavior require one manual E2E run on the target phone.
  • Next recommended task: Install the debug APK on the target Android device and verify login plus the internal repository list; after that, begin branch browsing.
  • Next owner: Human product owner for device E2E, then Codex.

2026-08-25 — TASK-011 device E2E closure

  • Agent and role: Human product owner, device verifier; Codex, evidence recorder
  • Verification performed: Installed application login against the production company Gitea and retrieval of the authenticated account's internal repository list.
  • Result: System-browser OAuth, verified App Link return, authenticated API access, and repository-list rendering passed on the target device.
  • Review status: Automated gates previously passed and final independent review reported no P0/P1; the remaining manual device gate is now closed.
  • Task status: TASK-011 complete.
  • Next recommended task: Add repository selection and branch listing.
  • Next owner: Codex.

2026-08-25 — TASK-012

  • Agent and role: Codex, workflow implementer
  • Commit/diff: TASK-012 documentation diff
  • Features implemented: Durable current-state and remaining-roadmap handoff, Claude working-style prompt, and bidirectional append-only question/answer mailboxes.
  • How it was implemented: Writer-specific directories and immutable per-message files preserve the single-writer protocol; the roadmap orders work by frozen-contract dependencies and assigns one owner per task.
  • Files changed: handoff/, TASK-006A status, TASK-012, collaboration documentation, and this log.
  • Checks and results: Documentation paths, baseline commits, ownership, task dependencies, and mailbox cross-references inspected; no Android code or external infrastructure changed.
  • Review findings and resolutions: Pending Claude read-only review of the handoff package.
  • Known limitations: Mailboxes coordinate repository-based handoff and do not provide real-time notification; agents must inspect them at task start.
  • Next recommended task: Claude implements TASK-006A only and hands its commit to Codex for review.
  • Next owner: Claude.

2026-08-25 — TASK-012 review closure and activation

  • Agent and role: Codex, finding resolver and handoff owner; Claude, independent read-only reviewer
  • Commit/diff: reviewed package commit 57d1c09; immutable activation ref handoff/task-006a-ready
  • Findings resolved: Canonical delivery sequence now explicitly permits independent TASK-006A before the TASK-004/005 remainder; TASK-006A authorizes only its completion-log append; both mailbox directions share symmetric immutable templates and unique correction filenames; completed OAuth evidence is synchronized.
  • Checks and results: Documentation cross-references and task dependencies inspected; git diff --check passed before activation; no Android production file or external infrastructure changed.
  • Review result: No handoff activation occurs until the tag resolves to the committed metadata state. Claude checks out the tag, creates a claude/ branch, implements TASK-006A only, and then stops for Codex review.
  • Known limitation: Repository mailboxes are asynchronous and must be checked manually at task start.
  • Next owner: Claude for TASK-006A.

2026-08-25 — TASK-006A implementation

  • Agent and role: Claude, assigned feature implementer (writable scope limited to feature/codebrowser/content/ and this task's evidence/log entries)
  • Commit/diff: branch claude/task-006a-ready created from activation tag handoff/task-006a-ready (base 965d55d); implementation adds ContentClassification.kt, SourceContentClassifier.kt, and SourceContentClassifierTest.kt under feature/codebrowser/content/
  • Features implemented: Pure-Kotlin classifier that turns already-loaded SourceContent bytes into one of five immutable states — bounded text (with independent line/character truncation flags), binary, Git LFS pointer, unsupported encoding, or oversized — using caller-supplied positive byte/character/line limits.
  • How it was implemented: Oversized input is rejected by raw byte count before any decoding; binary is detected by a raw NUL-byte scan; text decoding uses a strict UTF-8 CharsetDecoder with CodingErrorAction.REPORT for malformed and unmappable input so it never substitutes replacement characters; a Git LFS pointer is recognized only from the exact canonical version line followed by valid oid sha256:<64 hex> and size <integer> lines within the 1024-byte spec bound; remaining text is truncated independently by line count and character count with an explicit isTruncated flag. No shared domain, data, Gradle, manifest, or navigation file was modified; RepositoryModels.kt/RepositoryPath.kt/RepositoryRef.kt remain identical to contract-freeze commit 0929282.
  • Files changed: app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/content/ContentClassification.kt, app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/content/SourceContentClassifier.kt, app/src/test/java/com/ubisam/ubgitea/feature/codebrowser/content/SourceContentClassifierTest.kt, tasks/006a-code-content-classifier.md (evidence/status), this log entry.
  • Checks and results: ./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.codebrowser.content.*" passed (25/25); ./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks passed (82 unit tests repository-wide, 0 failures; lint 0 errors, 22 pre-existing warnings unchanged from TASK-001 baseline); git diff --check clean; sensitive-pattern scan of changed files found no secret/token/password/private-key markers.
  • Review findings and resolutions: None yet — pending Codex read-only review.
  • Known limitations: No Android device/emulator was used, since this task has no Android framework dependency (pure JVM-testable Kotlin); static formatting/check task remains unconfigured for the module.
  • Next recommended task: Codex reviews this commit read-only per docs/workstreams.md cross-review rules; TASK-006 UI remains blocked until Codex freezes the TASK-004/005 contracts.
  • Next owner: Codex, for review.

2026-08-26 — TASK-006A Codex integration review

  • Agent and role: Codex, integrator and reviewer
  • Commit/diff: Claude commit 979729a, integrated as 4f5faf3, plus Codex Unicode-boundary fix
  • Review findings and resolutions: One P2 was found: UTF-16 substring truncation could split an emoji or another supplementary character into a lone surrogate. Rendering now counts and truncates Unicode code points, with a focused emoji regression test.
  • Checks and results: Focused classifier, full unit-test, debug-build, lint, diff, and sensitive-pattern gates are rerun after the fix.
  • Known limitations: TASK-006 remains blocked on completion and freeze of the TASK-004/005 API and navigation contracts.
  • Next recommended task: Codex completes the TASK-004 branch/tag/tree/file API remainder, then TASK-005 repository selection and branch browsing.
  • Next owner: Codex.

2026-08-26 — TASK-004

  • Agent and role: Codex, implementer and integrator
  • Commit/diff: TASK-004 working diff following ed2121b
  • Features implemented: Authenticated read-only repository, branch, tag, directory-tree, Base64 source-file, and commit API adapter with immutable pagination.
  • How it was implemented: OkHttp HttpUrl encodes every owner/repository/path segment and ref/path query independently; private nullable Gson DTOs validate before mapping; bearer-token access is injected; contents arrays are locally paged; file type/path/encoding/size are verified; HTTP and network outcomes use typed failures.
  • Files changed: Core network failures, repository data adapter/tests, tag domain/use-case contracts, HTTP decision ADR, Gradle test dependency, TASK-004 evidence, and this log.
  • Checks and results: Focused in-memory and MockWebServer tests passed; repository-wide debug build and lint passed; 90 unit tests across 19 suites passed with zero failures/errors; diff check and focused sensitive-pattern scan passed.
  • Review findings and resolutions: Self-review added page-overflow protection, returned-file path matching, nullable size validation, Unicode-safe prior classifier integration, and a real local HTTP boundary test.
  • Known limitations: Gitea contents has no server-side pagination, so later directory pages repeat the request and slice the immutable returned snapshot locally.
  • Review status: Codex completed source/contract self-review. Independent review is deferred while the user has assigned the serial implementation stream to Codex and must be completed before TASK-006 contract freeze.
  • Next recommended task: TASK-005 repository selection and branch/tag browsing UI.
  • Next owner: Codex.

2026-08-26 — TASK-005

  • Agent and role: Codex, implementer and integrator.
  • Commit/diff: TASK-005 working diff following 8a47d30; the exact completion commit is recorded after commit creation.
  • Features implemented: Repository selection plus repository-scoped branch and tag browsing, default/protected/selected reference indicators, abbreviated commit IDs, refresh, retry, logout, and deterministic back navigation.
  • How it was implemented: A unidirectional immutable ViewModel state machine consumes the TASK-004 read-only browser contract, loads bounded pages serially, deduplicates references by name, cancels replaced work, and restores the cached repository-list snapshot. Small gateway interfaces make authentication and repository restoration JVM-testable without Android framework construction.
  • Files changed: Repository Compose/ViewModel/presenter and tests, MainActivity callback wiring, testable authentication/repository gateway boundaries, localized repository strings, and TASK-005 evidence.
  • Checks and results: Focused presenter/ViewModel tests passed; repository-wide debug build, 96 unit tests across 21 suites, and Android lint passed; diff and focused sensitive-pattern checks passed.
  • Review findings and resolutions: Self-review added immutable reference snapshots, operation cancellation on back navigation, typed reference errors retaining repository context, and ViewModel tests for selection/restoration/error behavior. Independent review is deferred while Codex owns the serial stream.
  • Known limitations: No device/emulator interaction test was available in this work cycle. The reviewed contract-freeze checkbox remains open until independent integration review.
  • Next recommended task: TASK-006 directory navigation and bounded source-file viewing using the TASK-004 API and reviewed TASK-006A classifier.
  • Next owner: Codex for continuous implementation; Claude may later perform the independent review when re-enabled by the product owner.

2026-08-26 — TASK-006

  • Agent and role: Codex, temporary feature implementer and integrator while Claude work is paused.
  • Commit/diff: TASK-006 working diff based on TASK-005 commit bbbff77; exact implementation commit recorded after creation.
  • Features implemented: Explicit branch/tag tree browsing, deterministic breadcrumbs and back navigation, directory/file selection, bounded selectable/copyable source text, and dedicated binary/LFS/unsupported-encoding/oversized/symlink/submodule fallbacks.
  • How it was implemented: A feature-owned immutable ViewModel state machine passes repository coordinates, ref, and validated path to every TASK-004 read-only request. It consumes bounded pages serially, cancels replaced work, retains file-vs-directory retry intent, and delegates byte classification to the reviewed TASK-006A component. The Compose screen keeps repository/ref/path visible and virtualizes directory rows.
  • Files changed: Code-browser ViewModel, navigation helper, Compose screen, Korean resources, focused tests, MainActivity feature wiring, selected-reference handoff, and TASK-006 evidence.
  • Checks and results: Focused code-browser tests passed; repository-wide debug build, 102 unit tests across 23 suites, and Android lint passed; diff and focused sensitive-pattern/read-only scans passed.
  • Review findings and resolutions: Self-review fixed root-loading back behavior, file retry incorrectly becoming a directory request, file breadcrumbs attempting directory loads, configuration-change screen loss, and non-virtualized directory rendering. Independent review is deferred.
  • Known limitations: Physical-device interaction has not yet been rerun. TASK-005's reviewed contract-freeze criterion remains open pending later independent review.
  • Next recommended task: TASK-009 immutable history/change/diff API contracts, followed by TASK-007 read-only history and change UI.
  • Next owner: Codex for the continuous serial stream.

2026-08-26 — TASK-004/005/006 independent review and freeze

  • Agent and role: Claude, independent reviewer per handoff/claude-master-handoff.md's "review and freeze" first action; human product owner authorized Claude as incoming primary implementer with handback to Codex kept possible at any time.
  • Commit/diff: reviewed 8a47d30 (TASK-004), bbbff77 (TASK-005), 16c46a5 (TASK-006) read-only, then fixed one finding on branch claude/review-005-006-freeze from 16c46a5.
  • Review scope and method: Independently reran assembleDebug testDebugUnitTest lintDebug --rerun-tasks (did not trust the recorded evidence alone) and read GiteaRepositoryBrowserApi.kt, HttpTransport.kt, RepositoryListViewModel.kt, RepositoryReferencePresenter.kt, CodeBrowserViewModel.kt, CodeBrowserNavigation.kt, and MainActivity.kt against the master handoff's checklist: races/cancellation, root back, rotation restoration, URL encoding, immutable snapshots, page overflow, and 404/409/422 mapping. TASK-009's commit was not yet available (still uncommitted upstream) and was excluded from this pass per human product owner direction.
  • Findings:
    • P2 (fixed): core/network/HttpTransport.kt requireSuccess() mapped 401/403/404/429/5xx but let 409 and 422 fall through to the generic ApiFailure.InvalidResponse, which would have blocked TASK-007's planned conflict/unprocessable UDF states. Added ApiFailure.Conflict/ApiFailure.Unprocessable and extended GiteaRepositoryBrowserApiTest's status-mapping test.
    • P3 (recorded, not fixed): CodeBrowserViewModel runs JSON parsing, Base64 decoding, directory sort/dedupe, and the TASK-006A classifier on viewModelScope's Main dispatcher after the network suspend point resumes. Not blocking; recommended for TASK-016 (release hardening) given its low-end-device and large-content scope.
    • Everything else checked (races/cancellation, root back via BackHandler in both screens, immutable snapshot wrapping, page-overflow bounds, URL component encoding) verified correct with no finding.
  • Checks and results after the fix: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL, 102 unit tests across 23 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean (one match was a test asserting client_secret absence, not a leak).
  • Task evidence updated: TASK-004, TASK-005, and TASK-006 each record this closure. TASK-005's "reviewed completion commit is TASK-006's contract-freeze base" and TASK-006's "evidence records the exact reviewed TASK-005 contract-freeze commit" checkboxes are both closed. TASK-009's corresponding freeze checkbox remains open pending its own commit and review.
  • Known limitations: No physical device was available, so device-interaction checks named in TASK-005/006 remain deferred, unchanged from before this review.
  • Next recommended task: TASK-007 (history and change UI), per the ordered backlog in handoff/claude-master-handoff.md, once TASK-009 lands and is reviewed; TASK-010 (work-item API contracts) ownership is transferred to Claude per the same handoff.
  • Next owner: Claude, continuing the primary implementer role with handback to Codex available at any time by flipping a task's Owner field back and picking up from its last recorded commit.

2026-08-26 — TASK-017: recent-push sort, paged repository list, last-pushed branch

  • Agent and role: Claude, feature implementer, per human product owner priority-1 request in conversation on 2026-08-26.
  • Commit/diff: branch claude/task-017-recent-push-favorites from fdda164; new task file tasks/017-repository-list-recent-push.md.
  • Features implemented: Repository list is now sorted most-recently-pushed-first (server-side sort=updated&order=desc, correct across page boundaries), paged 5 at a time with explicit previous/next navigation, and each visible repository independently shows (and can retry) its most recently committed branch.
  • How it was implemented: Added updatedAtEpochMillis to RepositorySummary and commitAtEpochMillis to BranchSummary, mapped from Gitea's updated_at and branch commit.timestamp. Replaced the legacy eager-loading RepositoryListApi/AccessibleRepositoriesGateway with the existing paged GiteaRepositoryBrowser.listRepositories. RepositoryListViewModel now tracks a page number and launches one cancellable job per visible row to fetch and compare that repository's branch timestamps; jobs are cancelled on page change/refresh/navigation-away and resumed (not restarted) when returning via back-navigation.
  • Files changed: domain/model/RepositoryModels.kt, data/repositories/GiteaRepositoryBrowserApi.kt, removed data/repositories/RepositoryListApi.kt and its test, feature/repositories/RepositoryListViewModel.kt, feature/repositories/RepositoryListScreen.kt, MainActivity.kt, strings_repositories.xml, associated tests, tasks/017-repository-list-recent-push.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 105 unit tests across 22 suites, 0 failures/errors, 0 lint errors; git diff --check clean (CRLF notices only); sensitive-pattern scan clean.
  • Review findings and resolutions: Self-review caught and fixed two issues before they shipped: (1) page-navigation/refresh calls had no try/catch, which would have crashed the app on any network failure during nextPage()/previousPage()/refreshRepositories() — wrapped in the same error-mapping pattern used elsewhere; (2) a repository with no default branch (a real Gitea state for empty repos) would have failed the entire page load via the existing fail-closed .required() mapping — defaultBranch is now nullable at the domain level, matching how the UI already handled it. Independent Codex review is pending.
  • Known limitations: No physical device was available to verify page navigation and per-row branch loading interactively. Timestamp ties between two branches resolve deterministically but arbitrarily (first match kept).
  • Next recommended task: TASK-018 (Gitea-star favorites tab), already scoped in tasks/018-favorites-gitea-star.md, is next; Codex review of TASK-017 can happen in parallel since it touches non-overlapping paths once merged.
  • Next owner: Codex for read-only review of TASK-017; Claude continuing to TASK-018.

2026-08-26 — TASK-018: Favorites tab backed by Gitea star

  • Agent and role: Claude, feature implementer, per human product owner priority-1 request in conversation on 2026-08-26 (same request as TASK-017).
  • Commit/diff: same branch as TASK-017 (claude/task-017-recent-push-favorites), committed as a separate commit on top of it; new task file tasks/018-favorites-gitea-star.md, ADR docs/decisions/004-favorites-write-user-scope.md.
  • Features implemented: A user can star a repository from the main list and unstar it from a new Favorites tab, which shows only their starred repositories with the same paging/loading/empty/error states as the main list.
  • How it was implemented: Added write:user to the app's requested OAuth scopes and replaced OAuthClientConfig's "every scope must be read-only" guard with "every scope must be read-only or in an explicit approved-write-scope allow-list" (currently exactly write:user) — the app's first non-read-only scope and first write endpoint. HttpTransport gained bearer-authenticated put/delete. GiteaRepositoryBrowserApi now also implements a new RepositoryFavorites domain interface (listStarred/star/unstar) on the same class, reusing its existing repository-mapping code. RepositoryListViewModel gained a RepositoryListMode (ALL/FAVORITES) carried on its Content/Empty states; toggling favorite either marks a row Starred (ALL mode, confirmed-not-optimistic) or removes it from the current page (FAVORITES mode, after a confirmed unstar). RepositoryListScreen gained a tab switcher and a per-row favorite button using plain localized text (no new icon dependency).
  • Files changed: core/auth/protocol/OAuthClientConfig.kt, core/network/HttpTransport.kt, data/repositories/GiteaRepositoryBrowserApi.kt, new domain/repository/RepositoryFavorites.kt, feature/repositories/RepositoryListViewModel.kt, feature/repositories/RepositoryListScreen.kt, MainActivity.kt, strings_repositories.xml, associated tests, new OAuthClientConfigTest.kt, ADR-004, tasks/018-favorites-gitea-star.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 117 unit tests across 23 suites, 0 failures/errors, 0 lint errors; git diff --check clean (CRLF notices only); sensitive-pattern scan clean.
  • Review findings and resolutions: Self-review narrowed two acceptance criteria from their original draft to match what was actually and deliberately built: favorite-toggle failures use one generic retryable state rather than per-HTTP-status messages (proportionate for a lightweight toggle), and favorite state updates only after a confirmed server response rather than optimistically. Independent Codex review is pending, and this is the first task to also request on-device verification of the new OAuth scope specifically (does the Gitea instance's OAuth app registration accept it without a server-side change).
  • Known limitations: No physical device was available — the new-scope login path is unverified. Existing signed-in users need one fresh login before Favorites works. Favorite state is never cached locally (always read fresh from Gitea).
  • Next recommended task: Codex reviews TASK-017 and TASK-018 together (same branch) and verifies the write:user login on a physical device before this reaches users; TASK-007 (history UI) remains next in the original roadmap once TASK-009 lands.
  • Next owner: Codex, for review and device verification.

2026-08-26 — TASK-010: pull-request and issue API contracts

  • Agent and role: Claude, feature implementer (ownership transferred by human product owner in handoff/claude-master-handoff.md), continuing work per the human product owner's "진행해줘" (proceed) instruction while TASK-009 remained uncommitted on Codex's side.
  • Commit/diff: branch claude/task-010-work-items-contract from 525a0aa; new domain/workitems/ and data/workitems/ packages.
  • Features implemented: Frozen, read-only domain and data contracts for pull requests, issues, reviews, and comments — listIssues/getIssue/listIssueComments, listPullRequests/getPullRequest/listPullRequestReviews/listPullRequestComments.
  • How it was implemented: IssueSummary/IssueDetail and PullRequestSummary/PullRequestDetail are fully separate models. WorkItemAuthor is Known/Unavailable so a deleted or null author never becomes an invented name. PullRequestState is derived from Gitea's merged flag combined with its open/closed state (there is no single field that already encodes "merged"). listIssues drops entries whose pull_request field is non-null so Gitea's issue-list endpoint (which also returns PRs) never duplicates what listPullRequests already exposes — done client-side rather than trusting a guessed, unverified query parameter. PR/issue discussion comments both reuse /issues/{number}/comments, matching how Gitea treats PRs as issues internally. Reused domain.model.Page<T> and core.network.ApiFailure rather than introducing parallel types.
  • Files changed: domain/workitems/WorkItemModels.kt, domain/workitems/WorkItemsRepository.kt, data/workitems/GiteaWorkItemsApi.kt, data/workitems/GiteaWorkItemsApiTest.kt, tasks/010-work-items-api-contract.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 128 unit tests across 24 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean; grep-confirmed no put/delete/postForm call anywhere in the new API class (read-only, as required).
  • Review findings and resolutions: None — self-review found no P0-P3. Independent Codex review and contract-freeze recording are still required before TASK-008 (the UI that consumes this contract) may begin.
  • Known limitations: Gitea's exact issue/PR filtering query-parameter behavior was not confirmed against authoritative live documentation; the client-side pull_request-field filter is correct regardless, at the cost of potentially shorter-than-requested pages after filtering. No physical device or live Gitea instance was used — coverage is fake-transport/unit-level only, consistent with how TASK-004/009 were verified before their own freeze review.
  • Next recommended task: Codex reviews and freezes this contract; TASK-008 (PR/issue UI) can then begin. TASK-007 remains blocked on TASK-009 independently.
  • Next owner: Codex, for review and contract-freeze recording.

2026-08-26 — TASK-009: history and diff API contracts

  • Agent and role: Claude, feature implementer, at the human product owner's explicit direction ("그냥 코덱스 말고 너가 다시 작업해줘" — implement it yourself instead of Codex) rather than continue waiting on Codex's separate, still-uncommitted attempt in the primary worktree.
  • Commit/diff: branch claude/task-009-history-diff-contract from 7d122d4; new domain/history/ and data/history/ packages. Codex's parallel uncommitted attempt at the same task was not read or referenced, to keep this an independent implementation.
  • Features implemented: Commit detail with per-file added/modified/deleted/renamed/other status, bounded commit and comparison patch/diff text with an explicit truncated flag, and ref comparison with locally-computed pagination.
  • How it was implemented: Verified Gitea's actual response shapes against its own Go source (modules/structs/repo_commit.go, compare.go field names) rather than guessing. Deliberately did not duplicate commit-history listing, since TASK-004's GiteaRepositoryBrowser.listCommits already provides it. Patch text comes from Gitea's .diff-suffixed raw-text endpoints (not the JSON commit/compare objects, which only carry file status, not diff content) and is bounded by byte limit then line limit, both constructor-injected and validated positive. The compare endpoint's single combined base...head path segment is built by concatenating both ref values before one addPathSegment call, so each ref (including any / in a branch name) is escaped exactly once — verified by a test using a ref containing a slash. Comparison pagination is computed locally against Gitea's one-shot compare response, reusing the same slicing pattern TASK-004 already uses for tree listing.
  • Files changed: domain/history/HistoryModels.kt, domain/history/HistoryRepository.kt, data/history/GiteaHistoryApi.kt, data/history/GiteaHistoryApiTest.kt, tasks/009-history-diff-api-contract.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 141 unit tests across 25 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean; grep-confirmed no put/delete/postForm call in the new API class.
  • Review findings and resolutions: None — self-review found no P0-P3. Two items flagged for reviewer attention rather than treated as settled: the exact Gitea file-status vocabulary beyond added/modified/deleted/renamed was not exhaustively confirmed, and the .diff-suffixed compare route was inferred from the documented single-commit convention rather than independently verified.
  • Known limitations: No physical device or live Gitea instance was used. Codex was independently attempting this same task in parallel (uncommitted); this entry and this implementation exist alongside that one and the two need to be reconciled by whoever reviews, not assumed to be the same.
  • Next recommended task: Codex (or whoever reviews) reconciles this implementation against its own parallel TASK-009 attempt, reviews, and records the contract-freeze commit so TASK-007 can begin.
  • Next owner: Codex, for reconciliation, review, and contract-freeze recording.

2026-08-26 — TASK-007: commit history and change UI

  • Agent and role: Claude, feature implementer, per the human product owner's direction that Claude owns all subsequent tasks rather than Codex ("이후 다른 작업들도 코덱스 말고 너가 다 작업해줘").
  • Commit/diff: branch claude/task-007-history-ui from 3c17476 (this session's own TASK-009, not yet Codex-reviewed); new feature/history/ package plus MainActivity.kt and CodeBrowserScreen.kt wiring.
  • Features implemented: Paginated commit history for the currently browsed ref; commit detail with distinguished added/modified/deleted/renamed file status; bounded, explicitly-truncated patch text for a single commit or a ref comparison; a comparison flow that validates one entered ref against the currently open ref as base.
  • How it was implemented: Reuses TASK-004's listCommits for history listing and this session's own TASK-009 HistoryRepository for detail/patch/compare — no shared/domain file touched. A commit's file list and its patch load as two independently-retryable steps rather than one blocking fetch. Entry point is a new "히스토리" button added to the existing code browser header, wired through a third browsingHistory screen flag in MainActivity layered above the existing browsingCode flag.
  • Files changed: feature/history/HistoryViewModel.kt, feature/history/HistoryScreen.kt, strings_history.xml, MainActivity.kt, feature/codebrowser/CodeBrowserScreen.kt, strings_codebrowser.xml, tasks/007-history-and-changes.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 150 unit tests across 26 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean.
  • Review findings and resolutions: Self-review caught and fixed one real bug before writing tests: a failed commit-detail fetch discarded which commit had failed, so "retry" silently fell back to reloading the whole list instead of re-fetching the commit the user actually tried to open. The Error state now retains the failed CommitSummary so retry targets the same commit.
  • Known limitations: TASK-009 has not been independently reviewed or contract-frozen — this task consumes it directly, with no cross-review from a second implementer, per explicit human direction. No physical device was available to verify the three-level back stack (repositories → code browser → history) interactively. Comparison always uses the open ref as base (no arbitrary base-ref picker); comparison patch is fetched once for the whole range, not per commit.
  • Next recommended task: TASK-010 is already done (this session); TASK-008 (PR/issue UI) is next and consumes it the same way TASK-007 consumed TASK-009 — directly, without waiting for Codex review, per the same standing direction.
  • Next owner: Claude, continuing to TASK-008.

2026-08-26 — TASK-008: pull-request and issue UI

  • Agent and role: Claude, feature implementer, continuing the standing direction that Claude owns all subsequent tasks without stopping to ask between them.
  • Commit/diff: branch claude/task-008-workitems-ui from 429668b; new feature/workitems/ package plus MainActivity.kt and RepositoryListScreen.kt wiring.
  • Features implemented: Separate issue and PR list/detail views (tabs), paginated, with comments (issues) and reviews+comments (PRs); deleted/unavailable authors render a fixed placeholder instead of crashing or inventing identity; every status is a text label, never color-only.
  • How it was implemented: Reuses TASK-010's domain contracts directly — no shared/domain file touched. Both list types apply distinctBy(number) per page and use the same discrete prev/next paging convention as TASK-017/007, which structurally prevents duplicate rows without separate merge logic. Entry point is a new button on the repository references screen (issues/PRs are repository-scoped, not ref-scoped, unlike history).
  • Files changed: feature/workitems/WorkItemsViewModel.kt, feature/workitems/WorkItemsScreen.kt, strings_workitems.xml, MainActivity.kt, feature/repositories/RepositoryListScreen.kt, strings_repositories.xml, tasks/008-work-items-ui.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 158 unit tests across 27 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean.
  • Review findings and resolutions: None — self-review found no P0-P3.
  • Known limitations: TASK-010 has not been independently reviewed or contract-frozen. No physical device or instrumented Compose test verified accessibility semantics interactively (consistent with every other feature this session). Pagination dedupe is verified within a single page's response, not across an accumulating infinite scroll.
  • Next recommended task: This closes the originally-scoped TASK-007/008 pair. Remaining roadmap items (TASK-013 navigation, TASK-014 organizations, TASK-015 notifications, TASK-016 release hardening) have no task files yet.
  • Next owner: Claude.

2026-08-26 — TASK-013: typed application navigation

  • Agent and role: Claude, feature implementer, continuing the standing direction to own all subsequent tasks without asking between them. No task file existed for TASK-013 beforehand (only proposed in an earlier handoff document); created it first per AGENTS.md.
  • Commit/diff: branch claude/task-013-navigation from c397fbe; new ui/navigation/ package, rewrote MainActivity.kt.
  • Features implemented: Replaced MainActivity's three independent booleans with one typed List<Route> back stack, restorable through configuration change and process death.
  • How it was implemented: Route is a sealed type of stable primitives only (owner/name/ref/ref-kind — never tokens or content). RouteStackSaver encodes each route as a delimiter-joined string using U+001F, a character RepositoryCoordinates/RepositoryRef already guarantee can never appear in real field values. A single LaunchedEffect(currentRoute) re-triggers the owning feature ViewModel's load whenever the top route changes, covering both fresh navigation and restoration identically. This is a genuine behavior improvement, not just a refactor: the stack is a real LIFO, so back correctly unwinds through arbitrarily nested screens instead of relying on which boolean happened to still be true underneath another.
  • Files changed: ui/navigation/Route.kt, ui/navigation/RouteStackSaver.kt, RouteEncodingTest.kt, MainActivity.kt, tasks/013-typed-navigation.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 164 unit tests across 28 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean.
  • Review findings and resolutions: None functional. One implementation detail worth recording: Compose's listSaver actually returns Saver<List<T>, Any>, not Saver<List<T>, List<Saveable>> as its type parameters suggest — found via the compiler's own type-mismatch error rather than assumed, and RouteStackSaver's declared type corrected to match.
  • Known limitations: Only the route itself survives process death, not deeper in-feature state (open commit, scroll position) — explicitly out of scope. No physical device was available to verify process-death restoration interactively; the Saver round-trip is unit-tested directly instead.
  • Next recommended task: TASK-014 (organizations) and TASK-015 (notifications) are real MVP scope items from docs/product.md with no task files yet; TASK-016 (release hardening) depends on those and needs a physical device. Continuing per standing direction.
  • Next owner: Claude.

2026-08-26 — TASK-014: organizations and organization repository filter

  • Agent and role: Claude, feature implementer, continuing the standing direction to own all subsequent tasks. No task file existed beforehand; created it first per AGENTS.md. Closes MVP outcome #2 in docs/product.md ("View the signed-in user and organizations").
  • Commit/diff: branch claude/task-014-organizations from 7756ec4; new domain/organizations/ package, additive changes to GiteaRepositoryBrowserApi/RepositoryListViewModel/RepositoryListScreen.
  • Features implemented: The repository list can be filtered to one organization (a horizontally-scrollable chip row: 전체 + one per org) and back, keeping the existing recent-push sort and paging.
  • How it was implemented: Verified Gitea's actual Organization struct (name/full_name) before implementing. GiteaRepositoryBrowserApi gained a third interface (OrganizationsRepository), reusing its existing mapRepository for org-scoped repos rather than duplicating mapping. The organizations list itself is fetched once per session and cached, since it changes rarely and every page load re-fetching it would be wasteful; a failure to load it degrades to an empty filter row rather than blocking the repository list, which is the primary content.
  • Files changed: domain/organizations/OrganizationModels.kt, domain/organizations/OrganizationsRepository.kt, GiteaRepositoryBrowserApi.kt, GiteaRepositoryBrowserApiTest.kt, RepositoryListViewModel.kt, RepositoryListViewModelTest.kt, RepositoryListUiStateTest.kt, RepositoryListScreen.kt, strings_repositories.xml, MainActivity.kt, tasks/014-organizations.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 169 unit tests across 28 suites, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean.
  • Review findings and resolutions: None — self-review found no P0-P3.
  • Known limitations: No physical device or real multi-organization account was available to verify interactively. Organization membership is read once per session, not refreshed mid-session (matches the app's existing no-background-refresh posture).
  • Next recommended task: TASK-015 (notifications) is the last remaining MVP-scope item without a task file; TASK-016 (release hardening) depends on it and needs a physical device.
  • Next owner: Claude.

2026-08-26 — TASK-015: read-only notifications

  • Agent and role: Claude, feature implementer, continuing the standing direction to own all subsequent tasks without stopping to ask between them ("따로 말하지말고 알아서 계속 다음 작업 진행해줘"). No task file existed beforehand; created it first per AGENTS.md. Closes the last remaining MVP-scope item from docs/product.md.
  • Commit/diff: branch claude/task-015-notifications from 4fe3bf1; new domain/notifications/, data/notifications/, feature/notifications/ packages, additive changes to Route.kt/RouteStackSaver.kt/MainActivity.kt/RepositoryListScreen.kt.
  • Features implemented: A signed-in user can view their Gitea notifications (unread-only, the endpoint's own default, or all), paginated, and tap one whose subject is a navigable issue/PR to jump to that repository's work items. No mark-read/unread/pinned write endpoint is reachable from any new code — deliberately excluded, same posture as the star/unstar write scope decision (ADR-004).
  • How it was implemented: Verified Gitea's actual NotificationThread/NotificationSubject struct shape via its own Go source before implementing. Subject type/state fall back to OTHER/UNKNOWN on an unrecognized value instead of failing closed. The subject's issue/PR number has no dedicated field in the API — it is parsed from the trailing path segment of the subject URL, yielding null (not a guess) when unparseable, which is exactly the case for commit/repository-type subjects. Unread state renders as a [읽지 않음] text label, never color alone. Self-review caught the same class of bug as TASK-007's history-retry issue: the first draft's Error state didn't retain which filter was active, so retrying after a failure while viewing "전체" would have silently reset to "읽지 않음" — fixed by adding unreadOnly to Error, with a regression test.
  • Files changed: domain/notifications/NotificationModels.kt, domain/notifications/NotificationsRepository.kt, data/notifications/GiteaNotificationsApi.kt, data/notifications/GiteaNotificationsApiTest.kt, feature/notifications/NotificationsViewModel.kt, feature/notifications/NotificationsScreen.kt, feature/notifications/NotificationsViewModelTest.kt, strings_notifications.xml, ui/navigation/Route.kt, ui/navigation/RouteStackSaver.kt, MainActivity.kt, feature/repositories/RepositoryListScreen.kt, tasks/015-notifications.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 188 unit tests total, 0 failures/errors, 0 lint errors; git diff --check clean; sensitive-pattern scan clean.
  • Review findings and resolutions: None beyond the retry-filter bug caught and fixed during self-review (see above). One item flagged for reviewer attention rather than treated as settled: applying status-types=unread&status-types=read to the notification-list endpoint to request "전체" was not independently verified against a live Gitea instance — Gitea's public docs describe that parameter only on the per-repo mark-read endpoint; if wrong, the fallback is two separate calls merged client-side.
  • Known limitations: Tapping an issue/PR notification opens that repository's work-item list, not the specific item's detail — Route.WorkItems (TASK-013) carries no item number, and adding one was out of this task's scope. No physical device was available to verify interactively.
  • Next recommended task: TASK-016 (release hardening) is the last item on the proposed roadmap; it depends on TASK-014/015 (both now done) and was previously noted as needing a physical device, which remains unavailable in this environment — will surface that as a genuine blocker rather than skip it silently.
  • Next owner: Claude.

2026-08-26 — TASK-016: release hardening (partial — device-dependent items explicitly blocked)

  • Agent and role: Claude, feature implementer, continuing the standing direction to own all subsequent tasks. No task file existed beforehand; created it first per AGENTS.md.
  • Commit/diff: continued on claude/task-015-notifications (no separate branch cut for this small follow-on); changes to CodeBrowserViewModel.kt/CodeBrowserViewModelTest.kt, new tasks/016-release-hardening.md.
  • Features implemented: Fixed the P3 dispatcher issue recorded during the TASK-004/005/006 review — CodeBrowserViewModel now runs directory listing/dedupe/sort and file fetch+classification on an injectable defaultDispatcher (Dispatchers.Default in production) instead of viewModelScope's Main dispatcher.
  • How it was implemented: An AVD (WhatToEat_API_36) turned out to be available in this environment — confirmed with the human product owner before using it, after they clarified an emulator should exist (Codex had used one on a different project). Booted it and ran a pre-authentication smoke test: cold install, launch, sign-in screen renders correctly (localized Korean text), tapping "Gitea로 로그인" correctly hands off to the system browser toward the OAuth flow, no crash. Attempting to click through Chrome's first-run screen to test further hit a hard constraint, not a tooling gap: completing Gitea login requires entering a password, which is a universally prohibited action in this agent's safety rules regardless of whose credentials are offered — one blind tap nearly walked into Chrome's "add a Google account" flow, which was backed out of immediately (am force-stop) without proceeding. Confirmed with the human product owner (2026-08-26) how to proceed: do everything verifiable without login, leave the rest explicitly blocked rather than attempted or silently skipped. Also ran a full-repository secret scan (not scoped to one diff), reviewed the manifest/backup-rules/token-vault security posture (all already correct — allowBackup="false", data_extraction_rules.xml excludes all sharedpref data, AES/GCM via Android Keystore, no Log.*/println anywhere in app/src/main), and confirmed assembleRelease itself builds. Deliberately did not enable R8/ProGuard minification: every data/*Api.kt class uses Gson with TypeToken<List<T>> generic reflection, which R8 can silently break without correct keep rules, and the only way to catch that here is exercising the authenticated, JSON-parsing-heavy screens on a minified build — exactly what login being unreachable prevents verifying.
  • Files changed: feature/codebrowser/CodeBrowserViewModel.kt, feature/codebrowser/CodeBrowserViewModelTest.kt, tasks/016-release-hardening.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL — 182 unit tests across 30 suites, 0 failures/errors, 0 lint errors; assembleRelease BUILD SUCCESSFUL (lintVitalRelease passed); full-repository secret-pattern scan clean (only the pre-existing test asserting client_secret absence); git diff --check clean.
  • Review findings and resolutions: None beyond the P3 fix itself, which was already a recorded, pre-existing finding from an earlier review, not discovered fresh in this pass.
  • Known limitations (both recorded as explicitly blocked acceptance criteria, not silently skipped): (1) R8/ProGuard minification was not enabled — left as an open decision for whoever has real device + login access to verify safely. (2) On-device regression of every authenticated screen built this session (repository list/favorites/organizations, code browser, history, work items, notifications, navigation back-stack, logout) could not be performed — structurally blocked on this agent never entering a password, confirmed with the human product owner rather than assumed.
  • Next recommended task: This closes the originally-proposed roadmap (TASK-007 through TASK-016). Remaining work is exactly the two blocked items above, both requiring a human (or an agent with real login access) at a real device: complete the authenticated-screen regression pass, then make an informed call on enabling minification with real verification against a build that has actually exercised the JSON-heavy screens.
  • Next owner: Human product owner or Codex, for the device-and-login-dependent items above; otherwise no further roadmap items remain unassigned.

2026-08-26 — TASK-019 OAuth callback crash recovery

  • Agent and role: Codex, incident resolver; Claude diagnostic changes preserved.
  • Commit/diff: working diff on claude/task-015-notifications pending commit.
  • Features implemented: Restored the production OAuth request to its previously device-verified read-only scopes; added safe callback exception recovery, non-secret debug reason tags, and defensive saved-route decoding.
  • How it was implemented: write:user is no longer part of UBGiteaOAuthDefaults; callback storage/validation failures are caught at the ViewModel coroutine boundary and become INVALID_CALLBACK; malformed or truncated route records are discarded and fall back to Repositories.
  • Checks and results: Focused OAuth/repository/navigation tests passed; debug build, 184 unit tests across 30 suites, and lint passed; diff and focused secret checks passed.
  • Review findings and resolutions: P1 target-server authorization incompatibility from TASK-018 scope expansion was resolved by suspending write scope. P1 repeat-launch crash risk from unchecked saved-route indexes was fixed with shape validation. Callback exception regression coverage was added.
  • Known limitations: Physical-device callback verification is still required. Gitea star/unstar mutation is intentionally unavailable with the restored read-only token.
  • Next recommended task: Install the rebuilt debug APK, clear UBGitea app data once, complete login approval, and verify repository loading.
  • Next owner: Human product owner for device verification, then Claude for review.

2026-08-26 — TASK-020 repository-list crash fix

  • Agent and role: Codex, incident resolver.
  • Root cause: The repository LazyColumn used RepositoryCoordinates as its item key. Compose passes lazy-item keys to Android saved state, but this domain object is not a Bundle-saveable type, causing an uncaught main-thread IllegalArgumentException as soon as authenticated rows rendered.
  • Implementation: Repository rows now use the stable string key owner/name; repository components cannot contain /, so the key remains unique across owners and repositories while being Android-saveable.
  • Regression coverage: Added tests for deterministic key encoding and for equal repository names under different owners.
  • Checks: Focused tests and assembleDebug testDebugUnitTest lintDebug passed. The APK installed wirelessly over the authenticated device, MainActivity remained resumed, and the cleared crash buffer stayed empty. Final visual inspection is pending because the handset locked.

2026-08-26 — TASK-021 global recent-update repository order

  • Agent and role: Codex, implementer and device verifier.
  • Root cause: This Gitea server returned /user/repos in apparent name order despite the existing sort=updated&order=desc query, so trusting server paging could not guarantee that the newest repository appeared on page 1.
  • Implementation: The ViewModel now loads all server pages in 50-row batches, performs one stable descending sort by RepositorySummary.updatedAtEpochMillis, and slices that immutable snapshot into five-row UI pages. Snapshots are scoped by all/favorites/organization selection, reused for next/previous navigation, and invalidated by refresh and logout.
  • Checks: Focused regression tests and assembleDebug testDebugUnitTest lintDebug passed. The rebuilt APK was installed wirelessly; the authenticated first page visibly changed from alphabetical order to recent-update order, all five rows loaded their latest branch labels, and the crash buffer remained empty.

2026-08-26 — TASK-022: tolerate incomplete Gitea branch timestamps

  • Agent and role: Claude, picked up Codex's paused in-progress work in the same worktree (task-015-notifications) after the human product owner explicitly confirmed it was safe to continue rather than assuming so.
  • Commit/diff: continued on claude/task-015-notifications; domain/model/RepositoryModels.kt, data/repositories/GiteaRepositoryBrowserApi.kt, feature/repositories/RepositoryListViewModel.kt and three test files.
  • Features implemented: A repository with a branch whose commit timestamp is missing or malformed no longer fails the whole branch page (row-level latest-branch fetch or the repository reference screen). Root cause (per Codex's incident evidence, repo CSOT-T8-iPTN-AMA): listBranches's mapper required every commit.timestamp even though a timestamp isn't needed to identify or browse a branch.
  • How it was implemented: Codex had already landed the core fix (BranchSummary.commitAtEpochMillis → nullable, mapper tolerates a missing/malformed value instead of throwing, latest-branch picker filters to non-null timestamps before taking the max) plus two of the needed tests, uncommitted, when this was picked up. Verified RepositoryReferencePresenter needed no change (it never reads the timestamp field) and added an explicit test proving that rather than leaving it implicit. Found and filled one real gap in the in-progress test coverage: a repository whose branches are all missing timestamps wasn't tested — confirmed the existing code already handles it correctly (reports LastPushedBranchUiState.Failed, distinct from Empty, rather than silently showing nothing) and added the missing test.
  • Files changed: domain/model/RepositoryModels.kt, data/repositories/GiteaRepositoryBrowserApi.kt, feature/repositories/RepositoryListViewModel.kt, data/repositories/GiteaRepositoryBrowserApiTest.kt, feature/repositories/RepositoryReferencePresenterTest.kt, feature/repositories/RepositoryListViewModelTest.kt, tasks/022-tolerate-branch-timestamp-gaps.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL, all unit tests passing, 0 lint errors; git diff --check clean; secret-pattern scan of the staged diff clean.
  • Review findings and resolutions: One gap found and fixed in Codex's in-progress test coverage (the all-unknown-timestamps case) — see above.
  • Known limitations: Not retested live against CSOT-T8-iPTN-AMA (or any repository with a real missing/malformed branch timestamp) on a physical device with this exact fix — Codex reproduced and diagnosed the original crash on-device, but this fix itself has only unit-test coverage so far.
  • Next recommended task: A physical-device retest of this fix (Codex has working wireless-adb device access per TASK-020/021's evidence). No other roadmap items are open.
  • Next owner: Codex or the human product owner, for the device retest; otherwise no further items are unassigned.

2026-08-26 — TASK-023: tolerate a literal JSON null list response

  • Agent and role: Claude, live incident diagnosis and fix, with the human product owner's own device as the reporting/verification device (their first real end-to-end use of the app this session).
  • Commit/diff: continued on claude/task-015-notifications; data/repositories/GiteaRepositoryBrowserApi.kt, data/repositories/GiteaRepositoryBrowserApiTest.kt.
  • Features implemented: A parseList-backed endpoint (branches, tags, commits, repository listing) whose response body is the literal JSON null — Go's encoding/json serializes a nil slice this way instead of [] — now maps to an empty list instead of throwing ApiFailure.InvalidResponse(). Previously this showed a misleading "Gitea 서버에서 오류가 발생했습니다" (server error) message on a completely successful 200 OK response.
  • How it was diagnosed: The human product owner set up wireless adb debugging to their own physical device this session (paired via adb pair/adb connect); this was used to install successive debug builds directly and read logcat live rather than round-tripping screenshots. First confirmed via the Gitea server's own Container Manager request log that the failing branches call was a genuine 200 OK in ~7ms, ruling out an actual server-side 5xx despite the app's own error message claiming one. Then added three rounds of temporary BuildConfig.DEBUG-gated Log.e diagnostics (the caught exception, per-item mapping detail, then the raw response body) to narrow the cause down to bodyPreview=null — a literal null string, not empty or malformed JSON. All diagnostic logging was removed before committing; only the real fix and a matching test remain.
  • Files changed: data/repositories/GiteaRepositoryBrowserApi.kt, data/repositories/GiteaRepositoryBrowserApiTest.kt, tasks/023-tolerate-null-list-responses.md.
  • Checks and results: assembleDebug testDebugUnitTest lintDebug --rerun-tasks BUILD SUCCESSFUL, all unit tests passing, 0 lint errors; git diff --check clean; secret-pattern scan clean. Verified live: reinstalled over wireless adb and the human product owner confirmed CSOT-T8-iPTN-AMA now opens correctly.
  • Review findings and resolutions: The fix was made at the shared parseList helper level, not narrowly in listBranches, since the same Go nil-slice-to-null serialization can equally affect tags, commits, or repository listing — narrowing it to only the reported endpoint would have left the same latent bug in every sibling endpoint.
  • Known limitations: CSOT-T8-MACRO_SA (the other repository that showed the same symptom) was not individually re-confirmed by the human product owner, though it goes through the identical code path.
  • Next recommended task: None outstanding on the proposed roadmap. TASK-016's two previously-blocked items (full authenticated-screen device regression, R8/ProGuard minification decision) are now more tractable, since a real device session with working wireless adb and the human product owner's active participation is established for the first time this session.
  • Next owner: Claude, continuing per the standing direction, or the human product owner if they'd rather drive further device testing directly.