From eee9ce09c550df3f2030b29f0fd00c942d40d101 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 12 Sep 2026 20:38:28 -0700 Subject: [PATCH] Site IA: broad sidebar, four spec families, one registry (#169) Anthony: "that site needs better information architecture, it's impossible to find anything", "start broad in sidebar and drill down with dedicated pages, not all one page", and "I see none of our specs" on the home page. One registry, lib/specs.ts, now lists every specification in four families (people and agents; access and credentials; catalogs a site serves about itself; agents and process), with a landing path, a specification path and, for OpenServer's blocks, a parent. Everything that lists specs reads it: the sidebar (lib/nav.ts, four groups: Start, Specs, Tools, Company, rendered by SiteShell and by the home page string from the same array), /specs and /specs/, the home page's Standards Surface grid (families with their specs, replacing the five abstract primitives), /docs (grouped by family, then guides), the sitemap and llms.txt. DOC_SLUGS is derived from the registry. Adding a spec is one entry plus its files; the four hand-kept lists are gone. Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ Co-authored-by: Claude Fable 5.1 --- apps/logicsrc-web/src/app/docs/page.tsx | 83 +++++++--- apps/logicsrc-web/src/app/llms.txt/route.ts | 59 +++---- apps/logicsrc-web/src/app/sitemap.ts | 62 +++----- .../src/app/specs/[family]/page.tsx | 51 ++++++ apps/logicsrc-web/src/app/specs/page.tsx | 50 ++++++ .../src/components/site-shell.tsx | 80 ++++------ .../logicsrc-web/src/components/spec-list.tsx | 53 +++++++ apps/logicsrc-web/src/lib/docs.ts | 48 +----- apps/logicsrc-web/src/lib/nav.ts | 61 +++++++ apps/logicsrc-web/src/lib/page-markup.ts | 41 ++--- apps/logicsrc-web/src/lib/specs.ts | 150 ++++++++++++++++++ apps/logicsrc-web/src/styles.css | 34 ++++ 12 files changed, 553 insertions(+), 219 deletions(-) create mode 100644 apps/logicsrc-web/src/app/specs/[family]/page.tsx create mode 100644 apps/logicsrc-web/src/app/specs/page.tsx create mode 100644 apps/logicsrc-web/src/components/spec-list.tsx create mode 100644 apps/logicsrc-web/src/lib/nav.ts create mode 100644 apps/logicsrc-web/src/lib/specs.ts diff --git a/apps/logicsrc-web/src/app/docs/page.tsx b/apps/logicsrc-web/src/app/docs/page.tsx index ffc04ac..d9aca1f 100644 --- a/apps/logicsrc-web/src/app/docs/page.tsx +++ b/apps/logicsrc-web/src/app/docs/page.tsx @@ -2,51 +2,86 @@ import Link from "next/link"; import type { ReactNode } from "react"; import type { Metadata } from "next"; import { listDocs } from "@/lib/docs"; +import { FAMILIES, GUIDES, familyTree } from "@/lib/specs"; 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.", + "The specification text of every LogicSRC spec, grouped by family, and the guides: data model, CLI and TUI conventions, config, permissions, plugins.", alternates: { canonical: "/docs" }, }; +const row: React.CSSProperties = { padding: "0.9rem 0", borderTop: "1px solid #e3e6e0" }; +const h3: React.CSSProperties = { margin: "0 0 0.3rem", fontSize: "1.1rem", color: "#101418" }; +const p: React.CSSProperties = { color: "#41505d", margin: 0 }; + export default function DocsIndex(): ReactNode { - const docs = listDocs(); + const docs = new Map(listDocs().map((d) => [d.slug, d])); + const docFor = (path?: string) => (path ? docs.get(path.replace(/^\/docs\//, "")) : undefined); + return (

Docs

- Specification guides and conventions for the LogicSRC coordination - standard. Source lives in the{" "} + The specification text, one page per spec, grouped the way{" "} + the specs are. Source lives in the{" "} profullstack/logicsrc {" "} - repository. + repository under docs/.

-
    - {docs.map((doc) => ( -
  • - -

    - {doc.title} -

    + + {FAMILIES.map((family) => ( +
    +

    + + {family.name} - {doc.excerpt ? ( -

    {doc.excerpt}

    - ) : null} -

  • - ))} -
+ +

{family.line}.

+
    + {familyTree(family).flatMap(({ spec, children }) => [spec, ...children]).map((spec) => { + const doc = docFor(spec.doc); + if (!doc) return null; + return ( +
  • +

    + + {doc.title} + +

    +

    {spec.line}.

    +
  • + ); + })} +
+ + ))} + +
+

Guides

+

Conventions the reference implementations follow.

+
    + {GUIDES.map((g) => { + const doc = docs.get(g.slug); + if (!doc) return null; + return ( +
  • +

    + + {doc.title} + +

    + {doc.excerpt ?

    {doc.excerpt}

    : null} +
  • + ); + })} +
+
); diff --git a/apps/logicsrc-web/src/app/llms.txt/route.ts b/apps/logicsrc-web/src/app/llms.txt/route.ts index 5f68d86..5642149 100644 --- a/apps/logicsrc-web/src/app/llms.txt/route.ts +++ b/apps/logicsrc-web/src/app/llms.txt/route.ts @@ -1,49 +1,50 @@ +import { FAMILIES, GUIDES, familyTree } from "@/lib/specs"; + const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, ""); // GET /llms.txt — concise, link-rich orientation for LLM crawlers -// (https://llmstxt.org spec). +// (https://llmstxt.org spec). The spec sections are generated from the +// registry in lib/specs.ts, so a new spec appears here without a hand edit. export function GET(): Response { + const families = FAMILIES.map((family) => { + const lines = familyTree(family).flatMap(({ spec, children }) => { + const url = `${SITE_URL}${spec.landing ?? spec.doc ?? ""}`; + const doc = spec.doc && spec.landing ? ` Specification: ${SITE_URL}${spec.doc}.` : ""; + const head = `- [${spec.name}](${url}): ${spec.line}.${doc}`; + const kids = children.map( + (c) => ` - [${c.name}](${SITE_URL}${c.landing ?? c.doc ?? ""}): ${c.line}.` + ); + return [head, ...kids]; + }); + return `## ${family.name}\n\n${family.line}. Family page: ${SITE_URL}/specs/${family.slug}\n\n${lines.join("\n")}`; + }); + + const guides = GUIDES.map((g) => `- [${g.name}](${SITE_URL}/docs/${g.slug})`).join("\n"); + const body = `# LogicSRC -> Open schemas, primitives, and conventions for coordination between humans, AI agents, plugins, payment systems, and hosted products. LogicSRC defines the shared language; products can implement it without owning the standard. A Profullstack, Inc. open-specification project. +> Open schemas, primitives, and conventions for coordination between humans, AI agents, plugins, payment systems, and hosted products. LogicSRC defines the shared language; products can implement it without owning the standard. A Profullstack, Inc. open-specification project. Every specification is CC BY 4.0. -## Core +## Start -- [Home](${SITE_URL}/): Overview, standards surface, schemas, CLI, and reference implementations. -- [Docs](${SITE_URL}/docs): Specification guides and conventions. -- [OpenSpec](${SITE_URL}/openspec): LogicSRC vs OpenSpec.dev comparison and compatibility mode. +- [Home](${SITE_URL}/): Overview. +- [Specs](${SITE_URL}/specs): Every specification, in four families. +- [Docs](${SITE_URL}/docs): Specification text and guides. - [Blog](${SITE_URL}/blog): Project notes and release announcements. - [Blog RSS](${SITE_URL}/blog/rss.xml): Machine-readable feed of posts. +- [This site's OpenProfile.md](${SITE_URL}/.well-known/openprofile.md) -## Standards & products +${families.join("\n\n")} -- [ASDLC](${SITE_URL}/asdlc): The Agentic Software Development Lifecycle: nine phases for building software when agents work in parallel and CI/CD is the only gate, with conformance levels and the ratchet rule. -- [OpenMCP](${SITE_URL}/openmcp): An open catalog of MCP relays. Live catalog at https://openmcp.logicsrc.com; CLI installs with \`curl -fsSL https://openmcp.logicsrc.com/install.sh | sh\`. -- [OpenProfile.md](${SITE_URL}/openprofile): One Markdown file for who and where, people and agents alike. This site's own is at ${SITE_URL}/.well-known/openprofile.md. -- [OpenProfile.md](${SITE_URL}/openprofile): One Markdown file that says who you are and where you are, for people and agents alike: identity block, accounts, topics, reshare terms and operator, discovered at /.well-known/openprofile.md or through rel="openprofile". -- [OpenMCP](${SITE_URL}/openmcp): An open catalog of MCP relays: a relay serves /.well-known/openmcp.json, a catalog probes it and lists only what it found, and clients reach every relay through the catalog's REST, its own MCP endpoint, or signed webhooks. -- [OpenAccess](${SITE_URL}/openaccess): OAuth 2.1 with a grant you can carry: one hub account per person or agent, apps keep their own users and link them once, grants delegate narrower to agents, and a subscription bought in one app is honoured by every app that honours the product. Reference hub at openaccess.logicsrc.com. -- [OpenServer](${SITE_URL}/openserver): One file a hosting provider serves about what it sells, at /.well-known/openserver.json: every offer with kind (cloud, vps, dedicated, bare-metal, colocation, on-prem, shared, managed, paas, serverless, storage, gpu, edge, p2p, hybrid), the premises, management, tenancy and model axes, specs, one price, location and stock. Directories read the provider instead of scraping; first reader is nichedb.dev/c/hosting. -- [OpenCPU](${SITE_URL}/opencpu): The compute block of an OpenServer offer: threads against cores, the processor by its vendor name, dedicated, shared or burstable allocation, and a range that says what a buyer can add at checkout and for how much. -- [OpenMemory](${SITE_URL}/openmemory): The memory block of an OpenServer offer: RAM in mebibytes, DDR generation, ECC as three states, reserved or balloonable allocation, and a range priced per step. -- [OpenGPU](${SITE_URL}/opengpu): The gpu block of an OpenServer offer: the card by its vendor name, count and VRAM per device, interconnect, passthrough, MIG, vGPU or shared access, and a range over count. -- [OpenBandwidth](${SITE_URL}/openbandwidth): The network block of an OpenServer offer: port speed, transfer, unmetered, 95th percentile or flat metering, overage, IPv4 and IPv6 addresses as a priced resource, DDoS scrubbing, and a range. -- [OpenFile](${SITE_URL}/openfile): One file a publisher serves about the files it has published: content hash, swarm and HTTP fetch routes, verification, consent basis, price, and who holds it now, discovered at /.well-known/openfile.json. The web door onto an OpenSwarm ipfile swarm. -- [OpenDisk](${SITE_URL}/opendisk): One file a machine serves about the disk it will rent: free GiB, price per GiB-month, location, policy, proof cadence and hub standing, discovered at /.well-known/opendisk.json. What a peer-to-peer storage market is made of; reference marketplace d1sks.com. -- [OpenCoupon](${SITE_URL}/opencoupon): One file a merchant serves about what is on offer right now, at /.well-known/opencoupon.json: every code, sale and shipping threshold with kind, value, scope, dates, status and regions, expired codes kept so directories learn they died. A coupon site reads the merchant instead of a forum thread. -- [OpenRecipe.md](${SITE_URL}/openrecipe): One Markdown file that is a recipe: summary block (Serves, Prep, Cook, Cuisine, Diet, Author, Source), ingredients and steps as written, notes, nutrition; served next to the page or linked with rel="openrecipe"; schema.org/Recipe JSON-LD is derived from it, never the reverse. -- [OpenAffiliate](${SITE_URL}/openaffiliate): One file a merchant serves about the commission it pays, at /.well-known/openaffiliate.json: programs with what pays (sale, subscription, signup, lead, install), percent or amount, attribution window, hold days and payout methods; four calls let a person or an agent join with an OpenProfile.md, link with ?oa=code, read its own ledger and get paid to its own address. No network in the money; reference implementation crawlproof.com/affiliate. -- [OpenThreat](${SITE_URL}/openthreat): One file a security tool serves about what it found in the open, at /.well-known/openthreat.json: findings in public repositories, attacks on the reporter's own infrastructure, indicators and advisories, with severity, rule, subject and status. Private subjects are never in it, secrets are never located while open, announcing is on by default with a one-switch opt-out. First reporter threatcrush.com/discovery, first directory nichedb.dev/c/threats. -- [OpenBroadcast](${SITE_URL}/openbroadcast): The Broadcast section of an OpenProfile.md: the show a person hosts (podcast, radio, live audio, stream) with kind, format, cadence, audience, topics, slots, whether it pays or charges guests, and who the host is seeking, so a host and a guest are matched from two files rather than two forms. -- [OpenGuest](${SITE_URL}/openguest): The Guest section of an OpenProfile.md: that a person will appear on shows, with expertise, credentials, pitch, formats, availability, rate, past appearances and dealbreakers. An expert is a guest with Expertise and Credentials. Matched against OpenBroadcast. -- [AgentSwarm](${SITE_URL}/agent-swarm): Provider-neutral agent orchestration, model routing, and cost controls. -- [AgentByte](${SITE_URL}/agentbyte): Agent screening sessions, policy events, and APIs. -- [Credential Sharing](${SITE_URL}/credential-sharing): End-to-end-encrypted team vaults, plus source/target credential diffs, approval, sync, rollback, and audit. +## Guides + +${guides} ## Company & legal - [About](${SITE_URL}/about): What LogicSRC is and who maintains it (Profullstack, Inc.). - [Hire Us](${SITE_URL}/hire-us): Implementation help at $400/hour for accepted LogicSRC work. +- [Pricing](${SITE_URL}/pricing) - [Terms](${SITE_URL}/terms) - [Privacy](${SITE_URL}/privacy) `; diff --git a/apps/logicsrc-web/src/app/sitemap.ts b/apps/logicsrc-web/src/app/sitemap.ts index 50f07df..ddf4580 100644 --- a/apps/logicsrc-web/src/app/sitemap.ts +++ b/apps/logicsrc-web/src/app/sitemap.ts @@ -1,7 +1,7 @@ import type { MetadataRoute } from "next"; import { publicClient } from "@/lib/supabase"; import { DOC_SLUGS } from "@/lib/docs"; -import { REPORTED_SPECS, reportIds } from "@/lib/reports"; +import { FAMILIES, allSpecs } from "@/lib/specs"; export const dynamic = "force-dynamic"; @@ -9,40 +9,19 @@ function baseUrl(): string { return (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, ""); } -// Static routes preserved from the legacy public/sitemap.xml. -const STATIC_ROUTES: Array<{ +type Route = { path: string; changeFrequency: MetadataRoute.Sitemap[number]["changeFrequency"]; priority: number; -}> = [ +}; + +// The site's own pages. Spec landing pages come from the registry in +// lib/specs.ts, so a new spec is never missing here. +const STATIC_ROUTES: Route[] = [ { path: "/", changeFrequency: "weekly", priority: 1.0 }, + { path: "/specs", changeFrequency: "weekly", priority: 0.9 }, { path: "/docs", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openontology", changeFrequency: "weekly", priority: 0.9 }, - { path: "/opencreds", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openswarm", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openprd", changeFrequency: "weekly", priority: 0.9 }, - { path: "/asdlc", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openprofile", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openbroadcast", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openguest", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openmcp", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openaccess", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openserver", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openthreat", changeFrequency: "weekly", priority: 0.9 }, - { path: "/opencpu", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openmemory", changeFrequency: "weekly", priority: 0.9 }, - { path: "/opengpu", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openbandwidth", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openfile", changeFrequency: "weekly", priority: 0.9 }, - { path: "/opendisk", changeFrequency: "weekly", priority: 0.9 }, - { path: "/opencoupon", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openrecipe", changeFrequency: "weekly", priority: 0.9 }, - { path: "/openaffiliate", changeFrequency: "weekly", priority: 0.9 }, { path: "/openontology/explore", changeFrequency: "daily", priority: 0.7 }, - { path: "/openspec", changeFrequency: "weekly", priority: 0.8 }, - { path: "/agent-swarm", changeFrequency: "weekly", priority: 0.8 }, - { path: "/agentbyte", changeFrequency: "weekly", priority: 0.8 }, - { path: "/credential-sharing", changeFrequency: "weekly", priority: 0.8 }, { path: "/hire-us", changeFrequency: "weekly", priority: 0.8 }, { path: "/pricing", changeFrequency: "monthly", priority: 0.7 }, { path: "/blog", changeFrequency: "daily", priority: 0.7 }, @@ -51,10 +30,22 @@ const STATIC_ROUTES: Array<{ { path: "/privacy", changeFrequency: "monthly", priority: 0.4 }, ]; +function specRoutes(): Route[] { + const families: Route[] = FAMILIES.map((f) => ({ + path: `/specs/${f.slug}`, + changeFrequency: "weekly", + priority: 0.9, + })); + const landings: Route[] = allSpecs() + .filter((s) => s.landing) + .map((s) => ({ path: s.landing as string, changeFrequency: "weekly", priority: 0.9 })); + return [...families, ...landings]; +} + export default async function sitemap(): Promise { const base = baseUrl(); - const staticEntries: MetadataRoute.Sitemap = STATIC_ROUTES.map((route) => ({ + const staticEntries: MetadataRoute.Sitemap = [...STATIC_ROUTES, ...specRoutes()].map((route) => ({ url: `${base}${route.path}`, changeFrequency: route.changeFrequency, priority: route.priority, @@ -66,15 +57,6 @@ export default async function sitemap(): Promise { priority: 0.6, })); - // Benchmark reports: the index per reported spec, and each published report. - const reportEntries: MetadataRoute.Sitemap = []; - for (const slug of REPORTED_SPECS) { - reportEntries.push({ url: `${base}/docs/${slug}/reports`, changeFrequency: "monthly", priority: 0.5 }); - for (const id of reportIds(slug)) { - reportEntries.push({ url: `${base}/docs/${slug}/reports/${id}`, changeFrequency: "yearly", priority: 0.4 }); - } - } - let postEntries: MetadataRoute.Sitemap = []; try { const supabase = publicClient(); @@ -94,5 +76,5 @@ export default async function sitemap(): Promise { postEntries = []; } - return [...staticEntries, ...docEntries, ...reportEntries, ...postEntries]; + return [...staticEntries, ...docEntries, ...postEntries]; } diff --git a/apps/logicsrc-web/src/app/specs/[family]/page.tsx b/apps/logicsrc-web/src/app/specs/[family]/page.tsx new file mode 100644 index 0000000..7d6624c --- /dev/null +++ b/apps/logicsrc-web/src/app/specs/[family]/page.tsx @@ -0,0 +1,51 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import type { ReactNode } from "react"; +import type { Metadata } from "next"; +import { SiteShell } from "@/components/site-shell"; +import { SpecList } from "@/components/spec-list"; +import { FAMILIES, familyBySlug } from "@/lib/specs"; + +export function generateStaticParams(): Array<{ family: string }> { + return FAMILIES.map((f) => ({ family: f.slug })); +} + +export async function generateMetadata({ + params +}: { + params: Promise<{ family: string }>; +}): Promise { + const { family: slug } = await params; + const family = familyBySlug(slug); + if (!family) return {}; + return { + title: `${family.name} · Specs · LogicSRC`, + description: `${family.line}. ${family.specs.map((s) => s.name).join(", ")}.`, + alternates: { canonical: `/specs/${family.slug}` } + }; +} + +export default async function FamilyPage({ + params +}: { + params: Promise<{ family: string }>; +}): Promise { + const { family: slug } = await params; + const family = familyBySlug(slug); + if (!family) notFound(); + return ( + +
+
+

+ Specs · {family.name} +

+

{family.name}

+

{family.line}.

+
+

{family.blurb}

+ +
+
+ ); +} diff --git a/apps/logicsrc-web/src/app/specs/page.tsx b/apps/logicsrc-web/src/app/specs/page.tsx new file mode 100644 index 0000000..530e571 --- /dev/null +++ b/apps/logicsrc-web/src/app/specs/page.tsx @@ -0,0 +1,50 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; +import type { Metadata } from "next"; +import { SiteShell } from "@/components/site-shell"; +import { FAMILIES, familyTree } from "@/lib/specs"; + +export const metadata: Metadata = { + title: "Specs · LogicSRC", + description: + "Every LogicSRC specification in four families: people and agents, access and credentials, catalogs a site serves about itself, and agents and process. Each family drills down to its specs, each spec to an overview and the specification text.", + alternates: { canonical: "/specs" } +}; + +export default function SpecsIndex(): ReactNode { + return ( + +
+
+

LogicSRC standards surface

+

Specs

+

+ Four families. Pick one, then a spec, then read its overview or the specification text. + Every spec is CC BY 4.0, every rule degrades, and a file at a well-known URL is the whole + protocol. +

+
+
+ {FAMILIES.map((family) => ( +
+

+ + {family.name} + +

+

{family.line}.

+

+ {familyTree(family) + .map(({ spec }) => spec.name) + .join(" · ")} +

+

+ See the family +

+
+ ))} +
+
+
+ ); +} diff --git a/apps/logicsrc-web/src/components/site-shell.tsx b/apps/logicsrc-web/src/components/site-shell.tsx index a77d57a..4c14a24 100644 --- a/apps/logicsrc-web/src/components/site-shell.tsx +++ b/apps/logicsrc-web/src/components/site-shell.tsx @@ -1,48 +1,12 @@ import type { ReactNode } from "react"; import { renderInstallCommand } from "@/lib/install-command"; +import { NAV_GROUPS } from "@/lib/nav"; -// Mirrors the rail/nav from page-markup.ts so standalone routes (e.g. /blog) -// share the site chrome. Anchor links point at the homepage sections. -const NAV: Array<{ href: string; label: string; external?: boolean }> = [ - { href: "/#overview", label: "Overview" }, - { href: "/#schemas", label: "Schemas" }, - { href: "/agent-swarm", label: "Soon" }, - { href: "/agentbyte", label: "AgentByte" }, - { href: "/credential-sharing", label: "Credentials" }, - { href: "/openontology", label: "OpenOntology" }, - { href: "/opencreds", label: "OpenCreds" }, - { href: "/openswarm", label: "OpenSwarm" }, - { href: "/openprd", label: "OpenPRD" }, - { href: "/asdlc", label: "ASDLC" }, - { href: "/openprofile", label: "OpenProfile" }, - { href: "/openbroadcast", label: "OpenBroadcast" }, - { href: "/openguest", label: "OpenGuest" }, - { href: "/openmcp", label: "OpenMCP" }, - { href: "/openaccess", label: "OpenAccess" }, - { href: "/openserver", label: "OpenServer" }, - { href: "/openthreat", label: "OpenThreat" }, - { href: "/opencpu", label: "OpenCPU" }, - { href: "/openmemory", label: "OpenMemory" }, - { href: "/opengpu", label: "OpenGPU" }, - { href: "/openbandwidth", label: "OpenBandwidth" }, - { href: "/openfile", label: "OpenFile" }, - { href: "/opendisk", label: "OpenDisk" }, - { href: "/opencoupon", label: "OpenCoupon" }, - { href: "/openrecipe", label: "OpenRecipe.md" }, - { href: "/openaffiliate", label: "OpenAffiliate" }, - { href: "/#cli", label: "CLI" }, - { href: "/docs", label: "Docs" }, - { href: "/blog", label: "Blog" }, - { href: "/openspec", label: "OpenSpec" }, - { href: "/pricing", label: "Pricing" }, - { href: "/hire-us", label: "Hire Us" }, - { href: "/about", label: "About" }, - { href: "https://github.com/profullstack/logicsrc", label: "GitHub ↗", external: true }, - { href: "/terms", label: "Terms" }, - { href: "/privacy", label: "Privacy" }, - { href: "/#reference", label: "Reference" }, -]; - +/** + * The site chrome for every standalone route. The sidebar comes from + * lib/nav.ts, the same array the home page renders, so the two cannot drift. + * `active` is a label or an href; either marks the current entry. + */ export function SiteShell({ children, active, @@ -64,17 +28,25 @@ export function SiteShell({ Static content from a module constant -- nothing user-supplied. */}
@@ -96,8 +68,10 @@ export function SiteShell({ > © {new Date().getFullYear()} Profullstack, Inc. · LogicSRC + Specs Docs RSS + llms.txt Terms Privacy diff --git a/apps/logicsrc-web/src/components/spec-list.tsx b/apps/logicsrc-web/src/components/spec-list.tsx new file mode 100644 index 0000000..fa51fa1 --- /dev/null +++ b/apps/logicsrc-web/src/components/spec-list.tsx @@ -0,0 +1,53 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; +import { familyTree, type Family, type Spec } from "@/lib/specs"; + +const row: React.CSSProperties = { padding: "1rem 0", borderTop: "1px solid #e3e6e0" }; +const title: React.CSSProperties = { margin: "0 0 0.3rem", fontSize: "1.15rem", color: "#101418" }; +const line: React.CSSProperties = { color: "#41505d", margin: 0 }; +const links: React.CSSProperties = { display: "flex", gap: "0.9rem", marginTop: "0.4rem", fontSize: "0.9rem" }; +const child: React.CSSProperties = { padding: "0.55rem 0 0.55rem 1.25rem", borderLeft: "2px solid #e3e6e0", marginLeft: "0.25rem" }; + +function SpecLinks({ spec }: { spec: Spec }): ReactNode { + return ( + + {spec.landing ? Overview : null} + {spec.doc ? Specification : null} + {spec.status === "soon" ? coming soon : null} + + ); +} + +/** A family's specs, top-level ones first, each with the blocks that nest under it. */ +export function SpecList({ family }: { family: Family }): ReactNode { + return ( +
    + {familyTree(family).map(({ spec, children }) => ( +
  • +

    + + {spec.name} + +

    +

    {spec.line}.

    + + {children.length > 0 ? ( +
      + {children.map((c) => ( +
    • + + + {c.name} + + + : {c.line}. + +
    • + ))} +
    + ) : null} +
  • + ))} +
+ ); +} diff --git a/apps/logicsrc-web/src/lib/docs.ts b/apps/logicsrc-web/src/lib/docs.ts index ffe1f90..139aac4 100644 --- a/apps/logicsrc-web/src/lib/docs.ts +++ b/apps/logicsrc-web/src/lib/docs.ts @@ -1,54 +1,20 @@ import { readFileSync } from "node:fs"; import { resolve } from "node:path"; +import { docSlugs } from "./specs"; // 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 = [ - "asdlc", - "openswarm", - "opencreds", - "openprd", - "openontology", - "openontology-governance", - "openontology-interoperability", - "openjob", - "openresume", - "openprofile", - "openbroadcast", - "openguest", - "openmcp", - "openaccess", - "openserver", - "openthreat", - "openfile", - "opendisk", - "opencoupon", - "openrecipe", - "openaffiliate", - "openstream", - "opencpu", - "openmemory", - "opengpu", - "openbandwidth", - "openspec-comparison", - "data-model", - "cli", - "tui", - "config", - "permissions", - "plugins", - "credential-sharing", - "agent-screening", -] as const; +// The public docs: every spec in lib/specs.ts that has a specification text, +// then the guides listed there. Internal notes (roadmap, positioning, arcade) +// are not in the registry and so are not served. +export const DOC_SLUGS: readonly string[] = docSlugs(); -export type DocSlug = (typeof DOC_SLUGS)[number]; +export type DocSlug = string; export function isDocSlug(slug: string): slug is DocSlug { - return (DOC_SLUGS as readonly string[]).includes(slug); + return DOC_SLUGS.includes(slug); } export function readDoc(slug: string): string | null { diff --git a/apps/logicsrc-web/src/lib/nav.ts b/apps/logicsrc-web/src/lib/nav.ts new file mode 100644 index 0000000..1d79364 --- /dev/null +++ b/apps/logicsrc-web/src/lib/nav.ts @@ -0,0 +1,61 @@ +import { FAMILIES } from "./specs"; + +/** + * The sidebar, broad first. Four groups: where to start, the spec families + * (each a page that drills down to its specs), the tools, and the company. + * Rendered by the React SiteShell and by the server-string home page from the + * same array, so the two cannot drift. + */ +export type NavItem = { href: string; label: string; external?: boolean }; +export type NavGroup = { label: string; items: NavItem[] }; + +export const NAV_GROUPS: NavGroup[] = [ + { + label: "Start", + items: [ + { href: "/", label: "Overview" }, + { href: "/specs", label: "Specs" }, + { href: "/docs", label: "Docs" }, + { href: "/blog", label: "Blog" } + ] + }, + { + label: "Specs", + items: FAMILIES.map((f) => ({ href: `/specs/${f.slug}`, label: f.name })) + }, + { + label: "Tools", + items: [ + { href: "/docs/cli", label: "CLI" }, + { href: "/openspec", label: "OpenSpec.dev mode" }, + { href: "/credential-sharing", label: "Credentials" } + ] + }, + { + label: "Company", + items: [ + { href: "/pricing", label: "Pricing" }, + { href: "/hire-us", label: "Hire Us" }, + { href: "/about", label: "About" }, + { href: "https://github.com/profullstack/logicsrc", label: "GitHub ↗", external: true }, + { href: "/terms", label: "Terms" }, + { href: "/privacy", label: "Privacy" } + ] + } +]; + +/** The sidebar as a server-rendered string, for the home page template. */ +export function renderNavHtml(activeHref = "/"): string { + return NAV_GROUPS.map( + (g) => + `${g.label}` + + g.items + .map( + (i) => + `${i.label}` + ) + .join("") + ).join(""); +} diff --git a/apps/logicsrc-web/src/lib/page-markup.ts b/apps/logicsrc-web/src/lib/page-markup.ts index f28f02e..b36fba9 100644 --- a/apps/logicsrc-web/src/lib/page-markup.ts +++ b/apps/logicsrc-web/src/lib/page-markup.ts @@ -4,14 +4,8 @@ // browser. Interactivity (hire-us form, CoinPay button, section scroll) lives in // the `home-interactivity` client component. import { renderInstallCommand } from "./install-command"; - -const primitives = [ - { name: "Identity", detail: "DIDs, OAuth accounts, profiles, and organization membership." }, - { name: "Coordination", detail: "Boards, posts, threads, comments, tasks, bids, and submissions." }, - { name: "Agents", detail: "Agent profiles, capabilities, runs, logs, permissions, and audit trails." }, - { name: "Value", detail: "Payments, escrow, wallets, reputation events, and settlement hooks." }, - { name: "Events", detail: "Event streams, webhooks, schema versions, and integration audit logs." } -]; +import { renderNavHtml } from "./nav"; +import { FAMILIES, familyTree } from "./specs"; const schemas = [ { name: "logicsrc-task", path: "packages/schemas/schemas/logicsrc-task.schema.json" }, @@ -128,26 +122,7 @@ export function renderPageMarkup(): string {
${renderInstallCommand("rail")} - +
@@ -170,16 +145,18 @@ export function renderPageMarkup(): string {

Standards Surface

-

LogicSRC defines the shared language; products can implement it without owning the standard.

+

Four families of specifications. LogicSRC defines the shared language; products implement it without owning the standard.

- ${primitives.map((item) => ` + ${FAMILIES.map((family) => `
-

${item.name}

-

${item.detail}

+

${family.name}

+

${family.line}.

+

${familyTree(family).map(({ spec }) => `${spec.name}`).join(" · ")}

`).join("")}
+

Every spec, by family →

diff --git a/apps/logicsrc-web/src/lib/specs.ts b/apps/logicsrc-web/src/lib/specs.ts new file mode 100644 index 0000000..0bdaafb --- /dev/null +++ b/apps/logicsrc-web/src/lib/specs.ts @@ -0,0 +1,150 @@ +/** + * The one registry of LogicSRC specifications. + * + * Everything that lists specs reads this file: the sidebar, /specs and its + * family pages, the home page's family grid, /docs, the sitemap and llms.txt. + * Adding a spec is one entry here plus its docs/.md and, if it has one, + * its app//page.tsx. There is no second list to keep in step. + * + * The site starts broad in the sidebar (four families) and drills down: + * /specs/ lists the family's specs, / is a spec's landing page, + * /docs/ is the specification text. + */ + +export type Spec = { + slug: string; + name: string; + /** One sentence, no trailing period; shown on lists. */ + line: string; + /** Landing page path when one exists (usually /). */ + landing?: string; + /** Specification text path when one exists (usually /docs/). */ + doc?: string; + /** A block of a larger spec, listed under it. */ + parent?: string; + status?: "0.1" | "0.2" | "draft" | "soon"; +}; + +export type Family = { + slug: string; + name: string; + /** One line under the family name. */ + line: string; + /** A short paragraph on the family page. */ + blurb: string; + specs: Spec[]; +}; + +const s = ( + slug: string, + name: string, + line: string, + extra: Partial = {} +): Spec => ({ slug, name, line, landing: `/${slug}`, doc: `/docs/${slug}`, ...extra }); + +export const FAMILIES: Family[] = [ + { + slug: "people", + name: "People and agents", + line: "Who someone is, what they have done, and what they offer, in files they own", + blurb: + "One Markdown file for a person or an agent, served from their own domain and linked from every platform that has a page for them. The profile carries the identity, the accounts and the topics; the sections carry what a platform needs to match on, so a job board, a booking site or a dating app reads the file instead of asking forty questions again.", + specs: [ + s("openprofile", "OpenProfile.md", "One Markdown file for who you are and where you are, people and agents alike", { status: "0.2" }), + s("openresume", "OpenResume.md", "What you have done, in the same spirit, linked from the profile", { landing: undefined }), + s("openjob", "OpenJob", "What the work is, so a candidate's agent and a job board agree", { landing: undefined }), + s("openbroadcast", "OpenBroadcast", "The Broadcast section: the show a person hosts and who they are seeking"), + s("openguest", "OpenGuest", "The Guest section: that a person will appear, their expertise, availability and terms"), + s("agentbyte", "AgentByte", "Agent screening sessions, policy events and APIs", { doc: "/docs/agent-screening", status: "draft" }) + ] + }, + { + slug: "access", + name: "Access and credentials", + line: "Grants you can carry, and the vault the tokens live in", + blurb: + "OAuth 2.1 with a grant you can carry between apps, a portable vault format for the credentials behind an agent's accounts, and the sync architecture that moves team secrets between the places they are kept.", + specs: [ + s("openaccess", "OpenAccess", "OAuth 2.1 with a grant you can carry: one hub account, apps keep their own users, entitlements travel"), + s("opencreds", "OpenCreds", "A portable vault for the credentials behind an agent's accounts"), + s("credential-sharing", "Credential Sharing", "End-to-end-encrypted team vaults with source and target diffs, approval, sync, rollback and audit") + ] + }, + { + slug: "catalogs", + name: "Catalogs a site serves about itself", + line: "One file at a fixed URL, read by directories instead of scraped", + blurb: + "A provider, a merchant, a scanner or a relay already keeps a table of what it sells or found. Each of these is that table, exported at /.well-known/.json in a shape every reader agrees on, verified by the origin it came from. Directories such as nichedb.dev read the file; the publisher stays the author.", + specs: [ + s("openserver", "OpenServer", "One file a hosting provider serves about what it sells: every offer, its specs, price, location and stock"), + s("opencpu", "OpenCPU", "The compute block: threads against cores, allocation, and a range for what a buyer can dial", { parent: "openserver" }), + s("openmemory", "OpenMemory", "The memory block: mebibytes, DDR type, ECC as three states, allocation", { parent: "openserver" }), + s("opendisk", "OpenDisk", "The disk a machine will rent: free GiB, price per GiB-month, location, policy", { parent: "openserver" }), + s("opengpu", "OpenGPU", "The gpu block: the card by vendor name, count, VRAM, interconnect, access", { parent: "openserver" }), + s("openbandwidth", "OpenBandwidth", "The network block: port, meter, overage, IPv4 and IPv6 as a priced resource", { parent: "openserver" }), + s("openfile", "OpenFile", "One file a publisher serves about the files it has published: hash, swarm and HTTP routes", { parent: "openserver" }), + s("openmcp", "OpenMCP", "An open catalog of MCP relays: a relay serves /.well-known/openmcp.json and a catalog probes it"), + s("opencoupon", "OpenCoupon", "One file a merchant serves about what is on offer right now, expired codes kept so directories learn they died"), + s("openaffiliate", "OpenAffiliate", "One file a merchant serves about the commission it pays"), + s("openrecipe", "OpenRecipe.md", "One Markdown file that is a recipe, with schema.org derived from it and never the reverse"), + s("openthreat", "OpenThreat", "One file a security tool serves about what it found in the open: public subjects only, secrets never located") + ] + }, + { + slug: "process", + name: "Agents and process", + line: "How agents coordinate, settle, stream, and how the software that serves them gets built", + blurb: + "The lifecycle for building software when agents work in parallel and CI is the only gate, the requirement document an agent can execute, the settlement and proof layer under a swarm, a lossless byte-stream envelope, and the five nouns a shared ontology needs.", + specs: [ + s("asdlc", "ASDLC", "The Agentic Software Development Lifecycle: nine phases, four conformance levels and the ratchet rule"), + s("openprd", "OpenPRD", "A product requirement document an agent can execute and a person can read"), + s("openswarm", "OpenSwarm", "Settlement and proof of work done under a peer-to-peer swarm"), + s("openstream", "OpenStream", "A lossless byte-stream relay envelope, with benchmark reports per release", { landing: undefined }), + s("openontology", "OpenOntology", "Five nouns for a shared ontology, with governance and interoperability notes"), + s("agent-swarm", "AgentSwarm", "Provider-neutral agent orchestration, model routing and cost controls", { doc: undefined, status: "soon" }), + s("openspec", "OpenSpec.dev comparison", "How LogicSRC compares with OpenSpec.dev, and the compatibility mode", { doc: "/docs/openspec-comparison" }) + ] + } +]; + +/** Guides that are documentation rather than a specification. Listed on /docs under their own heading. */ +export const GUIDES: Array<{ slug: string; name: string }> = [ + { slug: "data-model", name: "Data model" }, + { slug: "cli", name: "CLI" }, + { slug: "tui", name: "TUI" }, + { slug: "config", name: "Config" }, + { slug: "permissions", name: "Permissions" }, + { slug: "plugins", name: "Plugins" }, + { slug: "openontology-governance", name: "OpenOntology governance" }, + { slug: "openontology-interoperability", name: "OpenOntology interoperability" } +]; + +export function allSpecs(): Spec[] { + return FAMILIES.flatMap((f) => f.specs); +} + +export function familyBySlug(slug: string): Family | undefined { + return FAMILIES.find((f) => f.slug === slug); +} + +export function familyOfSpec(slug: string): Family | undefined { + return FAMILIES.find((f) => f.specs.some((x) => x.slug === slug)); +} + +/** The docs/.md files served at /docs/: every spec that has one, then the guides. */ +export function docSlugs(): string[] { + const fromSpecs = allSpecs() + .map((x) => x.doc) + .filter((d): d is string => Boolean(d)) + .map((d) => d.replace(/^\/docs\//, "")); + return Array.from(new Set([...fromSpecs, ...GUIDES.map((g) => g.slug)])); +} + +/** Top-level specs of a family, each with the blocks that nest under it. */ +export function familyTree(family: Family): Array<{ spec: Spec; children: Spec[] }> { + return family.specs + .filter((x) => !x.parent) + .map((spec) => ({ spec, children: family.specs.filter((c) => c.parent === spec.slug) })); +} diff --git a/apps/logicsrc-web/src/styles.css b/apps/logicsrc-web/src/styles.css index 48e9705..59dcf2f 100644 --- a/apps/logicsrc-web/src/styles.css +++ b/apps/logicsrc-web/src/styles.css @@ -892,3 +892,37 @@ pre { font-size: 0.85rem; } } + +/* Sidebar groups: broad first, each entry a page that drills down. */ +.rail nav .nav-group-block { + display: contents; +} +.rail nav .nav-group { + display: block; + margin: 0.9rem 0 0.25rem; + padding: 0 0.6rem; + font-size: 0.68rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #7a8794; +} +.rail nav .nav-group:first-child { + margin-top: 0.25rem; +} +.tile h3 a { + color: inherit; + text-decoration: none; +} +.tile .tile-specs { + margin-top: 0.55rem; + font-size: 0.85rem; + line-height: 1.7; +} +.tile .tile-specs a { + color: inherit; +} +.section-foot { + margin: 1rem 0 0; + font-size: 0.95rem; +}