From 40e90f3ed2ea5a7c87a0a0f1c01fcc383becb998 Mon Sep 17 00:00:00 2001 From: Daniels-Main Date: Fri, 31 Jul 2026 20:49:08 +0200 Subject: [PATCH 1/3] Improve website SEO foundation --- .gitignore | 1 + README.md | 6 +- ROADMAP.md | 18 +- TASKS.md | 15 +- docs/microsoft-store-submission.md | 8 +- website/README.md | 44 +++-- website/build.mjs | 282 +++++++++++++++++++++++++++ website/docs/docs.js | 129 ------------- website/docs/index.html | 76 -------- website/docs/manifest.json | 26 +-- website/index.html | 295 ++++++++++++++++------------- website/package.json | 10 +- website/seo-check.mjs | 116 ++++++++++++ website/style.css | 2 +- 14 files changed, 639 insertions(+), 389 deletions(-) create mode 100644 website/build.mjs delete mode 100644 website/docs/docs.js delete mode 100644 website/docs/index.html create mode 100644 website/seo-check.mjs diff --git a/.gitignore b/.gitignore index af071f3..74c30f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ ui/dist/ ui/.vite/ **/*.tsbuildinfo ui/src/**/*.js +/website/dist/ # Editor / OS .DS_Store diff --git a/README.md b/README.md index 78130f6..1265669 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Strand -> A fast, keyboard-first Git client — built for reviewing what AI coding -> agents do to your code. +> A fast, keyboard-first Git client for Windows, macOS, and Linux — with +> first-class workflows for reviewing what AI coding agents do to your code. [![CI](https://github.com/danielss-dev/strand/actions/workflows/ci.yml/badge.svg)](https://github.com/danielss-dev/strand/actions/workflows/ci.yml) [![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](./LICENSE) @@ -299,7 +299,7 @@ strand/ │ └── strand-tauri/ # Tauri 2 app shell + IPC commands ├── packaging/ # Store/distribution manifests assembled around release binaries ├── ui/ # Vite + React + TypeScript frontend -├── website/ # strandgit.com: landing page + user guide (website/docs/, no build step) +├── website/ # strandgit.com: landing page + pre-rendered user guide ├── docs/ # design notes, perf baseline, packaging ├── PRD.md # product spec ├── ROADMAP.md # milestones and status diff --git a/ROADMAP.md b/ROADMAP.md index 7a1f290..1edb9c9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -718,7 +718,7 @@ doc → doc links stay in Preview either way. Verified: `vitest` (105, +17 markdown), `tsc`, `cargo check -p strand-tauri`. **Landing page (2026-06-12):** First pass at the public face, ahead of the -beta: `website/` holds a static, no-build-step landing page for +beta: `website/` initially held a static, no-build-step landing page for `strandgit.com`. It reuses the app's identity wholesale — the OKLCH token palette from `tokens.css`, self-hosted Geist + JetBrains Mono, the single-hue accent system (a row of hero dots rotates `--accent-h` live, @@ -1526,10 +1526,10 @@ store-level `reviewWorktree`); palette gained Clean up / Prune entries; the documentation: `website/docs/` holds nine markdown guide pages (overview, getting started, repositories & workspaces, reviewing agent changes, worktrees, everyday git, commits & history, keyboard & palette, settings), -each fact-checked against `ui/src` before landing, rendered client-side by a -static viewer (`docs/index.html` + vendored `marked.min.js` — the site keeps -its no-build-step invariant; updating docs = editing the `.md` and -redeploying). The landing page was synced to 0.9.x reality: Linux download +each fact-checked against `ui/src` before landing. Markdown remains the source +of truth; since 2026-07-31 a zero-dependency build pre-renders it through the +vendored `marked.min.js` into static clean-URL HTML. The landing page was synced +to 0.9.x reality: Linux download button live (AppImage), stale ~10 MB installer claims corrected to real 15/31 MB sizes, the worktrees card now covers Compare + Merge & clean up, new Workspaces and AI-commit-messages cards fill the feature grid, and Docs is @@ -2470,6 +2470,14 @@ pull-request chronology now places the latest commit, comment, or lifecycle event at the top while retaining deterministic tie ordering. Focused frontend tests cover merged and closed timelines. +**Website SEO foundation shipped (2026-07-31):** Strand's public site now +leads with its fast cross-platform Git-client category while keeping agent +review as the differentiator. The homepage exposes canonical, social, and +SoftwareApplication metadata; all 13 guide pages pre-render to crawlable clean +URLs; and the build emits a sitemap plus robots policy. A repository SEO check +fails on duplicate or missing metadata, invalid JSON-LD, broken routes, legacy +query/Markdown links, or a sitemap that drifts from the generated pages. + --- ## Cross-cutting tracks (run in parallel with all milestones) diff --git a/TASKS.md b/TASKS.md index b6ec568..0d51fb1 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2093,7 +2093,7 @@ quick-wins from that audit already landed (see ROADMAP changelog). legal, updater-rehearsal, clean-machine, and real-platform checks in `docs/release-checklist.md` remain open. - ☑ Landing page at `strandgit.com` + downloads + auto-update manifest - (site built: `website/` — static, no build step, design tokens + fonts lifted + (site built: `website/` — static output, design tokens + fonts lifted from the app, interactive app-replica demo + ⌘K palette, AGPL/honor-system pricing section. **Deployed on Railway** — project `landings`, service `strand-landing`, live at https://strandgit.com (custom domain DNS @@ -2109,9 +2109,9 @@ quick-wins from that audit already landed (see ROADMAP changelog). selection, repository/workspace Review scope, notes/baseline/file actions, and pointer + keyboard pane resizing; desktop/narrow browser QA is clean.) - ☑ User-guide docs on the website (2026-07-08: `website/docs/` — nine - fact-checked markdown pages + `manifest.json`, rendered client-side by - `docs/index.html`/`docs.js` with vendored `marked.min.js`; no build step — - updating docs = editing the `.md` files and redeploying. Landing page synced + fact-checked markdown pages + `manifest.json`; since 2026-07-31, `build.mjs` + pre-renders them with vendored `marked.min.js` into crawlable clean-URL HTML + under `website/dist/`, while Markdown remains the source of truth. Landing page synced to 0.9.x: Linux download button live, installer sizes corrected, worktree Compare / Merge & clean up + Workspaces + AI commit messages cards, Docs link in nav/footer/⌘K.) @@ -2119,6 +2119,13 @@ quick-wins from that audit already landed (see ROADMAP changelog). 2026-07-18 across all nine guide pages, native-menu language, privacy/status copy, landing release labels, website maintainer notes, and `docs/changelog/2026-07-18-strand-1.0.0.md`. +- ☑ Website SEO foundation (2026-07-31: general-first Git-client homepage + title/H1/copy and section order; canonical/Open Graph/SoftwareApplication + metadata; 13 pre-rendered guide routes; generated `robots.txt` and + `sitemap.xml`; `seo-check.mjs` validates metadata, JSON-LD, routes, and links) +- ☐ Deploy the SEO build, permanently redirect `www.strandgit.com` to the apex + domain, verify the domain in Google Search Console, import it into Bing + Webmaster Tools, and submit `https://strandgit.com/sitemap.xml`. --- diff --git a/docs/microsoft-store-submission.md b/docs/microsoft-store-submission.md index c2e9eb7..4d0d8f8 100644 --- a/docs/microsoft-store-submission.md +++ b/docs/microsoft-store-submission.md @@ -32,8 +32,8 @@ automation. - Complete owner/counsel review of the open Strand trademark gate and approve the factual privacy notice and user-content guidelines at - `https://strandgit.com/docs/?page=privacy` and - `https://strandgit.com/docs/?page=content-guidelines`. + `https://strandgit.com/docs/privacy/` and + `https://strandgit.com/docs/content-guidelines/`. - Do not obtain or upload a Windows publisher certificate for this route. Partner Center signs the accepted MSIX during certification. @@ -166,11 +166,11 @@ installer-specific step does not apply to the MSIX route. repository content and connected Git-host data, and an optional user-reviewed crash report can contain repository paths. - Privacy policy: - `https://strandgit.com/docs/?page=privacy` + `https://strandgit.com/docs/privacy/` - Website: `https://strandgit.com` - Support: `https://github.com/danielss-dev/strand/issues` - User-generated content guidelines: - `https://strandgit.com/docs/?page=content-guidelines` + `https://strandgit.com/docs/content-guidelines/` - Non-Microsoft drivers or NT services: **No** - Tested to meet accessibility guidelines: **Do not claim this yet.** Strand is keyboard-operable, but no external conformance audit is recorded. diff --git a/website/README.md b/website/README.md index d20e7c5..a6ca58f 100644 --- a/website/README.md +++ b/website/README.md @@ -1,16 +1,19 @@ # Strand landing page -Static site for `strandgit.com`. No build step. +Static site for `strandgit.com`. A zero-dependency Node build pre-renders the +Markdown user guide into crawlable HTML and writes the deployable site to +`dist/`. **Deployed on Railway**: project `landings` → service `strand-landing` -(`railway up` from this folder redeploys; `npm start` is what Railway runs — -`serve . -l $PORT`; `npm run build` is a no-op that exists only because the -Railway image build wants a build command). Test URL: +(`railway up` from this folder redeploys; Railway runs `npm run build` and then +`npm start`, which serves `dist/` on `$PORT`). Test URL: . The custom domain `strandgit.com` is live through the Railway service. -Preview locally with `pnpm site` from the repo root (serves on - via [`serve`](https://github.com/vercel/serve)). +Preview locally with `pnpm site` from the repo root (builds and serves on + via [`serve`](https://github.com/vercel/serve)). Run +`npm test` from this folder to build and validate titles, descriptions, +canonicals, JSON-LD, the sitemap, robots policy, and internal links. - `index.html` / `style.css` / `script.js` — the whole site. Design tokens are lifted from the app (`ui/src/styles/tokens.css`): same warm-charcoal OKLCH @@ -35,23 +38,30 @@ Preview locally with `pnpm site` from the repo root (serves on fuzzy / highlighted UI): it scrolls to page sections, switches the demo views, sets the accent, and opens GitHub / X. Items live in `ITEMS` in `script.js` — add new destinations there. -- `docs/` — the user guide, served at `/docs/`. Content is plain markdown - (`*.md`, one file per page) rendered client-side by `docs/index.html` + - `docs/docs.js` using the vendored `docs/marked.min.js` (still no build - step). **To update a page, edit its `.md` and redeploy. To add a page, - drop the `.md` in `docs/` and add a row to `docs/manifest.json`** (order - there drives the sidebar and prev/next pager). Cross-page links are plain - relative `foo.md` links — the viewer rewrites them (and they render on - GitHub too). Keep the guide in sync with app releases: every claim in it +- `docs/` — the user-guide source, served as pre-rendered pages at `/docs/` + and `/docs//`. Content is plain markdown (`*.md`, one file per page), + converted at build time by `build.mjs` with the vendored + `docs/marked.min.js`. **To update a page, edit its `.md` and redeploy. To add + a page, drop the `.md` in `docs/` and add a title + unique description row + to `docs/manifest.json`** (order drives the sidebar, sitemap, and prev/next + pager). Cross-page links stay as relative `foo.md` links in source so they + render on GitHub; the build rewrites them to canonical clean URLs. Keep the + guide in sync with app releases: every claim in it was fact-checked against `ui/src` on 2026-07-18 for the 1.0 release candidate. The public privacy policy used by distribution listings is - `/docs/?page=privacy`, and Store UGC guidance is - `/docs/?page=content-guidelines`; deploy website changes before submitting - either Store URL. + `/docs/privacy/`, and Store UGC guidance is `/docs/content-guidelines/`. + Legacy `?page=` URLs redirect to their clean equivalents; deploy website + changes before submitting either Store URL. ## Before launch - [x] Custom-domain DNS and TLS are live at `strandgit.com`. +- [ ] Redirect `www.strandgit.com` permanently to `https://strandgit.com/` + in the Railway/DNS control plane (the stale `www` record currently lands + on an unrelated 404 host). +- [ ] Verify the apex domain in Google Search Console, import it into Bing + Webmaster Tools, and submit `https://strandgit.com/sitemap.xml` after + this SEO build is deployed. - [x] Download CTAs resolve the latest platform assets through the GitHub Releases API, with the release page as the failure fallback. - [ ] Point "Get a commercial license" at `COMMERCIAL.md` / a purchase flow diff --git a/website/build.mjs b/website/build.mjs new file mode 100644 index 0000000..0f40d9d --- /dev/null +++ b/website/build.mjs @@ -0,0 +1,282 @@ +import { createRequire } from 'node:module'; +import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const require = createRequire(import.meta.url); +const { marked } = require('./docs/marked.min.js'); +const root = path.dirname(fileURLToPath(import.meta.url)); +const docsRoot = path.join(root, 'docs'); +const outRoot = path.join(root, 'dist'); +const origin = 'https://strandgit.com'; + +const manifest = JSON.parse(await readFile(path.join(docsRoot, 'manifest.json'), 'utf8')); +const pages = manifest.pages; +const slugs = new Set(); + +if (!Array.isArray(pages) || pages.length === 0) { + throw new Error('docs/manifest.json must contain at least one page'); +} + +for (const page of pages) { + if (!page.file || !page.title || !page.description) { + throw new Error(`Incomplete documentation metadata: ${JSON.stringify(page)}`); + } + if (!/^[a-z0-9-]+$/.test(page.file)) { + throw new Error(`Invalid documentation slug: ${page.file}`); + } + if (slugs.has(page.file)) { + throw new Error(`Duplicate documentation slug: ${page.file}`); + } + slugs.add(page.file); +} + +if (!slugs.has('index')) { + throw new Error('The documentation manifest must contain the index page'); +} + +await rm(outRoot, { recursive: true, force: true }); +await mkdir(outRoot, { recursive: true }); + +for (const file of ['index.html', 'style.css', 'script.js', 'favicon.svg', 'favicon.png', 'og-image.svg', 'og-image.png']) { + await cp(path.join(root, file), path.join(outRoot, file)); +} +await cp(path.join(root, 'fonts'), path.join(outRoot, 'fonts'), { recursive: true }); +await mkdir(path.join(outRoot, 'docs'), { recursive: true }); +await cp(path.join(docsRoot, 'docs.css'), path.join(outRoot, 'docs', 'docs.css')); + +const pageUrl = (slug) => slug === 'index' ? '/docs/' : `/docs/${slug}/`; +const absolutePageUrl = (slug) => `${origin}${pageUrl(slug)}`; + +for (let index = 0; index < pages.length; index += 1) { + const page = pages[index]; + const markdownPath = path.join(docsRoot, `${page.file}.md`); + let markdown; + try { + markdown = await readFile(markdownPath, 'utf8'); + } catch { + throw new Error(`Missing documentation source: docs/${page.file}.md`); + } + + markdown = rewriteMarkdownLinks(markdown, page.file); + const rendered = renderArticle(markdown); + const html = renderDocument(page, rendered, index); + const outputDir = page.file === 'index' + ? path.join(outRoot, 'docs') + : path.join(outRoot, 'docs', page.file); + await mkdir(outputDir, { recursive: true }); + await writeFile(path.join(outputDir, 'index.html'), html, 'utf8'); +} + +await writeFile( + path.join(outRoot, 'robots.txt'), + `User-agent: *\nAllow: /\n\nSitemap: ${origin}/sitemap.xml\n`, + 'utf8', +); + +const sitemapUrls = [`${origin}/`, ...pages.map((page) => absolutePageUrl(page.file))]; +await writeFile( + path.join(outRoot, 'sitemap.xml'), + `\n\n${sitemapUrls.map((url) => ` ${escapeXml(url)}`).join('\n')}\n\n`, + 'utf8', +); + +console.log(`Built Strand website with ${pages.length} static documentation pages.`); + +function rewriteMarkdownLinks(markdown, sourceSlug) { + return markdown.replace(/\]\(([a-z0-9-]+)\.md(#[^)]+)?\)/g, (match, target, hash = '') => { + if (!slugs.has(target)) { + throw new Error(`Broken documentation link in ${sourceSlug}.md: ${target}.md`); + } + return `](${pageUrl(target)}${hash})`; + }); +} + +function renderArticle(markdown) { + let article = marked.parse(markdown, { gfm: true, breaks: false }); + const usedIds = new Set(); + const toc = []; + + article = article.replace(/([\s\S]*?)<\/h\1>/g, (match, level, inner) => { + const label = decodeEntities(stripTags(inner)).trim(); + let id = slugify(label); + while (usedIds.has(id)) id += '-x'; + usedIds.add(id); + toc.push({ id, label, level: Number(level) }); + return `${inner}#`; + }); + + article = article.replace(//g, '
'); + article = article.replace(/<\/table>/g, '
'); + article = article.replace(/ { + const current = entry.file === page.file; + const label = entry.file === 'index' ? 'Overview' : entry.title; + return `${escapeHtml(label)}`; + }).join('\n '); + const toc = rendered.toc.length > 1 + ? `` + : ''; + const pager = renderPager(index); + const legacyRedirect = page.file === 'index' ? renderLegacyRedirect() : ''; + + return ` + + + + + ${escapeHtml(title)} + + + + + + + + + + + + + + + + + + + + + + + + + ${legacyRedirect} + + + + +
+
+ Guide contents + +
+ + + +
+${indent(rendered.article.trim(), 6)} +${indent(pager, 6)} +
+ + ${toc} +
+ +
+ +
+ + +`; +} + +function renderPager(index) { + const links = []; + const previous = pages[index - 1]; + const next = pages[index + 1]; + if (previous) links.push(pagerLink(previous, 'Previous', 'pager-prev')); + if (next) links.push(pagerLink(next, 'Next', 'pager-next')); + return links.length ? `
${links.join('')}
` : ''; +} + +function pagerLink(page, direction, className) { + const title = page.file === 'index' ? 'Strand User Guide' : page.title; + return `${direction}${escapeHtml(title)}`; +} + +function renderLegacyRedirect() { + const routes = JSON.stringify([...slugs].filter((slug) => slug !== 'index')); + return ``; +} + +function slugify(value) { + return value.toLowerCase().replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-'); +} + +function stripTags(value) { + return value.replace(/<[^>]*>/g, ''); +} + +function decodeEntities(value) { + return value + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'"); +} + +function escapeHtml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function escapeAttribute(value) { + return escapeHtml(value); +} + +function escapeXml(value) { + return escapeHtml(value); +} + +function indent(value, spaces) { + const prefix = ' '.repeat(spaces); + return value.split('\n').map((line) => prefix + line).join('\n'); +} diff --git a/website/docs/docs.js b/website/docs/docs.js deleted file mode 100644 index 2a75473..0000000 --- a/website/docs/docs.js +++ /dev/null @@ -1,129 +0,0 @@ -/* Strand docs viewer — fetches manifest.json + .md, renders with the - vendored marked.min.js. Static, no build step: adding a page = drop the - .md in this folder and add a row to manifest.json. */ -(function () { - 'use strict'; - - var article = document.getElementById('docs-article'); - var tocWrap = document.getElementById('docs-toc'); - var tocList = document.getElementById('docs-toc-list'); - - function slugify(text) { - return text.toLowerCase().replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-'); - } - - function fail(msg) { - article.innerHTML = '

Not found

' + msg + - ' Back to the guide index.

'; - } - - fetch('manifest.json') - .then(function (r) { return r.json(); }) - .then(function (manifest) { - var pages = manifest.pages; - var param = new URLSearchParams(location.search).get('page') || 'index'; - var slug = /^[a-z0-9-]+$/.test(param) ? param : 'index'; - var idx = pages.findIndex(function (p) { return p.file === slug; }); - if (idx === -1) { buildNav(pages, 'index'); fail('That page isn’t in the guide.'); return; } - var page = pages[idx]; - - buildNav(pages, slug); - document.title = (slug === 'index' ? 'Strand Docs' : page.title + ' — Strand Docs'); - - fetch(page.file + '.md') - .then(function (r) { if (!r.ok) throw new Error(r.status); return r.text(); }) - .then(function (md) { render(md, pages, idx); }) - .catch(function () { fail('This page failed to load.'); }); - }) - .catch(function () { fail('The guide manifest failed to load.'); }); - - function buildNav(pages, current) { - ['docs-nav', 'docs-nav-mobile'].forEach(function (id) { - var nav = document.getElementById(id); - if (!nav) return; - nav.innerHTML = ''; - pages.forEach(function (p) { - var a = document.createElement('a'); - a.href = p.file === 'index' ? './' : '?page=' + p.file; - a.textContent = p.file === 'index' ? 'Overview' : p.title; - if (p.file === current) { a.className = 'on'; a.setAttribute('aria-current', 'page'); } - nav.appendChild(a); - }); - }); - } - - function render(md, pages, idx) { - article.innerHTML = marked.parse(md, { gfm: true, breaks: false }); - - /* Rewrite doc-to-doc links (foo.md, foo.md#bar) onto the viewer. */ - article.querySelectorAll('a[href]').forEach(function (a) { - var href = a.getAttribute('href'); - var m = href.match(/^([a-z0-9-]+)\.md(#.*)?$/); - if (m) { - a.setAttribute('href', (m[1] === 'index' ? './' : '?page=' + m[1]) + (m[2] || '')); - } else if (/^https?:\/\//.test(href)) { - a.setAttribute('target', '_blank'); - a.setAttribute('rel', 'noopener'); - } - }); - - /* Heading anchors + on-this-page rail. */ - var used = {}; - var entries = []; - article.querySelectorAll('h2, h3').forEach(function (h) { - var id = slugify(h.textContent); - while (used[id]) id += '-x'; - used[id] = true; - h.id = id; - var link = document.createElement('a'); - link.className = 'hlink'; - link.href = '#' + id; - link.textContent = '#'; - link.setAttribute('aria-label', 'Link to ' + h.textContent); - h.appendChild(link); - entries.push({ id: id, text: h.firstChild.textContent || h.textContent.replace(/#$/, ''), lv: h.tagName === 'H3' ? 3 : 2 }); - }); - if (entries.length > 1 && tocWrap) { - tocList.innerHTML = ''; - entries.forEach(function (e) { - var a = document.createElement('a'); - a.href = '#' + e.id; - a.textContent = e.text; - if (e.lv === 3) a.className = 'lv3'; - tocList.appendChild(a); - }); - tocWrap.hidden = false; - } - - /* Horizontal scroll containers for wide tables. */ - article.querySelectorAll('table').forEach(function (t) { - var wrap = document.createElement('div'); - wrap.className = 'table-scroll'; - t.parentNode.insertBefore(wrap, t); - wrap.appendChild(t); - }); - - /* Prev / next pager from manifest order. */ - var pager = document.createElement('div'); - pager.className = 'docs-pager'; - var prev = pages[idx - 1]; - var next = pages[idx + 1]; - if (prev) pager.appendChild(pagerLink(prev, 'Previous', 'pager-prev')); - if (next) pager.appendChild(pagerLink(next, 'Next', 'pager-next')); - if (prev || next) article.appendChild(pager); - - if (location.hash) { - var target = document.getElementById(location.hash.slice(1)); - if (target) target.scrollIntoView(); - } - } - - function pagerLink(p, dir, cls) { - var a = document.createElement('a'); - a.className = cls; - a.href = p.file === 'index' ? './' : '?page=' + p.file; - a.innerHTML = '' + dir + ''; - a.querySelector('.pager-title').textContent = p.file === 'index' ? 'Strand User Guide' : p.title; - return a; - } -})(); diff --git a/website/docs/index.html b/website/docs/index.html deleted file mode 100644 index a6ead87..0000000 --- a/website/docs/index.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - Strand Docs - - - - - - - - - - - - - - - -
-
- Guide contents - -
- - - -
-

Loading…

-
- - -
- -
- -
- - - - - diff --git a/website/docs/manifest.json b/website/docs/manifest.json index df7df0e..4e680a4 100644 --- a/website/docs/manifest.json +++ b/website/docs/manifest.json @@ -1,17 +1,17 @@ { "pages": [ - { "file": "index", "title": "Strand User Guide" }, - { "file": "getting-started", "title": "Getting Started" }, - { "file": "work", "title": "Work: Files & Terminals" }, - { "file": "repositories-and-workspaces", "title": "Repositories & Workspaces" }, - { "file": "reviewing-agent-changes", "title": "Reviewing Agent Changes" }, - { "file": "pull-requests", "title": "Pull Requests" }, - { "file": "worktrees", "title": "Worktrees" }, - { "file": "everyday-git", "title": "Everyday Git" }, - { "file": "commits-and-history", "title": "Commits & History" }, - { "file": "keyboard-and-palette", "title": "Keyboard & Command Palette" }, - { "file": "settings", "title": "Settings" }, - { "file": "privacy", "title": "Privacy Policy" }, - { "file": "content-guidelines", "title": "Content Guidelines" } + { "file": "index", "title": "Strand User Guide", "description": "Learn how to use Strand, the fast, keyboard-first Git client for Windows, macOS, and Linux." }, + { "file": "getting-started", "title": "Getting Started", "description": "Download and install the Strand Git client on Windows, macOS, or Linux, then open your first repository." }, + { "file": "work", "title": "Work: Files & Terminals", "description": "Use Strand as a Git client with an integrated file browser, lightweight editor, previews, and repository terminals." }, + { "file": "repositories-and-workspaces", "title": "Repositories & Workspaces", "description": "Manage multiple Git repositories, tabs, recent projects, and cross-repository workspaces in Strand." }, + { "file": "reviewing-agent-changes", "title": "Reviewing Agent Changes", "description": "Review AI coding-agent changes with whole-file diffs, baselines, notes, and reusable feedback in Strand." }, + { "file": "pull-requests", "title": "Pull Requests", "description": "Browse and review GitHub and Azure DevOps pull requests from the Strand desktop Git client." }, + { "file": "worktrees", "title": "Worktrees", "description": "Use Strand's Git worktree GUI to isolate agent tasks, compare attempts, review changes, and merge the winner." }, + { "file": "everyday-git", "title": "Everyday Git", "description": "Stage files and lines, commit, branch, merge, rebase, resolve conflicts, and synchronize repositories visually." }, + { "file": "commits-and-history", "title": "Commits & History", "description": "Explore your Git commit graph, search history, inspect diffs, rebase commits, use reflog, and blame files in Strand." }, + { "file": "keyboard-and-palette", "title": "Keyboard & Command Palette", "description": "Drive the Strand Git client from the keyboard with configurable shortcuts, focused navigation, and a command palette." }, + { "file": "settings", "title": "Settings", "description": "Configure Strand themes, diffs, Git identity, shortcuts, integrations, AI providers, updates, and privacy." }, + { "file": "privacy", "title": "Privacy Policy", "description": "Read how the local-first Strand Git client handles repositories, credentials, AI features, updates, and website data." }, + { "file": "content-guidelines", "title": "Content Guidelines", "description": "Read the content, reporting, and acceptable-use guidelines for Strand's Git hosting integrations and AI-assisted features." } ] } diff --git a/website/index.html b/website/index.html index ca14412..d17ce07 100644 --- a/website/index.html +++ b/website/index.html @@ -3,19 +3,23 @@ - Strand — Review what your AI agent did - - - + Strand Git Client — Fast Git GUI for Windows, macOS & Linux + + + + + - + + + - - + + @@ -24,6 +28,31 @@ + @@ -70,12 +99,12 @@
-

A Git client for the agent era

-

Agents write the code.
Someone has to review it.

+

Git client for Windows, macOS & Linux

+

The fast Git client
for modern development.

- Strand is a fast, keyboard-first Git client built around one workflow: watch an AI coding - agent change your working tree, review every line with whole-file context, and send - feedback straight back to the agent. Free for individuals. + Strand is a fast, keyboard-first Git GUI for macOS, Windows, and Linux. Review diffs, + stage changes, manage branches and worktrees, resolve conflicts, and inspect AI-agent + changes with whole-file context. Free for individuals.

Download Strand @@ -441,99 +470,10 @@

Agents write the code.
Someone has to review it.

- -
-
-
-

01 — The loop

-

Built around the agent loop, not bolted onto it.

-

- Claude Code in one pane, Strand in the other. Most Git clients were designed for changes - you made and already understand. Strand assumes the opposite: the diff is new to - you, it's big, and it keeps moving. -

- -
- -
    -
  1. -

    Watch it happen

    -

    A native file watcher streams every change into Strand as the agent works — no refresh - button, no window-focus dance. Running agents in parallel? Worktrees get grouped tabs - and an overview dashboard: branch, dirty count, ahead/behind, one click to review.

    - repo://changed · debounced 400ms -
  2. -
  3. -

    Pin a baseline

    -

    “Review everything since this commit.” Committed, staged, and unstaged agent work since - the fork point shows as one diff — agents commit as they go, and your review shouldn't - care. Per-worktree sessions pin themselves at the merge-base automatically.

    - since 4f2c91d · committed + staged + unstaged -
  4. -
  5. -

    Review with whole-file context

    -

    Every changed file renders in its entirety with the edits inline — you judge changes - in context, not through a 3-line keyhole. j/k - through the queue, space to mark reviewed. If the agent - touches a reviewed file again, it flips back to unreviewed on its own.

    - highlighting off the main thread · 60fps on lockfiles -
  6. -
  7. -

    Send feedback back

    -

    Drop notes on lines as you read. “Copy feedback” builds one markdown prompt — quoted - diff excerpts included — ready to paste into the agent. Stage what's good; discard the - rest behind an automatic safety stash with a restore window.

    - ✎ Copy feedback (3) → paste into your agent -
  8. -
-
-
- - -
-
-
-

git blame says it was you

-

The commits carry
your signature.

-

- The agent writes the code, but it ships under your name — your sign-off, your repo, - your pager at 2 a.m. A Co-Authored-By trailer - won't save you in the postmortem. Read it before it merges. -

-

Don't ship shit to prod.

-
-
-
-
commit 9c4e7a14b2f08d3
-
Author: you <you@acme.dev>← signed by you
-
Date: Fri Jun 12 16:32:08 2026
-
 
-
feat(auth): retry transient failures ai
-
 
-
Co-Authored-By: Claude <noreply@anthropic.com>← written by the agent
-
-

Strand tags agent commits in the graph — the ai chip — and - “select commits since baseline” rounds up the whole session for review.

-
-
-
-
-

02 — Performance

+

01 — Performance

Fast is the feature.

Strand's prime directive — written into the repo, enforced in review: stable, fast, @@ -566,37 +506,10 @@

Fast is the feature.

- -
-
-
-

03 — Keyboard

-

Hands on home row.

-

- Keyboard-first, never keyboard-only. Every surface in Strand has a focus model — lists, - trees, dialogs, menus — so reviewing an agent session never makes you reach for the - mouse. And when you forget a key, ⌘K finds everything: - branches, files, commits, actions, fuzzy-matched. -

-

Menus open with ⇧F10, dialogs trap focus, screen readers get announced toasts. Accessibility isn't a mode.

-
-
    -
  • jkstep through the review queue
  • -
  • spacemark reviewed — and stay put
  • -
  • npjump between change blocks
  • -
  • sstage the file
  • -
  • dddiscard — safety-stashed first
  • -
  • mleave a note on the line
  • -
  • cjump to the commit form
  • -
  • Keverything else, fuzzy
  • -
-
-
-
-

04 — Everything else

+

02 — Complete Git client

A whole Git client.
Not just a review pane.

The review workflow sits on top of a complete, fast Git client — so accepting the agent's @@ -657,6 +570,122 @@

AI commit messages

+ +
+
+
+

03 — Keyboard

+

Hands on home row.

+

+ Keyboard-first, never keyboard-only. Every surface in Strand has a focus model — lists, + trees, dialogs, menus — so reviewing an agent session never makes you reach for the + mouse. And when you forget a key, ⌘K finds everything: + branches, files, commits, actions, fuzzy-matched. +

+

Menus open with ⇧F10, dialogs trap focus, screen readers get announced toasts. Accessibility isn't a mode.

+
+
    +
  • jkstep through the review queue
  • +
  • spacemark reviewed — and stay put
  • +
  • npjump between change blocks
  • +
  • sstage the file
  • +
  • dddiscard — safety-stashed first
  • +
  • mleave a note on the line
  • +
  • cjump to the commit form
  • +
  • Keverything else, fuzzy
  • +
+
+
+ + +
+
+
+

04 — Agent review

+

Built around the agent loop, not bolted onto it.

+

+ Claude Code in one pane, Strand in the other. Most Git clients were designed for changes + you made and already understand. Strand assumes the opposite: the diff is new to + you, it's big, and it keeps moving. +

+ +
+ +
    +
  1. +

    Watch it happen

    +

    A native file watcher streams every change into Strand as the agent works — no refresh + button, no window-focus dance. Running agents in parallel? Worktrees get grouped tabs + and an overview dashboard: branch, dirty count, ahead/behind, one click to review.

    + repo://changed · debounced 400ms +
  2. +
  3. +

    Pin a baseline

    +

    “Review everything since this commit.” Committed, staged, and unstaged agent work since + the fork point shows as one diff — agents commit as they go, and your review shouldn't + care. Per-worktree sessions pin themselves at the merge-base automatically.

    + since 4f2c91d · committed + staged + unstaged +
  4. +
  5. +

    Review with whole-file context

    +

    Every changed file renders in its entirety with the edits inline — you judge changes + in context, not through a 3-line keyhole. j/k + through the queue, space to mark reviewed. If the agent + touches a reviewed file again, it flips back to unreviewed on its own.

    + highlighting off the main thread · 60fps on lockfiles +
  6. +
  7. +

    Send feedback back

    +

    Drop notes on lines as you read. “Copy feedback” builds one markdown prompt — quoted + diff excerpts included — ready to paste into the agent. Stage what's good; discard the + rest behind an automatic safety stash with a restore window.

    + ✎ Copy feedback (3) → paste into your agent +
  8. +
+
+
+ + +
+
+
+

git blame says it was you

+

The commits carry
your signature.

+

+ The agent writes the code, but it ships under your name — your sign-off, your repo, + your pager at 2 a.m. A Co-Authored-By trailer + won't save you in the postmortem. Read it before it merges. +

+

Don't ship shit to prod.

+
+
+
+
commit 9c4e7a14b2f08d3
+
Author: you <you@acme.dev>← signed by you
+
Date: Fri Jun 12 16:32:08 2026
+
 
+
feat(auth): retry transient failures ai
+
 
+
Co-Authored-By: Claude <noreply@anthropic.com>← written by the agent
+
+

Strand tags agent commits in the graph — the ai chip — and + “select commits since baseline” rounds up the whole session for review.

+
+
+
+
@@ -757,7 +786,7 @@

Your agent is already typing.

strand -

The Git client for the agent era.
Stable, fast, performance-first.

+

The fast, cross-platform Git client.
Stable, clear, performance-first.