Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Open [http://localhost:3000](http://localhost:3000).
| `/` | Marketing landing page |
| `/open` | URL scheme receiver — accepts `?owner`, `?repo`, `?issue` params and triggers `worktree://open?...` |

The `/open` route is the public URL API consumed by the GitHub Action, CLI, and integrations. See [docs/api-versioning.md](docs/api-versioning.md) for the versioning policy and parameter reference.

## License

MIT
47 changes: 47 additions & 0 deletions docs/api-versioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# API Versioning Policy

## Overview

The primary "API surface" of this site is the `/open` URL scheme bridge. External consumers (GitHub Action comments, CI scripts, integrations) construct `/open?...` URLs. Breaking changes to accepted parameters must not invalidate old links.

## Current version: v1 (unversioned)

All existing `/open` URLs are treated as **v1**. The `v` query parameter is optional and defaults to `1`.

### v1 parameter sets

**GitHub issue**
```
/open?owner=<owner>&repo=<repo>&issue=<number>
```

**GitLab issue**
```
/open?owner=<owner>&repo=<repo>&gitlab_issue=<number>
```

**Jira issue**
```
/open?owner=<owner>&repo=<repo>&jira_issue_key=<key>&jira_host=<host>
```

## Versioning convention

| Version | Path | Status |
| ------- | ---------------- | ------- |
| v1 | `/open` | Current |
| v2+ | `/open?v=2&...` | Future |

### Rules

1. **Never remove or rename params from an existing version.** Old links must keep working.
2. **Introduce a new version for breaking changes.** Add `v=2` (or higher) when dropping, renaming, or reinterpreting existing params.
3. **New optional params are non-breaking.** They may be added to an existing version without a version bump.
4. **The `v` param must be a positive integer string.** Unknown values return an error state with a clear message.

## Adding a new version

1. Extend the `resolveParams` function in `src/app/open/page.tsx` with a branch for the new version.
2. Add the new param set to this document under a new `## v<N> parameter sets` section.
3. Keep the old version branch intact — do not delete it.
4. Update the version table above.
23 changes: 18 additions & 5 deletions src/app/open/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
import { useEffect, useRef, useState } from "react";
import { type IssueParams, buildWorktreeUrl } from "./issue-card";
import { NoParamsView } from "./no-params-view";
import { UnknownVersionView } from "./unknown-version-view";
import { OpeningView } from "./opening-view";
import { InstallView } from "./install-view";
import { OpenNav, OpenFooter } from "./open-layout";

type Phase =
| "loading" // reading URL params
| "opening" // scheme triggered, awaiting user confirmation
| "success" // user confirmed it opened
| "install" // user says it didn't open → show install guide
| "no-params"; // no valid params in URL
| "loading" // reading URL params
| "opening" // scheme triggered, awaiting user confirmation
| "success" // user confirmed it opened
| "install" // user says it didn't open → show install guide
| "no-params" // no valid params in URL
| "unknown-version"; // unrecognized v= param

function get(sp: URLSearchParams, key: string): string {
const val = sp.get(key);
Expand Down Expand Up @@ -42,13 +44,23 @@ function resolveParams(sp: URLSearchParams): IssueParams | null {
return { kind: "github", owner, repo, issue };
}

const SUPPORTED_VERSIONS: string[] = ["1"];

export default function OpenPage() {
const [phase, setPhase] = useState<Phase>("loading");
const [params, setParams] = useState<IssueParams | null>(null);
const [urlVersion, setUrlVersion] = useState<string>("1");
const schemeTriggered = useRef(false);

useEffect(() => {
const sp = new URLSearchParams(window.location.search);
const raw = sp.get("v");
const v = raw !== null ? raw : "1";
setUrlVersion(v);
if (!SUPPORTED_VERSIONS.includes(v)) {
setPhase("unknown-version");
return;
}
const p = resolveParams(sp);
if (!p) { setPhase("no-params"); return; }
setParams(p);
Expand All @@ -71,6 +83,7 @@ export default function OpenPage() {
<main className="open-main">
<div className="open-content">
{phase === "no-params" && <NoParamsView />}
{phase === "unknown-version" && <UnknownVersionView version={urlVersion} />}
{phase === "loading" && (
<div className="loading-center">
<div className="spinner-lg anim-spin" />
Expand Down
23 changes: 23 additions & 0 deletions src/app/open/unknown-version-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Link from "next/link";
import { AlertTriangle } from "lucide-react";

export function UnknownVersionView({ version }: { version: string }) {
return (
<div className="no-params-center anim-fade-up">
<div className="no-params-icon">
<AlertTriangle size={20} color="#9090a8" strokeWidth={1.5} />
</div>
<h1 className="no-params-title">Unsupported link version</h1>
<p className="no-params-body">
This link uses{" "}
<code className="inline-code">v={version}</code>, which is not
supported by this version of the site. The link may have been
generated by a newer version of the Worktree runner. Please update
your runner to open this workspace.
</p>
<Link href="/" className="btn-ghost">
← Back to home
</Link>
</div>
);
}
Loading