feat(app): Quick Sync connects to GitHub or Gitea, sends a token only when asked, and the app opens where it was left #210

Merged
james.maes merged 3 commits from feat/209-gitea-quick-sync into main 2026-11-11 22:25:03 +00:00
Owner

What

Step 4 of #201, stacked on #207: GitHub and Gitea are equal on the card the app opens on, and the start screen follows what the app already knows.

  • Quick Sync asks where the repositories are: GitHub or Gitea (same two labels as the wizard), and for Gitea the server's address. On a first run nothing is chosen, and the token box takes a token only once a place is.
  • Nothing is sent until the user asks. A token that is typed, pasted or filled in goes nowhere by itself. Find lists the organizations and Load repositories lists the repositories; both go to the listers of the place on the card. An account's page lists and loads on open as before.
  • A server address is taken when its box is left, not while it is typed.
  • Another place forgets what belonged to the first: token, organization, table.
  • The card opens the way the last working connection left it (place, address, organization, folder; never a token), and the app opens where it was left (last workspace, else the first account, else Quick Sync).
  • Save as account is offered in a bar once a connection loaded; the account is saved on the place the connection was on.
  • The connection chip names the place; the "only your own account is visible" hint is in the words of the forge.
  • README, ADR 0010, threat model T16, CLAUDE.md.

Why

Closes #209. From the owner's first look at the build: the app opened on "Connect to GitHub", and Gitea existed only in Add account. Then, from a note during the work: a pasted token was sent after half a second to whatever place happened to be selected, which on a first run was GitHub. Both are fixed here: no place is preselected, and no token leaves the machine without a button being pressed.

The rules, each pinned by a test in QuickSyncPlaceTests:

  • before a place is chosen nothing can be sent, and a default token is not filled in;
  • a token that is typed, pasted or filled in is sent nowhere until Find or Load;
  • a token is handed only to the listers of the place on the card;
  • choosing another place (GitHub to Gitea, one server to another) empties token, organization and table; the same server spelled another way is the same place;
  • a place whose listers cannot be made is not taken, and nothing goes to the listers of the old one;
  • the place cannot change while the workspace is busy;
  • what is remembered holds no token, and a remembered place that cannot be used is not restored.

How it was tested

  • QuickSyncPlaceTests (view model, settings, start workspace); the UI suite follows the new flow, with one new test for Quick Sync on a Gitea server against the offline fixture.
  • Gates, started from PowerShell: numbers in the comment below.
  • Looked at on screen: the first-run card, the Gitea card, the loaded table with the save bar.

Not in this change

  • Settings still holds one default token, GitHub's (step 5: a default per forge).
  • About and the Share invitation still say "GitHub" (step 6).

Not merged until

