Lists: create a GitHub-backed list from a repository (#73) - #175
Merged
Merged
Conversation
Adds the second tab of the new-list flow from /help/lists — Local List /
GitHub-backed List — as a self-contained control, plus the service call and
the browser badge behind it.
- `NewListTabs` (view + `NewListTabsViewModel`) owns the tab choice and the
whole GitHub tab: repo picker, title defaulting to the repository name,
optional parent list, Public list toggle. Hosted in `ListsView.xaml` with
one element and three Visibility attributes — the host's existing
title/description form *is* the Local tab, so nothing was rebuilt or moved.
- `GitHubRepoPickerViewModel` loads "My repositories" (bare `/api/github/repos`)
or an organization's (`?org=`), filters locally and reports honestly when the
server truncates.
- `GitHubLinkViewModel` reads link state from `/api/user/identities` and owns
the two browser handoffs — "Reconnect for GitHub Issues" (`?link=true`) and
"Manage organization access" (`manageOrgAccessUrl`), which are different
remedies.
- `GitHubListBadge` marks GitHub-backed lists in the browser, answering from a
one-request `GitHubListIndex` rather than a fetch per row.
- `InterlinedApiClient.GitHubLists.cs` adds the GitHub-backed create and the
backing reads; `GitHubListBacking` projects `source`/`githubRepo`/
`githubRepoPrivate` out of the raw list JSON so this doesn't collide with
the open PR extending `ListSummary`.
Corrects the epic on two points, both probed live: `/api/github/repos` needs no
client paging (the server returns the complete set in one bare array) but does
need an `?org=` to show anything but your own; and exactly 2000 rows is a server
truncation, now confirmed on six large organizations, so the picker says "first
2,000" instead of implying completeness.
The GitHub-backed create was deliberately never executed — it would wire a real
GitHub repository to a real list. Its contract was verified without creating
anything: `POST /api/lists { title, source: "github" }` with no `githubRepo`
answers 400 "githubRepo is required for GitHub-backed lists (format:
owner/repo)" and creates nothing.
Closes #73
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 16, 2026
…onverters Two changes. 1. ListsView.xaml conflict: #73 puts a local-tab Visibility on the "Create list" button; #18 wraps that button in a StackPanel alongside "Define columns". Resolved by keeping the StackPanel and moving the visibility binding ONTO it, so both buttons hide on the GitHub-backed tab — which is correct, since that tab has its own create flow and "Define columns" is meaningless for a GitHub-backed list whose columns come from the repo's issue fields. Leaving the binding on just the one button would have left "Define columns" visible on the wrong tab. 2. Consolidated the shared visibility converters into CommonConverters.cs. This merge produced CS0101/CS0111 for BOTH InverseBoolToVisibilityConverter (GitHubViewConverters.cs vs ListsViewConverters.cs) and NotNullToVisibilityConverter (GitHubViewConverters.cs vs AiConverters.cs) — the fifth and sixth instances of the same failure in this merge pass. All implementations were byte-identical. Rather than delete one copy again, both now live in CommonConverters.cs, which carries a comment explaining why: parallel PRs adding the same converter to different files always merge cleanly and always break the compiler, and these are namespace-visible without a using directive, so a per-view copy is never necessary. Note: the first attempt used a regex spanning the preceding comment block and swallowed a neighbouring class (NullOrEmptyToVisibilityConverter), breaking eight XAML files. Redone by anchoring on the declaration line and balancing braces. Every converter is now declared exactly once — verified by counting. Debug and Release both build clean; Sync.Core.Tests 38/38. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Stack
main→ #142 (issue-72-github-service, the nine/api/github/*endpoints) → this PR → #74 (repo link / private-repo tag / refresh) → #76 (reconnect handoff). Base isissue-72-github-service, notmain.What this adds
The GitHub-backed List tab of the new-list flow from
/help/lists, end to end.Views/NewListTabs.xaml(.cs),ViewModels/NewListTabsViewModel.csViewModels/GitHubRepoPickerViewModel.csViewModels/GitHubLinkViewModel.csViews/GitHubListBadge.xaml(.cs),Views/GitHubMark.xaml(.cs)Services/GitHubListIndex.csServices/InterlinedApiClient.GitHubLists.cssource/githubRepo/githubRepoPrivateModels/GitHubListBacking.csPOST /api/lists(github) envelopeModels/GitHubBackedListCreated.csFlow: pick a repository → the title defaults to the repo name (and is not clobbered afterwards if you type over it) → optional parent list → Public list toggle → create.
Hosting cost in
ListsView.xaml: 5 linesThat file is contended by #161/#163/#164, so this follows the #55/#56 pattern — a self-contained
UserControlwith its own view model, context in via dependency properties:plus
Visibility="{Binding IsLocalTabSelected, ElementName=NewListTabsPanel, …}"on the three existing local-form controls (the host's title/description/Create form is the Local tab — nothing was rebuilt, moved or duplicated), and one<local:GitHubListBadge ListId="{Binding Id}"/>in the browser's item template.ListsViewModelis untouched; so isListsView.xaml.cs; so isModels/ListSummary.cs.Two corrections to the epic, both from live probes
1. There is no client paging to do — but an
?org=is mandatory. The epic asked for a "paginated" picker.GET /api/github/reposreturns the complete set in one bare array (559 for?org=github, 278 for?org=dotnet— well past GitHub's 100/page ceiling): no envelope, noLinkheader,?page=/?per_page=change nothing. So the picker loads once and filters locally. What it does need is an?org=: the bare call returns only the linked account's own repositories, which is[]here.2. Exactly 2000 is a truncation, and the cap held on every further org I tried. #142 found
microsoft/google/apacheeach at exactly 2000. Twenty more orgs this round:Azure,mozilla,IBMintel1360,adobe1123,elastic958,hashicorp943,alibaba540,tencent296,openai272,rust-lang246,vercel240,nodejs231,angular214,facebook177,python96,kubernetes79,spring-projects76,golang61,gitlab-org2Six organizations now sit on exactly 2000. The picker therefore says "the server returns at most 2,000 per organization, so this is the first 2,000, not all of them" rather than implying completeness. The slim four-key projection (
full_name,name,private,owner_login) was also uniform across ~13,000 further items.The acceptance criterion that cannot be met literally
There is no scope introspection anywhere in the API.
/api/user/identitiesreports provider, username and timestamps — no scopes./api/auth/github/statusreports{ configured, clientId, manageOrgAccessUrl }, i.e. whether the server has a GitHub OAuth app, not what this user granted. A sign-in-only link is therefore indistinguishable from an Issues-capable one until a call comes back empty or refused.So rather than guess, the panel says so plainly (
GitHubLinkViewModel.ScopeNote) and does the useful half:/api/user/identities— never on an empty collection, becauseGET /api/github/reposreturns200 []on an account that is linked;manageOrgAccessUrl.Verified live (test account, bearer sync-token, 2026-09-16)
GET /api/user/identities→provider: github,InterlinedListMessenger. Re-confirms GitHub: InterlinedApiClient.GitHub.cs service + models for all nine /api/github/* endpoints #142: the account is linked, so200 []from/reposmeans "owns no repos", not "not linked".GET /api/auth/github/status→{ configured: true, clientId: Ov23li9eXYK1i6psJW6G, manageOrgAccessUrl: …/settings/connections/applications/Ov23li9eXYK1i6psJW6G }.GET /api/github/repos?org=…× 20 orgs — see the table.GET /api/listsandGET /api/lists?all=1both carrysource/githubRepo/githubRepoPrivate("local"/null/null).?all=1is whatGitHubListIndexuses.POST /api/lists { title: "ZZ claude-probe gh-norepo", source: "github" }with nogithubRepo→ 400"githubRepo is required for GitHub-backed lists (format: owner/repo)", and nothing was created (a followingGET /api/listsstill showed exactly the one pre-existing list). That is the contractCreateGitHubBackedListAsynctargets, verified without creating any repository linkage.dotnet build -c Debugand-c Release— both green, 0 warnings.Deliberately not called
refreshStatusis nullable and the caller re-reads. Needs a live create on a throwaway repo before it can be called verified.POST /api/github/issues, thePATCH, and the comments endpoint — they write to real GitHub repositories (GitHub: issue create / update / comment from list rows #75's territory).ZZ claude-probelist above, deleted and confirmed gone with a closingGET.Notes for review
Views/GitHubMark.xamlembeds Octicons'mark-githubpath (MIT, © GitHub, Inc.), attributed in a comment — swap it if you'd rather not carry third-party path data.GitHubListIndexis a static cache and is not registered inAppServices, to avoid touching a file three other branches also touch.githubDefaultRepois read out of the raw/api/userJSON by GitHub: InterlinedApiClient.GitHub.cs service + models for all nine /api/github/* endpoints #142's client, not fromCurrentUser—Models/CurrentUser.csis untouched (open PR fix(models): reconcile Message and CurrentUser against live payloads #131).Buttons, notRadioButtons, on purpose: aRadioButton's click handlerSetValuesIsChecked, which tears down a one-way binding to the view model.Closes #73
🤖 Generated with Claude Code