Skip to content

Commit ec960b3

Browse files
daklclaude
andauthored
feat: add product website and automated screenshot capture (#18)
Astro static site at website/ deployed to dakl.github.io/codez via GitHub Pages. Includes hero, features grid, theme showcase, download section, and an automated Playwright script to capture app screenshots. Also centers the empty state text relative to the full window rather than just the main content area. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8f6d525 commit ec960b3

27 files changed

Lines changed: 6603 additions & 1 deletion
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Deploy Website
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths: ["website/**"]
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: pages
16+
cancel-in-progress: true
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- uses: actions/setup-node@v4
25+
with:
26+
node-version: 20
27+
cache: npm
28+
cache-dependency-path: website/package-lock.json
29+
30+
- name: Install dependencies
31+
working-directory: website
32+
run: npm ci
33+
34+
- name: Build
35+
working-directory: website
36+
run: npm run build
37+
38+
- uses: actions/upload-pages-artifact@v3
39+
with:
40+
path: website/dist
41+
42+
deploy:
43+
needs: build
44+
runs-on: ubuntu-latest
45+
environment:
46+
name: github-pages
47+
url: ${{ steps.deployment.outputs.page_url }}
48+
steps:
49+
- id: deployment
50+
uses: actions/deploy-pages@v4

e2e/screenshots.spec.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* Playwright script to capture Codez app screenshots for the website.
3+
*
4+
* Usage:
5+
* npm run screenshots
6+
*
7+
* Output:
8+
* website/public/screenshots/app.png
9+
*/
10+
import { execSync } from "node:child_process";
11+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
12+
import { tmpdir } from "node:os";
13+
import path from "node:path";
14+
import {
15+
_electron as electron,
16+
type ElectronApplication,
17+
type Page,
18+
} from "@playwright/test";
19+
import { test } from "@playwright/test";
20+
21+
const OUTPUT_DIR = path.join(__dirname, "..", "website", "public", "screenshots");
22+
const WINDOW_WIDTH = 1200;
23+
const WINDOW_HEIGHT = 800;
24+
25+
function createGitRepo(dirName: string): string {
26+
const sandboxDir = mkdtempSync(path.join(tmpdir(), "codez-ss-"));
27+
const repoDir = path.join(sandboxDir, dirName);
28+
mkdirSync(repoDir);
29+
30+
execSync(
31+
[
32+
"git init",
33+
"git checkout -b main",
34+
'git config user.email "dk@example.com"',
35+
'git config user.name "Daniel Klevebring"',
36+
`echo "# ${dirName}" > README.md`,
37+
"git add .",
38+
'git commit -m "Initial commit"',
39+
"echo 'src/' >> .gitignore",
40+
"git add .",
41+
'git commit -m "Add gitignore"',
42+
].join(" && "),
43+
{ cwd: repoDir, stdio: "ignore" },
44+
);
45+
46+
// Pre-trust the folder so Claude skips the trust prompt
47+
const claudeDir = path.join(repoDir, ".claude");
48+
mkdirSync(claudeDir, { recursive: true });
49+
writeFileSync(
50+
path.join(claudeDir, "settings.json"),
51+
JSON.stringify({ permissions: { allow: ["*"] } }),
52+
);
53+
execSync("git add -A && git commit -m 'Add claude config'", {
54+
cwd: repoDir,
55+
stdio: "ignore",
56+
});
57+
58+
return repoDir;
59+
}
60+
61+
test("capture app screenshot", async () => {
62+
test.setTimeout(90_000);
63+
64+
mkdirSync(OUTPUT_DIR, { recursive: true });
65+
const dataDir = mkdtempSync(path.join(tmpdir(), "codez-screenshots-"));
66+
67+
console.log("Creating git repos...");
68+
const codezRepo = createGitRepo("codez");
69+
const papershelfRepo = createGitRepo("papershelf");
70+
console.log(` codez: ${codezRepo}`);
71+
console.log(` papershelf: ${papershelfRepo}`);
72+
73+
console.log("Launching Electron app...");
74+
const app = await electron.launch({
75+
args: [path.join(__dirname, "..", "dist", "main", "main", "index.js")],
76+
env: { ...process.env, E2E_TEST: "true", E2E_DATA_DIR: dataDir },
77+
});
78+
79+
const window = await app.firstWindow();
80+
console.log("Window ready");
81+
82+
// Resize to consistent dimensions
83+
await app.evaluate(
84+
({ BrowserWindow }, { width, height }) => {
85+
const win = BrowserWindow.getAllWindows()[0];
86+
win.setSize(width, height);
87+
win.center();
88+
},
89+
{ width: WINDOW_WIDTH, height: WINDOW_HEIGHT },
90+
);
91+
92+
// Add repos
93+
console.log("Adding repos...");
94+
await window.evaluate(
95+
async ([codez, papershelf]) => {
96+
const api = (window as any).electronAPI;
97+
await api.addRepo(codez);
98+
await api.addRepo(papershelf);
99+
},
100+
[codezRepo, papershelfRepo],
101+
);
102+
console.log("Repos added");
103+
104+
// Create sessions one at a time for better error visibility
105+
console.log("Creating session 1: codez on main...");
106+
await window.evaluate(
107+
async ([repo]) => {
108+
await (window as any).electronAPI.createSession(repo, "claude", "", "Parallel session manager");
109+
},
110+
[codezRepo],
111+
);
112+
console.log("Session 1 created");
113+
114+
console.log("Creating session 2: codez on custom-fonts...");
115+
await window.evaluate(
116+
async ([repo]) => {
117+
await (window as any).electronAPI.createSession(repo, "claude", "custom-fonts", "Custom font integration");
118+
},
119+
[codezRepo],
120+
);
121+
console.log("Session 2 created");
122+
123+
console.log("Creating session 3: papershelf on v3.0-dev...");
124+
await window.evaluate(
125+
async ([repo]) => {
126+
await (window as any).electronAPI.createSession(repo, "claude", "v3.0-dev", "Migrate to React 19");
127+
},
128+
[papershelfRepo],
129+
);
130+
console.log("Session 3 created");
131+
132+
// Set Sand theme before reload so it's applied on load
133+
console.log("Setting Sand theme...");
134+
await window.evaluate(async () => {
135+
await (window as any).electronAPI.saveSettings({ theme: "sand" });
136+
});
137+
138+
// Reload to pick up sessions + theme
139+
console.log("Reloading UI...");
140+
await window.evaluate(() => location.reload());
141+
await window.waitForTimeout(1500);
142+
143+
// Let the UI finish rendering (don't select a session — avoids leaking
144+
// terminal output with paths, version numbers, and personal info)
145+
await window.waitForTimeout(500);
146+
147+
const screenshotPath = path.join(OUTPUT_DIR, "app.png");
148+
await window.screenshot({ path: screenshotPath });
149+
console.log(`Saved: ${screenshotPath}`);
150+
151+
await app.close();
152+
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"test": "npm run rebuild:node && vitest run",
2020
"test:watch": "npm run rebuild:node && vitest",
2121
"test:e2e": "npm run rebuild:electron && npm run build && npx playwright test",
22+
"screenshots": "npm run rebuild:electron && npm run build && npx playwright test e2e/screenshots.spec.ts",
2223
"lint": "biome check src/",
2324
"lint:fix": "biome check --write src/",
2425
"format": "biome format --write src/",

src/renderer/components/SessionView/SessionView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ export function SessionView() {
6868
if (!activeSessionId || !session) {
6969
return (
7070
<div className="flex-1 flex items-center justify-center">
71-
<div className="text-center">
71+
{/* Offset left by half the sidebar width (w-60 = 240px) to center in the full window */}
72+
<div className="text-center -ml-60">
7273
<h1 className="text-2xl font-semibold text-text-primary mb-2">Codez</h1>
7374
<p className="text-sm text-text-muted">Press ⌘N to start a new session</p>
7475
</div>

website/astro.config.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineConfig } from "astro/config";
2+
3+
export default defineConfig({
4+
site: "https://dakl.github.io",
5+
base: "/codez",
6+
});

0 commit comments

Comments
 (0)