1.1.1 is tagged (#200) and #203, #205 and #207 are merged. The base of this pull request is the branch of #207.

Checklist

  • Commits are signed and signed off (git commit -S -s)
  • Zero build warnings; format and lint are clean
  • Tests added or updated; the 100% coverage gate still passes
  • Docs updated for user-visible changes; threat model updated if the attack surface changed (README, ADR 0010, T16, CLAUDE.md)
  • No secrets, credentials, or personal data
  • New dependencies are justified below, or none were added (none)
  • Substantial AI assistance, if any, is noted here: written with Claude Code.

🤖 Generated with Claude Code

## What Step 4 of #201, stacked on #207: GitHub and Gitea are equal on the card the app opens on, and the start screen follows what the app already knows. - **Quick Sync asks where the repositories are:** GitHub or Gitea (same two labels as the wizard), and for Gitea the server's address. On a first run nothing is chosen, and the token box takes a token only once a place is. - **Nothing is sent until the user asks.** A token that is typed, pasted or filled in goes nowhere by itself. **Find** lists the organizations and **Load repositories** lists the repositories; both go to the listers of the place on the card. An account's page lists and loads on open as before. - **A server address is taken when its box is left**, not while it is typed. - **Another place forgets what belonged to the first:** token, organization, table. - **The card opens the way the last working connection left it** (place, address, organization, folder; never a token), and **the app opens where it was left** (last workspace, else the first account, else Quick Sync). - **Save as account** is offered in a bar once a connection loaded; the account is saved on the place the connection was on. - The connection chip names the place; the "only your own account is visible" hint is in the words of the forge. - README, ADR 0010, threat model T16, CLAUDE.md. ## Why Closes #209. From the owner's first look at the build: the app opened on "Connect to GitHub", and Gitea existed only in Add account. Then, from a note during the work: a pasted token was sent after half a second to whatever place happened to be selected, which on a first run was GitHub. Both are fixed here: no place is preselected, and no token leaves the machine without a button being pressed. The rules, each pinned by a test in `QuickSyncPlaceTests`: - before a place is chosen nothing can be sent, and a default token is not filled in; - a token that is typed, pasted or filled in is sent nowhere until Find or Load; - a token is handed only to the listers of the place on the card; - choosing another place (GitHub to Gitea, one server to another) empties token, organization and table; the same server spelled another way is the same place; - a place whose listers cannot be made is not taken, and nothing goes to the listers of the old one; - the place cannot change while the workspace is busy; - what is remembered holds no token, and a remembered place that cannot be used is not restored. ## How it was tested - `QuickSyncPlaceTests` (view model, settings, start workspace); the UI suite follows the new flow, with one new test for Quick Sync on a Gitea server against the offline fixture. - Gates, started from PowerShell: numbers in the comment below. - Looked at on screen: the first-run card, the Gitea card, the loaded table with the save bar. ## Not in this change - Settings still holds one default token, GitHub's (step 5: a default per forge). - About and the Share invitation still say "GitHub" (step 6). ## Not merged until 1.1.1 is tagged (#200) and #203, #205 and #207 are merged. The base of this pull request is the branch of #207. ## Checklist - [x] Commits are signed and signed off (`git commit -S -s`) - [x] Zero build warnings; format and lint are clean - [x] Tests added or updated; the 100% coverage gate still passes - [x] Docs updated for user-visible changes; threat model updated if the attack surface changed (README, ADR 0010, T16, CLAUDE.md) - [x] No secrets, credentials, or personal data - [x] New dependencies are justified below, or none were added (none) - [x] Substantial AI assistance, if any, is noted here: written with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Gates on efed82b, started from PowerShell: build 0 warnings; engine and view-model tests 1,440; CLI tests 248; 100% line coverage on the three assemblies; format clean; UI suite 15/15 (the suite follows the new flow: choose a place, enter the token, press Find; one new test for Quick Sync on a Gitea server). An independent review follows.

Gates on `efed82b`, started from PowerShell: build 0 warnings; engine and view-model tests 1,440; CLI tests 248; 100% line coverage on the three assemblies; format clean; UI suite 15/15 (the suite follows the new flow: choose a place, enter the token, press Find; one new test for Quick Sync on a Gitea server). An independent review follows.
Author
Owner

Added fcb90d6 after the owner tried the build:

  • No place is chosen by focus. The choice of place was a RadioButtons group, which selects its first option when keyboard focus lands on it; the window opens with the focus there, so GitHub came up chosen. It is now two radio buttons of their own. Pinned by a UI test in a launched app of its own (FirstRunTests).
  • The pane's button closes the pane to its icons and opens it again, and the pane has a title (#213).

Gates on it, from PowerShell: build 0 warnings; engine 1,440; CLI 248; 100% line coverage; format clean; UI 17/17.

Added `fcb90d6` after the owner tried the build: - **No place is chosen by focus.** The choice of place was a `RadioButtons` group, which selects its first option when keyboard focus lands on it; the window opens with the focus there, so GitHub came up chosen. It is now two radio buttons of their own. Pinned by a UI test in a launched app of its own (`FirstRunTests`). - **The pane's button** closes the pane to its icons and opens it again, and the pane has a title (#213). Gates on it, from PowerShell: build 0 warnings; engine 1,440; CLI 248; 100% line coverage; format clean; UI 17/17.
Author
Owner

Independent review of efed82b: merge after fixes. It found nothing sent at construction, on restore, on a default-token fill or on a paste. Fixed in a03013f:

Finding What was done
Ctrl+R loaded with an address that was edited but not taken yet While the address box holds another text than the address that was taken, the card names no usable place
An address made unusable after a load left the old place's table syncable Sync, Retry failed and path recovery need a usable place; each command checks for itself; no account is offered from such a card
"Nothing before a place is chosen" held only in the shell The first choice forgets what was on the card; Load does nothing without a place, whatever starts it
The form's subscription was never removed and wrote back It follows the view model only while it is in the tree; its own changes of the token box are not taken for typing
Settings written at start and on every switch Written when the window closes, and what the app remembers is not written over a settings file that could not be read
The save offer came back on every refresh; a failing callback failed the load Offered once; remembering is not part of loading
Stale lines in README, ADR 0010 Corrected; T13 says what a changed settings.json can do

Left, noted here: the place can change while the path-recovery dialog is awaited (the dialog is modal in the app); a Quick Sync built without the listers of a place still looks up unasked (only tests build one).

Gates, from PowerShell: build 0 warnings; engine 1,446; CLI 248; 100% line coverage; format clean; UI 17/17.

Independent review of `efed82b`: **merge after fixes**. It found nothing sent at construction, on restore, on a default-token fill or on a paste. Fixed in `a03013f`: | Finding | What was done | | --- | --- | | Ctrl+R loaded with an address that was edited but not taken yet | While the address box holds another text than the address that was taken, the card names no usable place | | An address made unusable after a load left the old place's table syncable | Sync, Retry failed and path recovery need a usable place; each command checks for itself; no account is offered from such a card | | "Nothing before a place is chosen" held only in the shell | The first choice forgets what was on the card; Load does nothing without a place, whatever starts it | | The form's subscription was never removed and wrote back | It follows the view model only while it is in the tree; its own changes of the token box are not taken for typing | | Settings written at start and on every switch | Written when the window closes, and what the app remembers is not written over a settings file that could not be read | | The save offer came back on every refresh; a failing callback failed the load | Offered once; remembering is not part of loading | | Stale lines in README, ADR 0010 | Corrected; T13 says what a changed `settings.json` can do | Left, noted here: the place can change while the path-recovery dialog is awaited (the dialog is modal in the app); a Quick Sync built without the listers of a place still looks up unasked (only tests build one). Gates, from PowerShell: build 0 warnings; engine 1,446; CLI 248; 100% line coverage; format clean; UI 17/17.
Author
Owner

The review of #215 also read the last commit here (a03013f). Two findings concern this code and are fixed in #215 (1fc76d1), which is stacked on this branch: the window now writes "open where you left off" on every close, and an address typed over and typed back is the server again without leaving the box (Load and Find stayed withheld). Tests added there: path recovery while the address is no place, GitHub having no address to edit, and the test on a lookup in flight fails fast.

The review of #215 also read the last commit here (a03013f). Two findings concern this code and are fixed in #215 (1fc76d1), which is stacked on this branch: the window now writes "open where you left off" on every close, and an address typed over and typed back is the server again without leaving the box (Load and Find stayed withheld). Tests added there: path recovery while the address is no place, GitHub having no address to edit, and the test on a lookup in flight fails fast.
james.maes changed target branch from feat/206-gitea-wizard to main 2026-11-11 22:25:00 +00:00
james.maes added 3 commits 2026-11-11 22:25:00 +00:00
Step 4 of Gitea support (#201). GitHub and Gitea are equal options on the
card the app opens on, with the same two labels as the wizard.

- Quick Sync's card asks where the repositories are: GitHub or Gitea, and for
  Gitea the server's address. On a first run nothing is chosen, and the token
  box takes a token only once a place is. The title, the token box and the
  "How to create a token" link follow the place.
- Nothing is sent until the user asks. A token that is typed, pasted or filled
  in from a saved default goes nowhere by itself: the organization list is
  fetched when Find is pressed, and the repositories when Load is. Both go to
  the listers of the place on the card. An account's page lists and loads on
  open as before: its token was saved for its place.
- A server address is taken when its box is left, not while it is typed.
- Choosing another place empties the token, the organization and the table:
  the table's clone addresses are on the other host.
- The card opens the way the last connection that worked left it (place,
  address, organization, target folder, in settings.json). Never a token.
- Once a connection loaded its repositories, a bar offers to save it as an
  account; the account is saved on the place the connection was on.
- The app opens on the workspace that was open last; with nothing recorded,
  on the first account; with no account, on Quick Sync.
- The connection chip names the place, and the hint about a token that sees
  no organization is in the words of the forge.
- README, ADR 0010, threat model T16, CLAUDE.md.

Closes #209

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
- Quick Sync's choice of place was a RadioButtons group. That control selects
  its first option as soon as keyboard focus lands on it, and the window opens
  with the focus there: GitHub was chosen before the user did anything, and a
  token typed next was for a place nobody chose. The choice is now two radio
  buttons of their own, which are checked by an act (a click, Space, an arrow
  key, UI automation) and never by focus.
- The navigation pane was set to stay open, so its button did nothing. The
  pane now closes to its icons and opens again, and has a title beside the
  button.
- UI suite: a launched app of its own for the first run (no place chosen,
  focus chooses none, choosing is an act), and a test for the pane's button.

Refs #209
Closes #213

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
From the review of #210.

- An address that was edited but not taken yet (the box was not left: a
  keyboard shortcut moves no focus) was still the place of a load started
  with Ctrl+R. The card now names no usable place while its address box holds
  another text than the address that was taken.
- An address made unusable after a load left the old place's table syncable,
  and a token typed next went to the old server's clone addresses. Sync,
  Retry failed and path recovery need a usable place, as Load and Find do; the
  commands check for themselves, whatever started them; no account is offered
  from a card that names no place.
- A token or organization put on the card before a place was chosen is not
  kept for the place that is: the view model no longer relies on the shell's
  disabled token box.
- The form follows its view model only while it is in the tree, and its own
  changes of the token box are not taken for typing.
- The workspace that was open is written when the window closes, not on every
  switch, and what the app remembers by itself is not written over a settings
  file that could not be read.
- The offer to save the connection is made once; a failure to remember the
  connection does not fail the load.
- README, ADR 0010, T13.

Refs #209

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
james.maes merged commit 9a09cee62f into main 2026-11-11 22:25:03 +00:00
Sign in to join this conversation.