OxygenHub for Visual Studio Code
| Status | Draft 0.1 |
| Scope | The forge in the editor: sign in to an OxygenHub, see and review proposals with comments anchored to atoms, approve with a seal, land, work with issues, watch checks, and open anything on the hub. The counterpart of the GitHub Pull Requests and Issues extension. |
| Depends on | oxygen-vscode ≥ 0.7.0 (its exported API and remote providers), OxygenHub ≥ 0.5 (/api/v1), oxy with seal --proposal and proposal land |
| Date | 2026-09-27 |
1. Goals
- Everything the hub can do about a proposal or an issue is reachable without leaving the editor, and everything that changes history still goes through
oxyon the user's machine with the user's key: sealing an approval and landing runoxy, never a hub-side signature. - Build on oxygen-vscode, never beside it. Repositories, status, bookmarks and remotes come from its exported API; this extension registers a remote provider so "Open on Remote" works for hubs.
- Review where the code is. Proposal comments appear as VS Code comment threads on the projected state's files, anchored to atoms exactly as the hub stores them, so they follow the line through rebases.
- Honest about identity. An approval shows as pending until the hub confirms the seal; a check shows what the hub reports.
Non-goals for 1.0: creating repositories or organisations, administration, notifications beyond the status bar count, editing files on the hub.
2. How it compares
| GitHub Pull Requests and Issues | OxygenHub for VS Code |
|---|---|
| Sign in with GitHub (OAuth) | Sign in with a personal access token; an authentication provider oxygenhub other extensions can request |
| Pull Requests view: waiting for my review, created by me, all | Proposals view: Assigned to me (review requested or approval needed), Mine, Open, per repository |
| Checkout PR branch | Fetch the proposal's changes and check out its projected state into a bookmark (oxy fetch + bookmark set) |
| Review comments in the diff, threads | Comment threads on the projected state's files via the Comments API, anchored to atoms |
| Approve, Request changes | Same; Approve also offers to run oxy seal --proposal so it counts |
| Merge button | Land runs oxy proposal land (or the hub lands when it has no policy) |
| Issues view, create issue | Same, with labels, assignees and milestone |
| Checks under the PR | Checks on the projected state, live over the hub's event stream |
| Open on GitHub | Open on Remote, through the remote provider |
3. Architecture
src/
extension.ts activation, wiring, commands
auth.ts AuthenticationProvider "oxygenhub": sessions = {hub base URL, token}, stored in SecretStorage
hub.ts HubClient: typed fetch over /api/v1 using hub-api's generated types (src/hubapi.d.ts, copied by `npm run types`)
provider.ts the RemoteProvider registered with oxygen-vscode: matches a session's base URL, builds hub URLs
proposals.ts TreeDataProvider: proposals by repository and bucket; detail webview; actions
review.ts CommentController over oxy: content of the projected state, threads placed by atom, reply/resolve
issues.ts TreeDataProvider: issues; create, comment, close
checks.ts status bar: checks of the current bookmark's proposal; SSE subscription
hubapi.d.ts generated from the hub (do not edit)
Pure modules (hub.ts URL building and parsing, provider.ts) are tested with Node against a real hub started from the oxygenhub binary when present; the rest is exercised manually.
4. Sign in
oxygenhub.signIn asks for the hub URL (default: the first remote that looks like a hub, https://…/owner/name minus the last two segments) and a personal access token (Settings › Tokens on the hub; scopes repo:read repo:write proposals:write issues:write). The token is kept in SecretStorage under the base URL; sessions are exposed through vscode.authentication so oxygen-vscode or others can reuse them. oxygenhub.signOut forgets it.
5. Proposals
The OxygenHub: Proposals view lists, per repository that has a hub remote, proposals in three groups: needing me (open, not mine, where I have not approved or where my approval is pending a seal), mine, and other open ones. Each row shows number, title, author, target, state chips (draft, conflicted, landable) and the check summary. Actions: open detail, open on hub, check out, review (comment / request changes / approve), seal approval, land, close, and Mark Ready for Review on a draft or Convert to Draft on an open one (PATCH …/proposals/N {"draft"}, oxygenhub#313).
Detail is a webview: description, blockers, approvals (sealed or pending), checks, the changes list with signatures, and buttons for the actions. Check out fetches from the hub remote and sets a local bookmark proposal/N to the projected heads, then switches to it, so the files in the editor are the projected state; review threads then appear in place.
5.1 Diff editors
Each proposal row expands to the files it changes (GET …/proposals/N/files), with a diff icon per kind and +added −removed. Clicking a file opens VS Code's own diff editor; View Changes on the row (or in the detail) opens every file in one multi-file diff editor (vscode.changes). Both sides are read-only documents of the oxygenhub-proposal scheme, served from GET …/proposals/N/content/{base|head}/{path}: the target bookmark's state on the left, the projected state on the right, so a proposal can be read and reviewed without checking it out. A side where the file does not exist is empty; a rename shows its old path on the left. Open sides reload on refresh, since a proposal gains changes when its source bookmark moves.
6. Review threads
A CommentController for the checked-out proposal. Threads come from GET …/proposals/N/comments; each anchored comment is placed by asking oxy for the current line of its atom (oxy blame --json gives atoms per line; the extension finds the line whose (hash, index) matches). Replying posts a comment anchored to the same atom; a new thread on a line posts with the line's atom. Resolve toggles the hub's resolved flag. Threads whose atom is no longer live are shown in a separate "outdated" list in the view.
In the diff editors (§5.1) the same controller places threads on the projected side only, at the line the hub reports for each anchored comment (the API's line, found by blame on the projected state), so no checkout or local oxy call is needed. A new thread or reply there posts the file and line; the hub anchors it to that line's atom.
6.2 References while typing
In the Oxygen commit box and in comment editors, # completes the repository's issues and proposals (one number space, open ones first, newest first; the title is shown and matched) and @ completes people: the authors and assignees of those issues and proposals, and the signed-in user. Hovering a #N shows its kind, state, title and author with a link to the hub. The commit box's repository is the one its source control names (rootUri); a comment editor uses the workspace's hub repository when there is exactly one. The lists are fetched per repository and reused for a minute, and cleared on refresh.
7. Approvals and landing
Approve posts the review, then offers to run oxy seal --proposal <url> in the repository's terminal so the approval is backed by the user's seal; the row shows "pending seal" until the hub reports it sealed. Land runs oxy proposal land <url> in a terminal (the hub's own landing is offered when the hub reports it can land, for repositories without a policy). Both use the integrated terminal so the user sees exactly what runs with their key.
Ready to Land… (the Proposals view's title bar) lists, across every signed-in hub, the open proposals the user could land now (GET /user/proposals?landable=true, oxygenhub#311): approved, checks passed, no conflicts, in repositories they write to. One in the workspace is landed with oxy proposal land or opened; any other opens on the hub.
8. Issues
The OxygenHub: Issues view lists open issues per repository (assigned to me first). Actions: open detail (webview with the timeline and a comment box), open on hub, create issue (title and body inputs), close, reopen, assign to me.
8.1 Issues from the editor
New Issue from Selection (editor context menu) opens an issue quoting the selected lines, or the current line when nothing is selected, under a permalink to them on the hub: to the bookmark's head when it has exactly one and the file is saved, otherwise to the bookmark, with a note that the lines may move. A TODO, FIXME, XXX or HACK line suggests the title, and after the issue is opened the extension offers to write its number into the line (TODO(#12): …). The new issue opens in the editor.
8.2 Releases
The Releases view lists each repository's releases, newest first (GET /repos/{o}/{n}/releases): title, tag, latest or pre-release, date; the tooltip shows the start of the notes. Expanding a release lists its uploaded assets (size, downloads) and the hub's source archives; picking one downloads it through the browser, picking the release opens it on the hub. Save Asset… (inline on an asset) downloads it into a file the user picks, with the session's token, so a private repository's assets come too; the token is sent only to the hub itself.
Show Trust Report (inline on a release) opens the hub's trust report (GET …/releases/trust/{tag}, oxygenhub#79) as a Markdown preview: the tag's signature and signer, the changes since the previous release, each author's signed, sealed, unsigned, revoked and invalid changes, which attested OxygenFlow build made each asset (and whether it built the tagged state), and the audit's verdicts.
Draft Release… (the view's title bar, or inline on a repository) releases a tag that has none yet: pick the tag (GET /tags, minus those already released), confirm a title (the tag's message by default), and choose release or pre-release (a tag such as v2.0-rc1 or 1.0.0-beta.2 suggests pre-release). The notes are generated: the changes in the tag's history that the newest full release's tag does not have, newest first, one line each with the message's subject and a short hash. The release is created with POST /releases; assets are uploaded on the hub or with oxy. Editing notes after creation happens on the hub.
8.3 Diffusion
The Diffusion view (oxygenhub docs/diffusion.md) shows, for each repository, what it takes from (the subrepos it pins that are on the hub) and what it diffuses to (the repositories on the hub that pin it), from GET /repos/{o}/{n}/diffusion. Each row names the repository at the other end and reads like the hub's pill: the upstream's half, then the consumer's (untested │ passes), how far behind the pin is and the open bump proposal. Its icon shows the more urgent half: failing red, untested amber, waiting grey, else green; a consumer that does not follow does not count. The tooltip gives both halves' sentences, where the pin is and how the consumer follows. Picking a row opens its bump: beside the code when the bump is on this repository (the proposal detail, as the Proposals view opens it), else on the hub; a row without a bump opens the other repository. Open on OxygenHub (inline) opens the row's bump or repository, or a repository's Diffusion tab. Bumps are ordinary proposals, so they are also in Proposals, where they are reviewed, checked out and landed.
8.4 Drops
The Drops view lists the user's drops on each signed-in hub (GET /drops; grouped by hub when there is more than one), then others' drops opened this session. A row shows the title (else the first file's name), and whether it is secret, its files, its stars and whether it is a fork; expanding it lists the files. A file opens as a document of the oxygenhub-drop scheme (/ID/NAME?hub=BASE), served by a FileSystemProvider: reading takes the content from GET /drops/{id} (text up to 512 KiB) or the file's raw address; saving commits the file to the drop (PATCH /drops/{id} with that one file, message Edit NAME), so each save is a change in the drop's history; deleting removes the file the same way. Renaming is refused (add it under the new name and remove the old).
Share as a Drop… (an editor's context menu, an editor tab's menu) shares the selection, or the whole document when nothing is selected: the selected lines, their shared indentation removed, under the file's name (an untitled editor's by its language, snippet.py), titled with the file and lines (main.rs, lines 3–9). Share Files as a Drop… (the Explorer's menu) shares the selected files, a folder's files (skipping .oxy, .git, node_modules and target), named by their paths below the folder they share, so two index.ts stay apart. Both ask for the title and Secret (unlisted, readable with its address) or Public, the one oxygenhub.dropVisibility names offered first, then make it (POST /drops) and copy its address (oxygenhub.copyDropLink). The hub's limits apply (50 files, 1 MiB each, text, push protection), and its refusals are shown as they are.
A drop's menu: Add File…, Edit Title and Description…, Make Public / Secret and Delete Drop… (with a modal; it and its history are gone) for the user's own; Copy Drop Link, Copy oxy clone Command, Copy Embed HTML (an <iframe> of its embed address), Clone Drop… (oxy clone in a terminal, into a folder the user picks), Comments… (a pick of its comments with Add a comment…; the comment's author or the drop's owner may delete one) and Star / Unstar for any; Fork Drop for others' (POST /drops/{id}/fork, oxygenhub#349), which opens the copy. Open a Drop… takes a drop's page or id; a page on a hub the user is not signed in to is read anonymously, as a secret drop's address allows.
8.5 New repository
New Repository… creates a repository (POST /repos) on a signed-in hub under the user or one of their organisations (GET /user/orgs), with a name, an optional description and a visibility. When there is a description and the owner has assistants (GET /user/starter?owner=, oxygenhub#347), it offers to ask one for the starting files (POST …/starter) and follows GET …/starter until they are committed, failed, or about six minutes have passed (the hub finishes either way). Then it offers to clone it (oxy clone in a terminal) or open it on the hub.
9. Checks and status bar
A status bar item shows, for the current repository's current bookmark, the open proposal whose source it is and the state of its checks (✔ n passed, ✘ failing, … running). It subscribes to the hub's proposal event stream and refreshes on check events; clicking opens the proposal.
9.1 Notifications
A bell in the status bar shows the unread count summed over every signed-in hub (GET /notifications, polled on oxygenhub.autoRefresh). When the count rises, a message names the newest entry (or the number of new ones) with Open Inbox; oxygenhub.announceNotifications turns that off. Open Inbox is a quick pick of the newest entries, unread first, with Mark all as read at the top. Picking one marks it read and opens it in the editor when it is a proposal or issue of a repository in the workspace (the proposal detail or the issue view), otherwise on the hub. A hub whose token lacks user:read is skipped and named in the bell's tooltip.
9.2 Test results
A TestController puts the newest OxygenFlow test report of each repository's current bookmark (GET …/tests?bookmark=&limit=1, then GET …/tests/{id}) in VS Code's Testing view: the repository, then its suites, then its cases, each with its result, time, failure message and output. A case's id is the repository, suite, class and name, so the Testing view's history lines up across reports. Nothing runs locally: running a test, or refreshing, loads the hub's latest results again; refresh also follows the views' refresh.
9.3 Flow
In the Flow view, a job waiting on its approval rules (approval waiting while queued, oxygenflow#57) shows a shield and "waiting for approval", with Approve Job and Reject Job… (a modal: the run fails) inline (POST …/jobs/{job}/approve|reject). A queued or running run has Expedite Run… (asks why; POST …/expedite, oxygenhub#244) and Cancel Run. A job's tooltip gives its timings (#348): how long it waited for a runner, each phase its runner reported, and its peak disk and memory. Show Reports… on a job lists the reports it published (title, summary, metrics) and opens the picked one on the hub.
10. Configuration
oxygenhub.hubs: known hub base URLs (also learnt from sign in). oxygenhub.proposalBookmarkPrefix: proposal/. oxygenhub.autoRefresh: seconds between view refreshes (60). oxygenhub.dropVisibility: what Share as a Drop offers first, secret (default) or public. oxygenhub.copyDropLink: copy a new drop's address (true).
11. Milestones
| Delivers | |
|---|---|
| 0.1 | Sign in, hub client with generated types, remote provider (Open on Remote works), proposals view with detail and open-on-hub, issues view with create and comment |
| 0.2 | Check out a proposal, review threads on atoms, reviews, seal and land through the terminal, checks in the status bar with live events |
| 0.3 | Issues: labels, assignees, milestones; notifications count; create proposal from the current bookmark |
| 1.0 | Marketplace listing beside oxygen-vscode, three-platform tests |
12. Open questions
- Whether to render the proposal diff in a native diff editor (base vs projected content via an
oxyhub:content provider) instead of the webview's file list. Likely yes in 0.2, since threads need real editors anyway. - Token scopes: the extension could ask for a narrower token per action. Not for 1.0.