Add a project to a box from GitHub, instead of git-initting an empty one - #220
Merged
Merged
Conversation
The board merges a repo's copies across engines by (githubOwner, githubName), but that identity was captured once at registration and never again. The phone's "New project" git-inits an empty folder and registers it BEFORE anything is cloned into it, so the row is born with no identity and never regains one: the repo shows twice, and "Run on <box>" re-clones a copy the box already has, into a second directory. Read the identity from the repo's `origin` URL instead of `gh repo view`, and re-derive it on every projects:list so a repo that gains a remote later repairs itself. `gh repo view` needed gh installed, authed and online, so a box missing any of the three silently lost its identity; with no argument it also resolves through BaseRepo(), which reports a fork's PARENT, so two engines could derive two different identities for the same clone. The repair writes through a new repo.updateProject rather than upsertProject, which would have bumped lastOpenedAt and reshuffled the sidebar on every board load. The card key is now lowercased: two clones of one repo really do disagree on casing (clawnify/taskwindow here against clawnify/TaskWindow on a box) and GitHub treats those as the same repo.
The phone's only prominent action on "Add project" was "New", which creates a folder, git-inits it and makes an initial commit. People pressed it meaning "add", then grafted a real repo onto the empty one by hand. The box still shows the seam: TaskWindow's reflog is an initial commit at 21:22:24 followed by a `reset --hard origin/main` twenty minutes later. What that leaves behind is a repo with no origin, which is the one state that can never be merged with its copies on other engines and can never be provisioned onto a second box. Ask the intent up front instead. "Bring one over from GitHub" clones a repo the box's gh can see and registers it in one tap; "Use a repo already on the box" is the old browser; "Start something new" names a folder, inits it, and offers to create the GitHub repo for it at the same time, so a new project is portable from birth rather than a dead end. The repo list is read with `gh api user/repos?affiliation=owner,organization_member` rather than `gh repo list`, which takes a single owner and defaults to the authenticated user: it would have silently omitted every org repo, which for most people is where the work is. Creating the remote is best-effort — if GitHub refuses, the project is still registered and its null githubOwner is how the client knows it has no remote yet. Both halves are v9. Clone and the repo list stay off AteamApi, on the phone's Connection alongside update(), because they address a box rather than an entity on one: the desktop aggregate can't route them and would fall back to the local Mac. A pre-v9 box has neither, and since v7 skew is advisory rather than a refusal, FEATURE_MIN_VERSION.githubProjects is what hides them — an older engine accepts `createRemote` and silently ignores it. Those boxes get the paste-a-URL door instead, which works on anything from v2.
…ocally-then-i # Conflicts: # packages/git-core/src/index.ts # packages/server/src/dispatcher.ts
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.
Follow-up to #218, which fixed the symptom (a repo showing as two cards). This fixes what kept producing it.
Why
The phone's only prominent action on Add project was New, which creates a folder,
git inits it and makes an initial commit. On a screen titled "Add project", people press it meaning add, then graft a real repo onto the empty one by hand. The box still shows the seam:What that leaves is a repo with no origin, which is the one state that can never be merged with its copies on other engines (
unify.tskeys on owner/name read fromorigin) and can never be provisioned onto a second box at all (the composer refuses: "This repo has no git remote to clone onto a box"). Two of the three duplicate rows on my board came from this one button.What changes
Ask the intent up front rather than "pick a folder, and if it isn't a repo, offer to init it":
ghcan see; cloned onto the box and registered in one tap. Paste-a-URL fallback for anythingghwon't list.git inits it, and offers to create the GitHub repo for it at the same time, so a new project is portable from birth instead of a dead end.Design notes
gh api user/repos?affiliation=owner,organization_memberrather thangh repo list. The latter takes a single owner and defaults to the authenticated user, so it would have silently omitted every org repo, which for most people is where the work is. Verified live: the list comes back org repos included, most recently pushed first.Creating the remote is best-effort. If GitHub refuses, the project is still registered, and its null
githubOwneris how the client knows it has no remote yet. Losing the folder because GitHub said no would be worse.Clone and the repo list stay off
AteamApi. They address a box, not an entity on one, so the desktop aggregate cannot route them and would fall back to the local Mac (aggregate.ts: an unrouted method "would fall back to the local engine"). They live on the phone'sConnectionalongsideupdate(), which is offAteamApifor exactly the same reason. The desktop is not touched.Both halves are v9 and gated. Since v7 skew is advisory rather than a refusal, the bump alone protects nobody: a v8 box answers the repo list with "Unknown method" and accepts
createRemoteand silently ignores it.FEATURE_MIN_VERSION.githubProjectsis what hides them, the same lesson v8'sfollowUpsentry encodes. Older boxes get the paste-a-URL door, which works on anything from v2.Verification
createGithubReporun for real, against a name already taken so nothing could be created: gh parsed the exact argv, authenticated, reached GitHub and returnedGraphQL: Name already exists on this account (createRepository). The local repo was left with no origin, which also proves the best-effort path.listRemoteReposrun for real against the live API: org repos included, correct shape, sorted by push date.Alert.prompt's signature read from the installed React Native source, not recalled."~", which the engine wouldmkdirSyncas a folder actually named~. It now resolves the real path fromfs.listDir().parseGithubRepocases covering https, scp-style ssh,ssh://, trailing slash, both real casings, and non-GitHub hosts returning null). Workspace typecheck clean.tsc(not covered byscripts/typecheck.sh) has the same two errors asmainand no others; both are pre-existing rot indemo.tswhere the fixture never followedTaskDTO.prState(v6) andSessionDTO.agentSessionId(v7). Proved by typecheckingHEAD's mobile files and comparing. Left alone.Not covered by a test: the handler's catch-and-continue around
createGithubRepo, because testing it needs GitHub reachable in CI. Verified by reading it plus the proven throw above.The merge of
origin/mainresolved two conflicts, both the squash-reintroduction shape (an additive import of mine landing beside the region #218's squash rewrote). #217's changes todispatcher.tsmerged cleanly and its tests are in the 418.