From 4af391f58f08fcd8da90db73222147b5f37f298d Mon Sep 17 00:00:00 2001 From: Jacob Stephens Date: Mon, 19 Oct 2026 11:43:21 +0000 Subject: [PATCH 1/3] Improve thirdshift.app search discovery and metadata (#803) --- site/docs/index.html | 12 ++++++ site/generate-docs.mjs | 12 ++++++ site/how-it-works/index.html | 12 ++++++ site/index.html | 15 +++++-- site/prompts/index.html | 13 ++++++ site/robots.txt | 2 + site/sitemap.xml | 7 ++++ site/tests/pages.test.cjs | 78 ++++++++++++++++++++++++++++++++++++ 8 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 site/sitemap.xml diff --git a/site/docs/index.html b/site/docs/index.html index 8d94df6c..d4736dbf 100644 --- a/site/docs/index.html +++ b/site/docs/index.html @@ -7,6 +7,18 @@ Documentation · thirdshift + + + + + + + + + + + + diff --git a/site/generate-docs.mjs b/site/generate-docs.mjs index 7c673a9d..6ad08f46 100644 --- a/site/generate-docs.mjs +++ b/site/generate-docs.mjs @@ -34,6 +34,18 @@ const page = ` Documentation · thirdshift + + + + + + + + + + + + diff --git a/site/how-it-works/index.html b/site/how-it-works/index.html index b2876edf..d1371ece 100644 --- a/site/how-it-works/index.html +++ b/site/how-it-works/index.html @@ -6,6 +6,18 @@ How thirdshift works · thirdshift + + + + + + + + + + + + diff --git a/site/index.html b/site/index.html index 32dbfb8c..6326551e 100644 --- a/site/index.html +++ b/site/index.html @@ -3,13 +3,22 @@ -thirdshift: your issues go to press overnight +thirdshift · AI coding agents from GitHub issue to pull request + + - + @@ -17,7 +26,7 @@ - + diff --git a/site/prompts/index.html b/site/prompts/index.html index 22ec9192..2ea8ca1f 100644 --- a/site/prompts/index.html +++ b/site/prompts/index.html @@ -5,6 +5,19 @@ Prompts and skills · thirdshift + + + + + + + + + + + + + diff --git a/site/robots.txt b/site/robots.txt index c2a49f4f..7b5a0094 100644 --- a/site/robots.txt +++ b/site/robots.txt @@ -1,2 +1,4 @@ User-agent: * Allow: / + +Sitemap: https://thirdshift.app/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 00000000..d8d1f997 --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,7 @@ + + + https://thirdshift.app/ + https://thirdshift.app/docs/ + https://thirdshift.app/how-it-works/ + https://thirdshift.app/prompts/ + diff --git a/site/tests/pages.test.cjs b/site/tests/pages.test.cjs index 2934995a..f106fe85 100644 --- a/site/tests/pages.test.cjs +++ b/site/tests/pages.test.cjs @@ -35,6 +35,84 @@ async function pageForTest(t, options) { return page; } +test("public pages expose canonical search and share metadata without JavaScript", async (t) => { + const page = await pageForTest(t, { javaScriptEnabled: false }); + const titles = new Set(); + const descriptions = new Set(); + for (const path of ["/", "/how-it-works/", "/docs/", "/prompts/"]) { + assert.equal((await page.goto(`${origin}${path}`)).status(), 200); + const canonical = page.locator('head link[rel="canonical"]'); + assert.equal(await canonical.count(), 1, path); + assert.equal(await canonical.getAttribute("href"), `https://thirdshift.app${path}`); + const title = await page.title(); + const description = await page.locator('head meta[name="description"]').getAttribute("content"); + assert.ok(title.includes("thirdshift"), path); + assert.ok(description.length > 0, path); + titles.add(title); + descriptions.add(description); + for (const [selector, expected] of [ + ['meta[property="og:type"]', "website"], + ['meta[property="og:site_name"]', "thirdshift"], + ['meta[property="og:url"]', `https://thirdshift.app${path}`], + ['meta[property="og:title"]', title], + ['meta[property="og:description"]', description], + ['meta[property="og:image"]', "https://thirdshift.app/share-card.png"], + ['meta[name="twitter:card"]', "summary_large_image"], + ['meta[name="twitter:title"]', title], + ['meta[name="twitter:description"]', description], + ['meta[name="twitter:image"]', "https://thirdshift.app/share-card.png"], + ]) { + const tag = page.locator(`head ${selector}`); + assert.equal(await tag.count(), 1, `${path}: ${selector}`); + assert.equal(await tag.getAttribute("content"), expected, `${path}: ${selector}`); + } + assert.equal(await page.locator('meta[name="robots"][content*="noindex"]').count(), 0); + } + assert.equal(titles.size, 4, "each page has a distinct search title"); + assert.equal(descriptions.size, 4, "each page has a distinct search description"); + await page.goto(`${origin}/`); + assert.match(await page.title(), /AI coding agents.*GitHub issue.*pull request/); +}); + +test("robots advertises a sitemap of the public canonical pages", async (t) => { + const page = await pageForTest(t, { javaScriptEnabled: false }); + const robots = await page.request.get(`${origin}/robots.txt`); + assert.equal(robots.status(), 200); + assert.match(await robots.text(), /^Sitemap: https:\/\/thirdshift\.app\/sitemap\.xml$/m); + const sitemap = await page.request.get(`${origin}/sitemap.xml`); + assert.equal(sitemap.status(), 200); + const urls = await page.evaluate(xml => { + const document = new DOMParser().parseFromString(xml, "application/xml"); + if (document.querySelector("parsererror")) throw new Error("Invalid sitemap XML"); + if (document.documentElement.namespaceURI !== "http://www.sitemaps.org/schemas/sitemap/0.9") { + throw new Error("Invalid sitemap namespace"); + } + return Array.from(document.querySelectorAll("urlset > url > loc"), node => node.textContent); + }, await sitemap.text()); + assert.deepEqual(urls.sort(), [ + "https://thirdshift.app/", + "https://thirdshift.app/docs/", + "https://thirdshift.app/how-it-works/", + "https://thirdshift.app/prompts/", + ]); + for (const url of urls) { + assert.equal((await page.goto(`${origin}${new URL(url).pathname}`)).status(), 200); + assert.equal(await page.locator('link[rel="canonical"]').getAttribute("href"), url); + } +}); + +test("the homepage identifies the site to search engines without JavaScript", async (t) => { + const page = await pageForTest(t, { javaScriptEnabled: false }); + await page.goto(`${origin}/`); + const data = page.locator('head script[type="application/ld+json"]'); + assert.equal(await data.count(), 1); + const site = JSON.parse(await data.textContent()); + assert.equal(site["@context"], "https://schema.org"); + assert.equal(site["@type"], "WebSite"); + assert.equal(site.name, "thirdshift"); + assert.equal(site.url, "https://thirdshift.app/"); +}); + test("the homepage links to the shared Markdown manual without JavaScript", async (t) => { const page = await pageForTest(t, { javaScriptEnabled: false, viewport: { width: 320, height: 900 } }); await page.goto(`${origin}/`); From 538f3707e682ccc1d7fb5f13c8a9851b003a06dc Mon Sep 17 00:00:00 2001 From: Jacob Stephens Date: Mon, 19 Oct 2026 07:43:35 -0400 Subject: [PATCH 2/3] Improve site search metadata and crawl discovery (#803) --- site/docs/index.html | 2 +- site/generate-docs.mjs | 2 +- site/index.html | 23 +++++++++---- site/prompts/index.html | 3 +- site/robots.txt | 1 + site/sitemap.xml | 7 ++++ site/tests/README.md | 5 +++ site/tests/pages.test.cjs | 70 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 site/sitemap.xml diff --git a/site/docs/index.html b/site/docs/index.html index 8d94df6c..66230cd4 100644 --- a/site/docs/index.html +++ b/site/docs/index.html @@ -4,7 +4,7 @@ -Documentation · thirdshift +thirdshift documentation · Install, configure and run coding agents diff --git a/site/generate-docs.mjs b/site/generate-docs.mjs index 7c673a9d..524d11a2 100644 --- a/site/generate-docs.mjs +++ b/site/generate-docs.mjs @@ -31,7 +31,7 @@ const page = ` -Documentation · thirdshift +thirdshift documentation · Install, configure and run coding agents diff --git a/site/index.html b/site/index.html index 32dbfb8c..dc62e860 100644 --- a/site/index.html +++ b/site/index.html @@ -3,22 +3,31 @@ -thirdshift: your issues go to press overnight - +thirdshift · GitHub issues to pull requests with coding agents + + + - - + + - - + + @@ -53,7 +62,7 @@

Your issues go to press overnight.

-

The humans are the day shift: you talk the idea through, write the spec and cut it into tickets. The agents work the third shift: give thirdshift an Issue URL and it implements the issue, reviews its own work and delivers a pull request ready for review, or merges it when asked. Start a Run whenever you like, or let your operating system start passes on a schedule.

+

The humans are the day shift: you talk the idea through, write the spec and cut it into tickets. Coding agents work the third shift: give thirdshift a GitHub issue URL and it implements the issue, reviews its own work and delivers a pull request ready for review, or merges it when asked. Start a Run whenever you like, or let your operating system start passes on a schedule.

How thirdshift works: an engineer's guide from issue to pull request, through Worktrees, agent review and Delivery. Documentation covers installation, commands and configuration.

Built on Matt Pocock's skills: aihero.dev/skills · mattpocock/skills. Read every prompt and skill thirdshift sends.

diff --git a/site/prompts/index.html b/site/prompts/index.html index 22ec9192..40e648e6 100644 --- a/site/prompts/index.html +++ b/site/prompts/index.html @@ -4,7 +4,8 @@ Prompts and skills · thirdshift - + + diff --git a/site/robots.txt b/site/robots.txt index c2a49f4f..6ca7f3ed 100644 --- a/site/robots.txt +++ b/site/robots.txt @@ -1,2 +1,3 @@ User-agent: * Allow: / +Sitemap: https://thirdshift.app/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 00000000..d8d1f997 --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,7 @@ + + + https://thirdshift.app/ + https://thirdshift.app/docs/ + https://thirdshift.app/how-it-works/ + https://thirdshift.app/prompts/ + diff --git a/site/tests/README.md b/site/tests/README.md index 041422dc..ee153b31 100644 --- a/site/tests/README.md +++ b/site/tests/README.md @@ -12,6 +12,11 @@ npm test The tests start a local server on an unused port and close it and the browser before exiting. CI runs them in the `site-test` job. +The page tests also consume the site without JavaScript to check canonical URLs, +distinct search metadata, homepage site-name structured data and sitemap discovery +through `robots.txt`. When adding a public page, give it a canonical URL and unique +title and description, then include it in `site/sitemap.xml` and the page tests. + The user manual's source is `docs/guide.md` at the repository root. After editing it or `site/generate-docs.mjs`, run `cd site && npm run docs` and commit the generated `site/docs/index.html` in the same pull request. `npm test` first runs diff --git a/site/tests/pages.test.cjs b/site/tests/pages.test.cjs index 2934995a..f38df5a0 100644 --- a/site/tests/pages.test.cjs +++ b/site/tests/pages.test.cjs @@ -35,6 +35,76 @@ async function pageForTest(t, options) { return page; } +test("every public page identifies its canonical URL and purpose without JavaScript", async (t) => { + const page = await pageForTest(t, { javaScriptEnabled: false }); + const titles = new Set(); + const descriptions = new Set(); + for (const path of ["/", "/docs/", "/how-it-works/", "/prompts/"]) { + assert.equal((await page.goto(`${origin}${path}`)).status(), 200); + assert.equal(await page.locator('link[rel="canonical"]').count(), 1, path); + assert.equal(await page.locator('link[rel="canonical"]').getAttribute("href"), `https://thirdshift.app${path}`); + const title = await page.title(); + const description = await page.locator('meta[name="description"]').getAttribute("content"); + assert.match(title, /thirdshift/); + assert.ok(description.length > 50, path); + titles.add(title); + descriptions.add(description); + assert.equal(await page.locator("h1").count(), 1, path); + assert.ok((await page.locator("body").textContent()).includes("GitHub"), path); + } + assert.equal(titles.size, 4, "page titles distinguish the four search results"); + assert.equal(descriptions.size, 4, "each page has its own search description"); + await page.goto(`${origin}/`); + assert.match(await page.title(), /GitHub issues.*pull requests/); +}); + +test("robots advertises a valid sitemap of reachable canonical pages", async (t) => { + const page = await pageForTest(t, { javaScriptEnabled: false }); + const robots = await page.request.get(`${origin}/robots.txt`); + assert.equal(robots.status(), 200); + assert.match(await robots.text(), /^Allow: \/$/m); + assert.match(await robots.text(), /^Sitemap: https:\/\/thirdshift\.app\/sitemap\.xml$/m); + const sitemap = await page.request.get(`${origin}/sitemap.xml`); + assert.equal(sitemap.status(), 200); + const urls = await page.evaluate(xml => { + const document = new DOMParser().parseFromString(xml, "application/xml"); + if (document.querySelector("parsererror")) throw new Error("Invalid sitemap XML"); + if (document.documentElement.namespaceURI !== "http://www.sitemaps.org/schemas/sitemap/0.9") { + throw new Error("Missing sitemap namespace"); + } + return [...document.querySelectorAll("urlset > url > loc")].map(node => node.textContent); + }, await sitemap.text()); + assert.deepEqual(urls.sort(), [ + "https://thirdshift.app/", + "https://thirdshift.app/docs/", + "https://thirdshift.app/how-it-works/", + "https://thirdshift.app/prompts/", + ]); + for (const url of urls) { + assert.equal((await page.goto(`${origin}${new URL(url).pathname}`)).status(), 200, url); + assert.equal(await page.locator('link[rel="canonical"]').getAttribute("href"), url); + } +}); + +test("the homepage declares the site name as static structured data", async (t) => { + const page = await pageForTest(t, { javaScriptEnabled: false }); + await page.goto(`${origin}/`); + assert.equal(await page.locator('script[type="application/ld+json"]').count(), 1); + const site = JSON.parse(await page.locator('script[type="application/ld+json"]').textContent()); + assert.equal(site["@context"], "https://schema.org"); + assert.equal(site["@type"], "WebSite"); + assert.equal(site.name, "thirdshift"); + assert.equal(site.url, "https://thirdshift.app/"); + assert.equal(site.url, await page.locator('link[rel="canonical"]').getAttribute("href")); + assert.equal(site.name, await page.locator('meta[property="og:site_name"]').getAttribute("content")); + assert.equal(await page.locator('meta[property="og:title"]').getAttribute("content"), await page.title()); + assert.equal(await page.locator('meta[name="twitter:title"]').getAttribute("content"), await page.title()); + for (const selector of ['meta[property="og:description"]', 'meta[name="twitter:description"]']) { + assert.equal(await page.locator(selector).getAttribute("content"), + await page.locator('meta[name="description"]').getAttribute("content")); + } +}); + test("the homepage links to the shared Markdown manual without JavaScript", async (t) => { const page = await pageForTest(t, { javaScriptEnabled: false, viewport: { width: 320, height: 900 } }); await page.goto(`${origin}/`); From 13422a78a90781365d45ec1c34d1cff34a3340de Mon Sep 17 00:00:00 2001 From: Jacob Stephens Date: Mon, 19 Oct 2026 07:45:13 -0400 Subject: [PATCH 3/3] Consolidate public-page coverage after Standards review --- site/tests/pages.test.cjs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/site/tests/pages.test.cjs b/site/tests/pages.test.cjs index f38df5a0..9692ce49 100644 --- a/site/tests/pages.test.cjs +++ b/site/tests/pages.test.cjs @@ -5,6 +5,8 @@ const { readFile } = require("node:fs/promises"); const { join } = require("node:path"); const { chromium } = require("playwright"); +const publicPaths = ["/", "/docs/", "/how-it-works/", "/prompts/"]; + let server, browser, origin; before(async () => { server = createServer(async (request, response) => { @@ -39,7 +41,7 @@ test("every public page identifies its canonical URL and purpose without JavaScr const page = await pageForTest(t, { javaScriptEnabled: false }); const titles = new Set(); const descriptions = new Set(); - for (const path of ["/", "/docs/", "/how-it-works/", "/prompts/"]) { + for (const path of publicPaths) { assert.equal((await page.goto(`${origin}${path}`)).status(), 200); assert.equal(await page.locator('link[rel="canonical"]').count(), 1, path); assert.equal(await page.locator('link[rel="canonical"]').getAttribute("href"), `https://thirdshift.app${path}`); @@ -74,16 +76,8 @@ test("robots advertises a valid sitemap of reachable canonical pages", async (t) } return [...document.querySelectorAll("urlset > url > loc")].map(node => node.textContent); }, await sitemap.text()); - assert.deepEqual(urls.sort(), [ - "https://thirdshift.app/", - "https://thirdshift.app/docs/", - "https://thirdshift.app/how-it-works/", - "https://thirdshift.app/prompts/", - ]); - for (const url of urls) { - assert.equal((await page.goto(`${origin}${new URL(url).pathname}`)).status(), 200, url); - assert.equal(await page.locator('link[rel="canonical"]').getAttribute("href"), url); - } + // The metadata test checks reachability and canonical URLs for this inventory. + assert.deepEqual(urls.sort(), publicPaths.map(path => `https://thirdshift.app${path}`)); }); test("the homepage declares the site name as static structured data", async (t) => {