Repository navigation
feat: projects list - #17
Conversation
The second M1 slice: the signed-in account's projects, the entry point to the rest of the milestone. - Project model (freezed): id, name, path_with_namespace, description, stars, visibility, last activity; unknown fields ignored, omitted fields nullable - projects endpoint: GET /projects?membership=true ordered by last activity, page cursor read from X-Next-Page, never an assumed count - ProjectsRepository + Riverpod controller, plus an authed GitLabClient provider that rebuilds per account so a request can't run as a previous account - ProjectTile in the design system, taking plain values not a model - Projects screen with loading / error / empty / data states and pull-to-refresh - Home gains a My Work entry that opens the list - Localized into all five locales TDD caught three things before any manual run: - The endpoint typed its GET as List, so an error body (GitLab returns an object) failed the cast before the status could be read, turning a 401 into a spurious connection error. Fixed by checking status before casting. Test covers the 401. - The tile was passed only the namespace, dropping the project name from the path shown under the title. The identifier is the full path_with_namespace; a widget test pins it. - A never-completing Completer, not Future.delayed, is what holds a controller in AsyncLoading under the test clock without a pending-timer failure. Closes #16 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: sjungwon03 <sjungwon03@gmail.com>
sjungwon03-ai
left a comment
There was a problem hiding this comment.
Clean second M1 slice. All three bugs are genuinely covered test-first: the 401 fix reads status before the List cast (get), verified by a 401→GitLabAuthException test with an object error body; the tile now carries pathWithNamespace, pinned by both a tile test and a screen test asserting 'youthpick/backend'; and the loading-state test uses a never-completing Completer to hold AsyncLoading without a pending timer. The authed client provider rebuilds per account and closes the old client onDispose, so an in-flight request can't authenticate as a previous account. Pagination reads cursors from headers with nullable totals, empty and error are distinct tested states, and ProjectTile stays model-free. CI is green at head (format, analyze, 60 tests, codegen). LGTM.
Summary
The second M1 slice: the signed-in account's projects, reachable from home.
Projectmodel (freezed)GET /projects?membership=trueordered by last activity, page cursor fromX-Next-PageProjectsRepository+ Riverpod controller, and an authedGitLabClientprovider that rebuildsper account
ProjectTilein the design systemCloses #16
Three bugs caught test-first
GETasList, so an errorbody (GitLab returns an object) failed the cast before the status could be read. Now the status
is checked before the cast. Test covers the 401 →
GitLabAuthException.project.namespace(
youthpick) rather than the fullpath_with_namespace(youthpick/backend), which is theidentifier. A widget test pins the full path.
Future.delayedschedules a timer the fake clock flagsas pending and the state resolved to error; a never-completing
CompleterholdsAsyncLoadingcleanly. (Test-infra, but worth recording.)
Validation
dart format --set-exit-if-changed .— cleanflutter analyze --fatal-infos—No issues found!flutter test— 60 passing across five packagesChecklist
devCloses #Notes for reviewers
gitLabClientProvider) rebuilds when the account changes and closesthe old client on dispose, so an in-flight request can't end up authenticated as a previous
account.
slices. Caching (Drift) lands with the offline work; the repository is a thin pass-through for now.