feat: add 52 per-tool SEO landing pages and public changelog (#145)

* feat: add 52 per-tool SEO landing pages and public changelog

- Create individual landing pages for all 52 tools at /tools/{slug}
  with search-intent title tags, unique FAQs (156 Q&As), per-tool
  features, and rich schema markup (BreadcrumbList, WebApplication,
  HowTo, FAQPage)
- Replace static sitemap.xml with dynamic generation including all
  tool URLs
- Refactor bento grid to import from @snapotter/shared (eliminates
  330 lines of duplicated tool data) and link cards to tool pages
- Add @snapotter/shared as workspace dependency to landing site
- Add public changelog page to docs site with curated release notes
  from v1.8 through v1.17
- Update docs nav and sidebar with changelog link

* feat: enhance SEO and performance with updated metadata, robots.txt, and llms.txt
This commit is contained in:
SnapOtter
2026-05-18 16:39:54 +08:00
committed by GitHub
parent 0dadca3b99
commit 9941a1db03
22 changed files with 2580 additions and 439 deletions
+18
View File
@@ -134,7 +134,25 @@ interface LockData {
startedAt: string;
}
const LOCK_STALE_MS = 45 * 60 * 1000;
export function acquireInstallLock(bundleId: string): boolean {
// If a lock exists, check its age. OOM-killed processes or crashes
// may leave a stale lock file behind; treat anything older than 45
// minutes as abandoned.
if (existsSync(LOCK_PATH)) {
try {
const age = Date.now() - statSync(LOCK_PATH).mtimeMs;
if (age > LOCK_STALE_MS) {
unlinkSync(LOCK_PATH);
console.warn(
`[feature-status] Removed stale install lock (age: ${Math.round(age / 1000)}s)`,
);
}
} catch {
// stat/unlink failed -- fall through to O_EXCL which will fail too
}
}
try {
const fd = openSync(LOCK_PATH, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
const lock: LockData = {
+5
View File
@@ -219,6 +219,11 @@ export async function registerProgressRoutes(app: FastifyInstance): Promise<void
// Take over the response from Fastify for SSE streaming
reply.hijack();
// Disable socket timeout -- feature installs can take 30+ minutes
// for large model downloads. Without this, Node's requestTimeout
// kills the SSE connection mid-install.
request.raw.socket?.setTimeout?.(0);
// Send SSE headers via the raw Node response
reply.raw.writeHead(200, {
"Content-Type": "text/event-stream",