Skip to content

Commit 76d8a80

Browse files
committed
Refine marketing metadata and preview branch UX
- Add canonical/OG metadata and updated copy for marketing pages - Support preview panel docking, resizing, and keyboard shortcuts - Show only local branches in the branch selector - Fallback to t3code commit hash in desktop build metadata
1 parent 7ac810d commit 76d8a80

9 files changed

Lines changed: 351 additions & 34 deletions

File tree

apps/desktop/src/main.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,11 @@ function resolveEmbeddedCommitHash(): string | null {
355355

356356
try {
357357
const raw = FS.readFileSync(packageJsonPath, "utf8");
358-
const parsed = JSON.parse(raw) as { okcodeCommitHash?: unknown };
359-
return normalizeCommitHash(parsed.okcodeCommitHash ?? parsed.okcodeCommitHash);
358+
const parsed = JSON.parse(raw) as {
359+
okcodeCommitHash?: unknown;
360+
t3codeCommitHash?: unknown;
361+
};
362+
return normalizeCommitHash(parsed.okcodeCommitHash ?? parsed.t3codeCommitHash);
360363
} catch {
361364
return null;
362365
}

apps/marketing/astro.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { defineConfig } from "astro/config";
22

3+
/** Set in deploy (e.g. preview URL) so canonical and Open Graph URLs resolve. */
4+
const site = process.env.PUBLIC_SITE_URL;
5+
36
export default defineConfig({
7+
...(site ? { site } : {}),
48
server: {
59
port: Number(process.env.PORT ?? 4173),
610
},

apps/marketing/src/layouts/Layout.astro

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,36 @@
22
interface Props {
33
title?: string;
44
description?: string;
5+
/** Path for og:image, relative to site root (requires PUBLIC_SITE_URL at build). */
6+
ogImagePath?: string;
57
}
68
79
const {
810
title = "OK Code",
9-
description = "OK Code — The best way to code with AI.",
11+
description = "A minimal GUI for coding agents like Codex and Claude. Web and desktop.",
12+
ogImagePath = "/icon.png",
1013
} = Astro.props;
14+
15+
const canonical = Astro.site ? new URL(Astro.url.pathname, Astro.site).href : null;
16+
const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null;
1117
---
1218

1319
<html lang="en">
1420
<head>
1521
<meta charset="UTF-8" />
1622
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1723
<meta name="description" content={description} />
24+
<meta name="application-name" content="OK Code" />
25+
<meta name="theme-color" content="#09090b" />
26+
{canonical && <link rel="canonical" href={canonical} />}
27+
<meta property="og:type" content="website" />
28+
<meta property="og:title" content={title} />
29+
<meta property="og:description" content={description} />
30+
{ogImage && <meta property="og:image" content={ogImage} />}
31+
<meta name="twitter:card" content="summary_large_image" />
32+
<meta name="twitter:title" content={title} />
33+
<meta name="twitter:description" content={description} />
34+
{ogImage && <meta name="twitter:image" content={ogImage} />}
1835
<link rel="preconnect" href="https://fonts.googleapis.com" />
1936
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
2037
<link
@@ -31,7 +48,8 @@ const {
3148
<div class="page">
3249
<nav class="nav">
3350
<a href="/" class="nav-brand">
34-
<img src="/icon.png" alt="OK Code" class="nav-icon" />
51+
<img src="/icon.png" alt="" class="nav-icon" width="28" height="28" />
52+
<span class="nav-wordmark">OK Code</span>
3553
</a>
3654
<a
3755
href="https://github.com/OpenKnots/okcode"
@@ -150,6 +168,14 @@ const {
150168
.nav-brand {
151169
display: flex;
152170
align-items: center;
171+
gap: 0.5rem;
172+
}
173+
174+
.nav-wordmark {
175+
font-weight: 600;
176+
font-size: 1rem;
177+
letter-spacing: -0.02em;
178+
color: var(--fg);
153179
}
154180

155181
.nav-icon {

apps/marketing/src/pages/download.astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import Layout from "../layouts/Layout.astro";
33
---
44

5-
<Layout title="Download — OK Code" description="Download OK Code for macOS, Windows, or Linux.">
5+
<Layout
6+
title="Download — OK Code"
7+
description="Download OK Code for macOS, Windows, or Linux. Open source coding agent GUI from OpenKnots."
8+
>
69
<h1 class="heading">Download OK Code</h1>
710
<p class="subheading">
811
<span id="version-label">Loading latest release&hellip;</span>

apps/marketing/src/pages/index.astro

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
---
22
import Layout from "../layouts/Layout.astro";
3+
4+
const description =
5+
"A minimal GUI for coding agents like Codex and Claude. Web and desktop. Open source.";
36
---
47

5-
<Layout>
6-
<h1 class="tagline">OK Code is the best way to code with AI.</h1>
8+
<Layout title="OK Code" {description}>
9+
<h1 class="tagline">A minimal GUI for coding agents</h1>
10+
<p class="subtagline">
11+
Codex and Claude today. Web and desktop. From OpenKnots.
12+
</p>
713

814
<a
915
id="download-btn"
@@ -105,11 +111,24 @@ import Layout from "../layouts/Layout.astro";
105111
letter-spacing: -0.035em;
106112
line-height: 1.15;
107113
text-align: center;
108-
margin: 0 0 6vh;
114+
margin: 0 0 1rem;
109115
opacity: 0;
110116
animation: fade-in 1s ease-out forwards;
111117
}
112118

119+
.subtagline {
120+
font-size: clamp(1rem, 2.2vw, 1.2rem);
121+
font-weight: 400;
122+
color: var(--fg-muted);
123+
text-align: center;
124+
line-height: 1.5;
125+
max-width: 28rem;
126+
margin: 0 0 5vh;
127+
padding: 0 1rem;
128+
opacity: 0;
129+
animation: fade-in 1s ease-out 0.08s forwards;
130+
}
131+
113132
/* ── Hero button ── */
114133

115134
.hero-button {

apps/web/src/components/BranchToolbar.logic.test.ts

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
33
import {
44
dedupeRemoteBranchesWithLocalMatches,
55
deriveLocalBranchNameFromRemoteRef,
6+
filterSelectableBranches,
67
resolveBranchSelectionTarget,
78
resolveDraftEnvModeAfterBranchChange,
89
resolveBranchToolbarValue,
@@ -92,6 +93,46 @@ describe("deriveLocalBranchNameFromRemoteRef", () => {
9293
});
9394
});
9495

96+
describe("filterSelectableBranches", () => {
97+
it("shows only local branches in the branch selector", () => {
98+
const input: GitBranch[] = [
99+
{
100+
name: "main",
101+
current: true,
102+
isDefault: true,
103+
worktreePath: null,
104+
},
105+
{
106+
name: "feature/demo",
107+
current: false,
108+
isDefault: false,
109+
worktreePath: null,
110+
},
111+
{
112+
name: "origin/feature/demo",
113+
isRemote: true,
114+
remoteName: "origin",
115+
current: false,
116+
isDefault: false,
117+
worktreePath: null,
118+
},
119+
{
120+
name: "jason/feature/old-fork",
121+
isRemote: true,
122+
remoteName: "jason",
123+
current: false,
124+
isDefault: false,
125+
worktreePath: null,
126+
},
127+
];
128+
129+
expect(filterSelectableBranches(input).map((branch) => branch.name)).toEqual([
130+
"main",
131+
"feature/demo",
132+
]);
133+
});
134+
});
135+
95136
describe("dedupeRemoteBranchesWithLocalMatches", () => {
96137
it("hides remote refs when the matching local branch exists", () => {
97138
const input: GitBranch[] = [
@@ -149,7 +190,7 @@ describe("dedupeRemoteBranchesWithLocalMatches", () => {
149190
]);
150191
});
151192

152-
it("keeps non-origin remote refs visible even when a matching local branch exists", () => {
193+
it("hides non-origin remote refs too when a matching local branch exists", () => {
153194
const input: GitBranch[] = [
154195
{
155196
name: "feature/demo",
@@ -169,11 +210,43 @@ describe("dedupeRemoteBranchesWithLocalMatches", () => {
169210

170211
expect(dedupeRemoteBranchesWithLocalMatches(input).map((branch) => branch.name)).toEqual([
171212
"feature/demo",
172-
"my-org/upstream/feature/demo",
173213
]);
174214
});
175215

176-
it("keeps non-origin remote refs visible when git tracks with first-slash local naming", () => {
216+
it("hides non-preferred remote refs when a preferred remote is configured", () => {
217+
const input: GitBranch[] = [
218+
{
219+
name: "feature/demo",
220+
current: false,
221+
isDefault: false,
222+
worktreePath: null,
223+
},
224+
{
225+
name: "origin/feature/remote-only",
226+
isRemote: true,
227+
remoteName: "origin",
228+
current: false,
229+
isDefault: false,
230+
worktreePath: null,
231+
},
232+
{
233+
name: "my-org/upstream/feature/demo",
234+
isRemote: true,
235+
remoteName: "my-org/upstream",
236+
current: false,
237+
isDefault: false,
238+
worktreePath: null,
239+
},
240+
];
241+
242+
expect(
243+
dedupeRemoteBranchesWithLocalMatches(input, { preferredRemoteName: "origin" }).map(
244+
(branch) => branch.name,
245+
),
246+
).toEqual(["feature/demo", "origin/feature/remote-only"]);
247+
});
248+
249+
it("hides preferred-remote refs too when a matching local branch exists", () => {
177250
const input: GitBranch[] = [
178251
{
179252
name: "upstream/feature",
@@ -191,10 +264,11 @@ describe("dedupeRemoteBranchesWithLocalMatches", () => {
191264
},
192265
];
193266

194-
expect(dedupeRemoteBranchesWithLocalMatches(input).map((branch) => branch.name)).toEqual([
195-
"upstream/feature",
196-
"my-org/upstream/feature",
197-
]);
267+
expect(
268+
dedupeRemoteBranchesWithLocalMatches(input, {
269+
preferredRemoteName: "my-org/upstream",
270+
}).map((branch) => branch.name),
271+
).toEqual(["upstream/feature"]);
198272
});
199273
});
200274

apps/web/src/components/BranchToolbar.logic.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,28 @@ function deriveLocalBranchNameCandidatesFromRemoteRef(
7171
return [...candidates];
7272
}
7373

74+
export function filterSelectableBranches(branches: ReadonlyArray<GitBranch>): ReadonlyArray<GitBranch> {
75+
return branches.filter((branch) => !branch.isRemote);
76+
}
77+
7478
export function dedupeRemoteBranchesWithLocalMatches(
7579
branches: ReadonlyArray<GitBranch>,
80+
options?: {
81+
preferredRemoteName?: string | null;
82+
},
7683
): ReadonlyArray<GitBranch> {
7784
const localBranchNames = new Set(
7885
branches.filter((branch) => !branch.isRemote).map((branch) => branch.name),
7986
);
87+
const preferredRemoteName = options?.preferredRemoteName?.trim() || null;
8088

8189
return branches.filter((branch) => {
8290
if (!branch.isRemote) {
8391
return true;
8492
}
8593

86-
if (branch.remoteName !== "origin") {
87-
return true;
94+
if (preferredRemoteName && branch.remoteName !== preferredRemoteName) {
95+
return false;
8896
}
8997

9098
const localBranchCandidates = deriveLocalBranchNameCandidatesFromRemoteRef(

apps/web/src/components/BranchToolbarBranchSelector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import {
2323
import { readNativeApi } from "../nativeApi";
2424
import { parsePullRequestReference } from "../pullRequestReference";
2525
import {
26-
dedupeRemoteBranchesWithLocalMatches,
2726
deriveLocalBranchNameFromRemoteRef,
2827
EnvMode,
28+
filterSelectableBranches,
2929
resolveBranchSelectionTarget,
3030
resolveBranchToolbarValue,
3131
} from "./BranchToolbar.logic";
@@ -91,7 +91,7 @@ export function BranchToolbarBranchSelector({
9191
const branchesQuery = useQuery(gitBranchesQueryOptions(branchCwd));
9292
const branchStatusQuery = useQuery(gitStatusQueryOptions(branchCwd));
9393
const branches = useMemo(
94-
() => dedupeRemoteBranchesWithLocalMatches(branchesQuery.data?.branches ?? []),
94+
() => filterSelectableBranches(branchesQuery.data?.branches ?? []),
9595
[branchesQuery.data?.branches],
9696
);
9797
const currentGitBranch =

0 commit comments

Comments
 (0)