mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
feat(web): real /about and /docs pages (unique crawlable content)
Addresses the top cross-engine AEO finding — every route previously served the homepage SPA. /about and /docs are now distinct routes with their own server-rendered content and titles. - /about: substantive about page (what LogicSRC is, the standards surface, CommandBoard.run reference impl, GitHub, hire-us) — derived from public positioning, no fabricated team. - /docs + /docs/[slug]: render the repo's docs/*.md (curated public set) via marked, statically generated at build (no runtime fs dependency). - Drop about/docs from the catch-all; add doc URLs to the sitemap. Verified in a running build: /about and /docs serve unique content with distinct titles; /docs/[slug] renders each markdown doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6518dfb4a9
commit
fb20fd2e99
8 changed files with 291 additions and 9 deletions
|
|
@ -14,6 +14,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@profullstack/autoblog": "github:profullstack/autoblog#75e54af",
|
"@profullstack/autoblog": "github:profullstack/autoblog#75e54af",
|
||||||
"@supabase/supabase-js": "^2.105.4",
|
"@supabase/supabase-js": "^2.105.4",
|
||||||
|
"marked": "^18.0.5",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0"
|
"react-dom": "19.2.0"
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,9 @@ import { HomeInteractivity } from "@/components/home-interactivity";
|
||||||
// scrolled to the matching section. We preserve those URLs (they are canonical
|
// scrolled to the matching section. We preserve those URLs (they are canonical
|
||||||
// in sitemap.xml) by rendering the same page for each known route and 404ing
|
// in sitemap.xml) by rendering the same page for each known route and 404ing
|
||||||
// anything else.
|
// anything else.
|
||||||
|
// /about and /docs are now real routes (app/about, app/docs); the rest still
|
||||||
|
// render the homepage SPA scrolled to their section.
|
||||||
const ROUTE_META: Record<string, { title: string; description: string }> = {
|
const ROUTE_META: Record<string, { title: string; description: string }> = {
|
||||||
docs: {
|
|
||||||
title: "Docs · LogicSRC",
|
|
||||||
description: "LogicSRC specification guides, schemas, and CLI conventions for human–AI agent coordination.",
|
|
||||||
},
|
|
||||||
openspec: {
|
openspec: {
|
||||||
title: "LogicSRC vs OpenSpec.dev · LogicSRC",
|
title: "LogicSRC vs OpenSpec.dev · LogicSRC",
|
||||||
description: "How LogicSRC's coordination standard compares with OpenSpec.dev, including MCP and agent support.",
|
description: "How LogicSRC's coordination standard compares with OpenSpec.dev, including MCP and agent support.",
|
||||||
|
|
@ -25,10 +23,6 @@ const ROUTE_META: Record<string, { title: string; description: string }> = {
|
||||||
title: "Hire Us · LogicSRC",
|
title: "Hire Us · LogicSRC",
|
||||||
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $250/week for accepted work, paid via CoinPay.",
|
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $250/week for accepted work, paid via CoinPay.",
|
||||||
},
|
},
|
||||||
about: {
|
|
||||||
title: "About · LogicSRC",
|
|
||||||
description: "LogicSRC is the Profullstack, Inc. open-specification project for human and AI agent coordination.",
|
|
||||||
},
|
|
||||||
terms: { title: "Terms · LogicSRC", description: "LogicSRC terms of use." },
|
terms: { title: "Terms · LogicSRC", description: "LogicSRC terms of use." },
|
||||||
privacy: { title: "Privacy · LogicSRC", description: "LogicSRC privacy notes." },
|
privacy: { title: "Privacy · LogicSRC", description: "LogicSRC privacy notes." },
|
||||||
"agent-swarm": {
|
"agent-swarm": {
|
||||||
|
|
|
||||||
94
apps/logicsrc-web/src/app/about/page.tsx
Normal file
94
apps/logicsrc-web/src/app/about/page.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { SiteShell } from "@/components/site-shell";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "About · LogicSRC",
|
||||||
|
description:
|
||||||
|
"LogicSRC is the Profullstack, Inc. open-specification project for coordination between humans and AI agents — schemas, primitives, and conventions that products implement without owning the standard.",
|
||||||
|
alternates: { canonical: "/about" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AboutPage(): ReactNode {
|
||||||
|
return (
|
||||||
|
<SiteShell active="About">
|
||||||
|
<article className="band" style={{ maxWidth: "48rem" }}>
|
||||||
|
<div className="section-head">
|
||||||
|
<h2>About LogicSRC</h2>
|
||||||
|
<p>
|
||||||
|
LogicSRC is the open-specification project from{" "}
|
||||||
|
<a href="https://profullstack.com" rel="noreferrer">
|
||||||
|
Profullstack, Inc.
|
||||||
|
</a>{" "}
|
||||||
|
for coordination between humans, AI agents, plugins, payment systems,
|
||||||
|
and hosted products.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="blog-content"
|
||||||
|
style={{ lineHeight: 1.7, marginTop: "1.5rem" }}
|
||||||
|
>
|
||||||
|
<h3>What it is</h3>
|
||||||
|
<p>
|
||||||
|
LogicSRC defines a shared language — open JSON-Schema contracts and
|
||||||
|
conventions — so that independent tools, agents, and services can
|
||||||
|
coordinate without any one vendor owning the standard. Products
|
||||||
|
implement LogicSRC; they don't depend on a proprietary platform
|
||||||
|
to interoperate.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>The standards surface</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Identity</strong> — DIDs, OAuth accounts, profiles, and
|
||||||
|
organization membership.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Coordination</strong> — boards, tasks, and the workflow
|
||||||
|
primitives agents and humans share.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Agents</strong> — agent profiles, capabilities, runs,
|
||||||
|
logs, permissions, and audit trails.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Value</strong> — payment, invoicing, and metering hooks.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Events</strong> — a common event envelope for policy,
|
||||||
|
audit, and webhooks.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Reference implementation</h3>
|
||||||
|
<p>
|
||||||
|
<strong>CommandBoard.run</strong> is the first hosted product built
|
||||||
|
on LogicSRC — a modern BBS where humans and AI agents coordinate work
|
||||||
|
through boards, tasks, DID identity, OAuth, CLI, TUI, plugins,
|
||||||
|
reputation, audit logs, and payments. It demonstrates the primitives
|
||||||
|
across PWA, CLI, TUI, API, plugins, CoinPay, and uGig.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>How it's built</h3>
|
||||||
|
<p>
|
||||||
|
The spec, schemas, SDKs, CLI, TUI, and reference plugins are
|
||||||
|
developed in the open at{" "}
|
||||||
|
<a href="https://github.com/profullstack/logicsrc" rel="noreferrer">
|
||||||
|
github.com/profullstack/logicsrc
|
||||||
|
</a>
|
||||||
|
. See the <a href="/docs">docs</a> for the data model and conventions,
|
||||||
|
or the <a href="/openspec">OpenSpec.dev comparison</a> for how LogicSRC
|
||||||
|
differs from repo-local planning specs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Work with us</h3>
|
||||||
|
<p>
|
||||||
|
Profullstack implements LogicSRC-based systems at $250/week for
|
||||||
|
accepted work, paid via CoinPay. See <a href="/hire-us">Hire Us</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</SiteShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
58
apps/logicsrc-web/src/app/docs/[slug]/page.tsx
Normal file
58
apps/logicsrc-web/src/app/docs/[slug]/page.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { marked } from "marked";
|
||||||
|
import { DOC_SLUGS, docExcerpt, docTitle, readDoc } from "@/lib/docs";
|
||||||
|
import { SiteShell } from "@/components/site-shell";
|
||||||
|
|
||||||
|
// Statically generate one page per curated doc at build time.
|
||||||
|
export function generateStaticParams(): Array<{ slug: string }> {
|
||||||
|
return DOC_SLUGS.map((slug) => ({ slug }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dynamicParams = false;
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ slug: string }>;
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { slug } = await params;
|
||||||
|
const md = readDoc(slug);
|
||||||
|
if (!md) return { title: "Not found · LogicSRC" };
|
||||||
|
return {
|
||||||
|
title: `${docTitle(md, slug)} · LogicSRC Docs`,
|
||||||
|
description: docExcerpt(md) || undefined,
|
||||||
|
alternates: { canonical: `/docs/${slug}` },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function DocPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ slug: string }>;
|
||||||
|
}): Promise<ReactNode> {
|
||||||
|
const { slug } = await params;
|
||||||
|
const md = readDoc(slug);
|
||||||
|
if (!md) notFound();
|
||||||
|
|
||||||
|
const html = await marked.parse(md);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SiteShell active="Docs">
|
||||||
|
<article className="band" style={{ maxWidth: "48rem" }}>
|
||||||
|
<p style={{ marginBottom: "1.5rem" }}>
|
||||||
|
<Link href="/docs" style={{ color: "#5b6b7a", textDecoration: "none" }}>
|
||||||
|
← Docs
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="blog-content"
|
||||||
|
style={{ lineHeight: 1.7 }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
|
/>
|
||||||
|
</article>
|
||||||
|
</SiteShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
apps/logicsrc-web/src/app/docs/page.tsx
Normal file
53
apps/logicsrc-web/src/app/docs/page.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { listDocs } from "@/lib/docs";
|
||||||
|
import { SiteShell } from "@/components/site-shell";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Docs · LogicSRC",
|
||||||
|
description:
|
||||||
|
"LogicSRC specification guides — data model, CLI/TUI conventions, config, permission scopes, plugins, credential sharing, and the OpenSpec.dev comparison.",
|
||||||
|
alternates: { canonical: "/docs" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DocsIndex(): ReactNode {
|
||||||
|
const docs = listDocs();
|
||||||
|
return (
|
||||||
|
<SiteShell active="Docs">
|
||||||
|
<div className="band">
|
||||||
|
<div className="section-head">
|
||||||
|
<h2>Docs</h2>
|
||||||
|
<p>
|
||||||
|
Specification guides and conventions for the LogicSRC coordination
|
||||||
|
standard. Source lives in the{" "}
|
||||||
|
<a href="https://github.com/profullstack/logicsrc" rel="noreferrer">
|
||||||
|
profullstack/logicsrc
|
||||||
|
</a>{" "}
|
||||||
|
repository.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
|
||||||
|
{docs.map((doc) => (
|
||||||
|
<li
|
||||||
|
key={doc.slug}
|
||||||
|
style={{ padding: "1.25rem 0", borderTop: "1px solid #e3e6e0" }}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={`/docs/${doc.slug}`}
|
||||||
|
style={{ color: "inherit", textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
<h3 style={{ margin: "0 0 0.35rem", fontSize: "1.2rem", color: "#101418" }}>
|
||||||
|
{doc.title}
|
||||||
|
</h3>
|
||||||
|
</Link>
|
||||||
|
{doc.excerpt ? (
|
||||||
|
<p style={{ color: "#41505d", margin: 0 }}>{doc.excerpt}</p>
|
||||||
|
) : null}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</SiteShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
import { publicClient } from "@/lib/supabase";
|
import { publicClient } from "@/lib/supabase";
|
||||||
|
import { DOC_SLUGS } from "@/lib/docs";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
|
@ -35,6 +36,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
priority: route.priority,
|
priority: route.priority,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const docEntries: MetadataRoute.Sitemap = DOC_SLUGS.map((slug) => ({
|
||||||
|
url: `${base}/docs/${slug}`,
|
||||||
|
changeFrequency: "monthly",
|
||||||
|
priority: 0.6,
|
||||||
|
}));
|
||||||
|
|
||||||
let postEntries: MetadataRoute.Sitemap = [];
|
let postEntries: MetadataRoute.Sitemap = [];
|
||||||
try {
|
try {
|
||||||
const supabase = publicClient();
|
const supabase = publicClient();
|
||||||
|
|
@ -54,5 +61,5 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
postEntries = [];
|
postEntries = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...staticEntries, ...postEntries];
|
return [...staticEntries, ...docEntries, ...postEntries];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
apps/logicsrc-web/src/lib/docs.ts
Normal file
62
apps/logicsrc-web/src/lib/docs.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
// Repo-root docs/ (read at build time during static generation, so there is
|
||||||
|
// no runtime filesystem dependency in the deployed image).
|
||||||
|
const DOCS_DIR = resolve(process.cwd(), "../../docs");
|
||||||
|
|
||||||
|
// Curated, public-facing reference docs. Internal notes (roadmap, positioning,
|
||||||
|
// arcade) are intentionally excluded.
|
||||||
|
export const DOC_SLUGS = [
|
||||||
|
"openspec-comparison",
|
||||||
|
"data-model",
|
||||||
|
"cli",
|
||||||
|
"tui",
|
||||||
|
"config",
|
||||||
|
"permissions",
|
||||||
|
"plugins",
|
||||||
|
"credential-sharing",
|
||||||
|
"agent-screening",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type DocSlug = (typeof DOC_SLUGS)[number];
|
||||||
|
|
||||||
|
export function isDocSlug(slug: string): slug is DocSlug {
|
||||||
|
return (DOC_SLUGS as readonly string[]).includes(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readDoc(slug: string): string | null {
|
||||||
|
if (!isDocSlug(slug)) return null;
|
||||||
|
try {
|
||||||
|
return readFileSync(resolve(DOCS_DIR, `${slug}.md`), "utf8");
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function docTitle(markdown: string, slug: string): string {
|
||||||
|
const h1 = markdown.split("\n").find((line) => line.startsWith("# "));
|
||||||
|
return h1 ? h1.replace(/^#\s+/, "").trim() : slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function docExcerpt(markdown: string): string {
|
||||||
|
for (const raw of markdown.split("\n")) {
|
||||||
|
const line = raw.trim();
|
||||||
|
if (line && !line.startsWith("#") && !line.startsWith("```") && !line.startsWith(">")) {
|
||||||
|
return line.replace(/[*_`#>[\]()]/g, "").trim().slice(0, 160);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DocSummary = { slug: DocSlug; title: string; excerpt: string };
|
||||||
|
|
||||||
|
export function listDocs(): DocSummary[] {
|
||||||
|
const out: DocSummary[] = [];
|
||||||
|
for (const slug of DOC_SLUGS) {
|
||||||
|
const md = readDoc(slug);
|
||||||
|
if (!md) continue;
|
||||||
|
out.push({ slug, title: docTitle(md, slug), excerpt: docExcerpt(md) });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
13
package-lock.json
generated
13
package-lock.json
generated
|
|
@ -128,6 +128,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@profullstack/autoblog": "github:profullstack/autoblog#75e54af",
|
"@profullstack/autoblog": "github:profullstack/autoblog#75e54af",
|
||||||
"@supabase/supabase-js": "^2.105.4",
|
"@supabase/supabase-js": "^2.105.4",
|
||||||
|
"marked": "^18.0.5",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0"
|
"react-dom": "19.2.0"
|
||||||
|
|
@ -3853,6 +3854,18 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "18.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
|
||||||
|
"integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue