Skip to content

Lists: repository link, private-repo tag, and Refresh from GitHub (#74) - #176

Merged
Adron merged 2 commits into
mainfrom
issue-74-repo-link-refresh
Sep 24, 2026
Merged

Adron merged 2 commits into
mainfrom
issue-74-repo-link-refresh

Conversation

@Adron

@Adron Adron commented Sep 16, 2026

Copy link
Copy Markdown
Member

Stack

main → #142 (issue-72-github-service) → #175 (issue-73-github-backed-list) → this PR → #76 (reconnect handoff). Base is issue-73-github-backed-list, not main.

What this adds

Views/GitHubListHeader.xaml(.cs) + ViewModels/GitHubListHeaderViewModel.cs — the strip under a GitHub-backed list's name:

  • owner/repo issues as a link, opening that repository's issues page in the OS browser.
  • A Private repo tag, with copy that keeps the two visibilities apart.
  • Refresh from GitHub → POST /api/lists/{id}/refresh, then a re-read, then the host's row reload.

Plus Models/ListRefreshResult.cs and RefreshListFromGitHubAsync on the GitHub-lists partial.

Hosting cost in ListsView.xaml: 1 line

<local:GitHubListHeader ListId="{Binding SelectedList.Id}" AfterRefreshCommand="{Binding SelectListCommand}" AfterRefreshCommandParameter="{Binding SelectedList}"/>

The control collapses itself for a local list or a null id, so it needs no conditional wrapper. ListsViewModel is untouched; so is ListsView.xaml.cs; so is Models/ListSummary.cs.

The tag is about the repository, not the list — and it has three states

githubRepoPrivate is true / false / null, and the null case is the one that's easy to get wrong:

value renders
true Private repo tag + the explanation below
false "Public repository on GitHub."
null no tag, plus "Repository visibility hasn't been recorded yet — it's read from GitHub on the next sync. Refresh from GitHub to find out."

ShowPrivateRepoTag fires only on an explicit true, so a list created before the tag existed is never presented as public — which is exactly what /help/lists requires ("Lists created before this tag existed show no tag until their first sync"), and why ListSummary.GithubRepoPrivate is bool? in #133.

The explanation is shown as body copy, not buried in a tooltip, because the distinction is the point of the tag:

This repository is private on GitHub. That's about the repository, not about who can see this InterlinedList list — the two are set separately. Anyone you invite to the list who doesn't have repository access will get a GitHub sign-in page or "not found" when they follow the link.

Refresh re-reads, and that isn't belt-and-braces

Two independent reasons the POST alone isn't enough:

  1. The success envelope has never been observed (below), so trusting it would be guessing.
  2. A sync is documented to re-read repository visibility from GitHub, so githubRepoPrivate may have just changed — the re-read is how the tag updates. When it does change, the status line says so explicitly.

The re-read also writes back into GitHubListIndex, so the browser badge stays in step, and AfterRefreshCommand reloads the rows (a refresh rewrites them from the repository's issues).

Verified live (test account, 2026-09-16)

POST /api/lists/{id}/refresh against a throwaway local list — created as ZZ claude-probe refresh, probed, deleted, and confirmed gone with a closing GET (404 not_found), leaving the account's own single New list untouched:

POST /api/lists/{local-list-id}/refresh
  → 400 { "error": "Refresh is only available for GitHub-backed lists", "code": "bad_request" }
GET  /api/lists/{local-list-id}
  → 200, identical body, updatedAt unchanged          # the rejection is inert

So the 400 is safe and specific, and it's what CanRefresh prevents you reaching — surfaced as "This list isn't GitHub-backed, so there's nothing to refresh from" if the list's source ever changes underneath the UI.

Also confirmed: GET /api/lists and GET /api/lists/{id} both carry source/githubRepo/githubRepoPrivate.

dotnet build -c Debug and -c Release — both green, 0 warnings.

Deliberately not called

  • The refresh success path. The account owns no GitHub-backed list, and creating one would wire a real GitHub repository to a real list on shared test infrastructure. The OpenAPI spec documents 201 with the body "not individually modelled yet", so ListRefreshResult keeps Raw and picks up message / refreshStatus / a count if they happen to be present, rather than typing an envelope nobody has seen. Needs one live refresh of a real GitHub-backed list before it can be called verified — the field names are the only guess in this PR.
  • POST /api/github/issues, the PATCH, the comments endpoint — real GitHub writes, GitHub: issue create / update / comment from list rows #75's territory.

Closes #74

🤖 Generated with Claude Code

Adds the strip that sits under a GitHub-backed list's name, as a self-contained
`GitHubListHeader` control — one line of hosting in `ListsView.xaml`, nothing
added to `ListsViewModel`.

- `owner/repo issues` as a link, opening that repository's issues page in the
  OS browser.
- A `Private repo` tag on an explicit `githubRepoPrivate: true`, with copy that
  keeps repository access and list visibility apart: they are set separately, so
  someone invited to the list may still get a GitHub sign-in page or a 404.
- A **null** `githubRepoPrivate` renders no tag and says why — "visibility
  hasn't been recorded yet, it's read from GitHub on the next sync" — rather
  than presenting an unrecorded value as public.
- "Refresh from GitHub" calling `POST /api/lists/{id}/refresh`, then re-reading
  the list (the refresh envelope is unobserved, and the re-read is also the only
  way to pick up a visibility that just changed) and running the host's command
  to reload rows.

The endpoint's rejection path is verified live: `POST /api/lists/{id}/refresh`
on a `local` list answers 400 "Refresh is only available for GitHub-backed
lists" and is inert — re-reading gave an identical body with an unchanged
`updatedAt`. The success path could not be exercised: the account owns no
GitHub-backed list and creating one would wire a real repository, so
`ListRefreshResult` keeps the raw body instead of guessing an envelope.

Closes #74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ListsView.xaml conflict: GitHubListHeader (#74) and the PoweredTemplatePanel +
column form builder (#174/#161) all insert at the top of the right pane. All
additive — kept all three.

Order is deliberate: GitHubListHeader goes FIRST because it identifies the
selected list (owner/repo link, private-repo tag, Refresh from GitHub), so it
belongs directly under the title rather than below a large editor panel.

Debug and Release both build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Adron
Adron merged commit ba88768 into main Sep 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GitHub: repository link, private-repo tag, and Refresh from GitHub

1 participant