feat(clone): background clones and uploads in one global dock that survives F5 and restarts

Clone repository (Files), Add workspace -> Clone and a workspace's extra-folder
clones now start server-side jobs (background: true) and close at once; the
app-wide BackgroundJobsDock (Layout) shows each job's queue place, git progress,
Stop (click twice), and outcome: Open workspace, Retry, an inline login form on
ECREDENTIALS, a Settings -> Git link on ESSHAUTH, dismiss. lib/cloneJobs.ts
polls GET /workspaces/clone/jobs while any job is active, announces a finish it
saw once (toast + agenthub:clone-job event + the starter's onDone), and treats
an older server's synchronous answer as a finished clone. File Explorer uploads
report to lib/uploadJobs.ts (Stop aborts the request in flight; a reload-cut
upload shows as interrupted from localStorage). CloneProgress renders a job's
fields instead of polling a token.
This commit is contained in:
2026-11-02 10:43:57 +07:00
parent a7a8bfd24c
commit 57cbab916a
18 changed files with 1708 additions and 389 deletions
+13 -5
View File
@@ -398,10 +398,17 @@ Clone repository** (`client/src/components/lanes/CloneHereDialog.tsx`) clones
into the folder it has open — of the user's own home or of a workspace's own
folder, never a session worktree, an extra folder or another user's home —
through `POST /api/lanes/clone` below with that folder as `parent`, so every
check of that route applies, and opens a workspace on the clone. It sends a
`progressToken` and shows the queue place / git progress while the clone runs;
closing the dialog then only hides it behind a progress chip (click to reopen)
— the request keeps going and its outcome arrives as a toast.
check of that route applies, and opens a workspace on the clone. It sends
`background: true` and closes as soon as the server accepted the clone; from
then on the app-wide dock (`client/src/components/BackgroundJobsDock.tsx`,
rendered once by `Layout`, store `client/src/lib/cloneJobs.ts` polling
`GET /api/lanes/clone/jobs`) shows the queue place, git's progress, **Stop**
(click twice) and the outcome — on every page, after F5 and after a server
restart (an `interrupted` job offers **Retry**, a login failure an inline login
form, a refused SSH key a link to Settings → Git). Add workspace → **Clone**
and a workspace's **Or clone repositories** box start their clones the same
way. File Explorer uploads show in the same dock (`client/src/lib/uploadJobs.ts`)
with **Stop**; an upload cut short by a reload is shown as interrupted.
#### Clone a repository into a new workspace
@@ -431,7 +438,8 @@ else `400 EBADFIELD`) is the browser's own name for the clone: while the POST is
held open, `GET /api/lanes/clone/progress/:token` answers its 1-based
`position` among the waiting clones, then git's `--progress` line parsed into
`phase`, `percent` and `detail` (`"450/1000, 120.50 MiB | 5.20 MiB/s"`) — the
form polls it every second (`CloneProgress.tsx`). Only the user who started
dashboard's own forms no longer use it (they start background jobs, whose
list carries the same fields, rendered by `CloneProgress.tsx`). Only the user who started
the clone sees it (404 otherwise), and it is dropped a minute after the clone
ends. `url` passes the same allowlist as the git bar's
`publish` (`https://` with no userinfo, `ssh://`, scp-like `user@host:path` —