feat(lists): GitHub-backed lists — repo picker, repo link, private tag, refresh, locked schema (#50) - #113
Merged
Conversation
…ocked schema
A list can now be created from a GitHub repository's issues, and the detail
screen operates one honestly.
Create: the "New list" sheet gains a Local / GitHub-backed choice. The GitHub
half picks a repository (optionally scoped to an organisation via
`GET /api/github/orgs` → `GET /api/github/repos?org=`) and sends
`source: "github"` with `githubRepo` and `githubSource: "issues"`. The
`owner/repo` form is validated client-side because the server refuses anything
else with `400 bad_request` before it ever reaches GitHub.
Repo link: a GitHub-backed list links `owner/repo issues` under its title. When
the repository is private on GitHub the link carries a **Private repo** tag whose
copy says, explicitly, that it describes the *repository* and not the list, and
warns that an invited collaborator may still meet a GitHub sign-in or "not found"
page. An unrecorded visibility (`githubRepoPrivate: null`) shows no tag rather
than being presented as public. Visibility is re-read from each list payload, so
a sync picks up a repository that changed.
Refresh: `POST /api/lists/{id}/refresh` is surfaced as "Refresh from GitHub", and
only on a GitHub-backed list — the endpoint 400s on a local one.
Rows are issues: the schema mapper now reads `isReadOnly` (and the ListProperty
`propertyKey`/`propertyName`/`validationRules` spellings the synthetic GitHub
schema uses), so `Issue #`, `Link`, `Created` and `Updated` are shown but never
sent. The row form names the operation a save performs, using
`GET /api/github/repos/{owner}/{repo}/next-issue-number` to say which issue is
about to be opened, and delete is labelled as closing the issue.
Schema editor: locked to parent-only for these lists. The fixed columns render
read-only with an explanation, add/remove/retype and save are refused in the
ViewModel as well as hidden, and a parent-list picker (`PUT /api/lists/{id}`
with `parentId`) is the one edit that remains — silently allowing a schema edit
would only produce a confusing server error.
Unlinked GitHub is handled as a first-class state: an unlinked account (400
"GitHub account not linked") and a refused token (401 `github_error`) each get
their own explanation and a route to the existing connected-accounts screen,
rather than an empty picker. OAuth linking itself (#39) is not attempted.
`:feature:lists` calls `/api/github/…` through its own Retrofit interface and
DTOs rather than depending on `:feature:integrations`, since no feature module in
this repo depends on another.
Extends #54's `CreateListRequest` rather than duplicating it: `githubRepo` and
`githubSource` join the existing `parentId`/`folderId`/`messageId`/`initialRows`/
`metadata`/`source` options, all still omitted when absent.
Tests: create sends `githubRepo`/`githubSource` (and a local create still sends
neither); the schema editor is locked for a GitHub-backed list and unlocked for a
local one; refresh updates rows and is never spent on a local list; the Private
repo tag renders from `githubRepoPrivate` and its copy is pinned to name the
repository, not the list.
Closes #50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #50. Part of epic #49.
What changed
Repo picker on creation. The Lists FAB now opens a "New list" sheet with a Local / GitHub-backed
choice. The GitHub half picks a repository, optionally scoped by organisation, and sends
source: "github"+githubRepo+githubSource: "issues".Which module owns the GitHub calls, and why.
:feature:listscalls/api/github/…through itsown Retrofit interface and DTOs.
:feature:integrations'sIntegrationsApi/GitHubDto/GitHubMapperare module-internal, and the repo convention is that no feature module depends onanother — a cross-module dependency would also have collided with the in-flight #40 work. The
duplication is ~100 lines of lenient DTOs, documented as deliberate in
GithubDtos.kt.Repository link + Private repo tag.
GithubRepoLinkRowrendersowner/repo issuesunder thetitle, opening the repo's issues page. The tag copy is pinned by test and gets the distinction the
help text insists on: "This repository is private on GitHub… That is separate from who can see this
list", plus the sign-in / "not found" warning — and it never says "private list".
githubRepoPrivate: null(never synced) shows no tag — unknown is not rendered as public.Refresh from GitHub appears in the top bar only for a GitHub-backed list, and the ViewModel also
refuses to spend the request on a local one (the endpoint 400s).
Row edits → issue operations. The row writes already used the correct
{ data }/ no-If-Matchshape. What was missing was
isReadOnly:SchemaMappernow parses it (plus thepropertyKey/propertyName/propertyType/validationRulesspellings the synthetic GitHub schemauses), so
Issue #,Link,CreatedandUpdateddisplay but are never sent. The row formnames the operation ("Saving opens issue #42 in owner/repo") and delete reads "Close issue on
GitHub".
Schema editor locked to parent-only. Columns render read-only with an explanation; add / remove /
retype / save are refused in the ViewModel as well as hidden. A parent-list picker
(
PUT /api/lists/{id}withparentId) is the one remaining edit.GET /api/github/orgsdrives the picker's organisation filter chips (?org=), andnext-issue-numberdrives the add-row hint. Both fit; neither was forced.Unlinked case is not a broken picker:
GithubLinkProblemdistinguishes not linked from tokenrefused / missing Issues scope, each with its own explanation and a route to the connected-accounts
screen. An empty-but-successful repo list is explained separately as an org OAuth-approval problem.
#39 (OAuth linking) was not attempted — it remains blocked.
Live-API facts established while working
POST /api/listswithsource: "github"and no repo →400 {"error":"githubRepo is required for GitHub-backed lists (format: owner/repo)","code":"bad_request"},and the same for a repo with no slash. Client-side validation now matches the server exactly.
GET /api/github/reposand/api/github/orgsreturn bare arrays;next-issue-numberreturns{"nextNumber": n}.githubSourceis not validated before the GitHub call, so its accepted values could not beconfirmed.
"issues"is used because that is the only mapping documented — an informedassumption, not a verified one.
GET /api/github/reposand/api/github/orgsboth return200 [], so anend-to-end create could not be completed here.
Verification
./gradlew clean :app:assembleDebug testDebugUnitTest→ BUILD SUCCESSFUL, 1079 tests, 0failures. New:
GithubRepoLinkTest(copy),DefaultListsRepositoryGithubTest(create payload,refresh route, synthetic schema, re-parent),
DefaultGithubRepositoryTest(repos/orgs/next-issue-number, unlinked/refused mapping),
NewListViewModelTest, plus additions toSchemaEditorViewModelTestandListDetailViewModelTest. Compose tests compile, not executed (noemulator) — which is why the tag's copy is also asserted by a JVM test that does run.
ListsDatabasev2 → v3, destructive migration already configured.Risks / follow-ups
explicitNulls = falsedrops anullfromthe body. The module has the same pre-existing limitation for
folderId. Clearing needs anexplicit-
JsonNullrequest shape.ListsViewModel.createListwas removed as dead codeonce
NewListViewModeltook over creation for both kinds. The FAB no longer creates an untitled"New list" instantly — it opens the sheet.