Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions apps/logicsrc-web/e2e/logicsrc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,40 @@ test.describe("LogicSRC PWA", () => {
await expect(page.getByText("logicsrc credentials providers")).toBeVisible();
});

test("renders top-level docs and legal route targets", async ({ page }) => {
await page.goto("/privacy");
test("links every Top-Level Pages card at its route", async ({ page }) => {
await page.goto("/");

await expect(page.getByRole("heading", { name: "Top-Level Pages" })).toBeVisible();
await expect(page.getByText("/docs · Docs")).toBeVisible();
await expect(page.getByText("/blog · Blog")).toBeVisible();
await expect(page.getByText("/credential-sharing · Credential Sharing")).toBeVisible();
await expect(page.getByText("/terms · Terms")).toBeVisible();
await expect(page.getByText("/privacy · Privacy")).toBeVisible();

// Each card advertises a stable route, so each card has to be a link to it.
for (const route of [
"docs",
"blog",
"openspec",
"credential-sharing",
"hire-us",
"about",
"terms",
"privacy",
]) {
await expect(
page.locator(`#page-${route} a[href="/${route}"]`),
`/${route} card should link to /${route}`,
).toBeVisible();
}
});

test("renders a real Privacy page rather than the homepage", async ({ page }) => {
await page.goto("/privacy");

await expect(page.getByRole("heading", { name: "Privacy", exact: true })).toBeVisible();
await expect(page.getByRole("heading", { name: "Analytics" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Cookies" })).toBeVisible();
await expect(page.getByText("Secret values are encrypted end-to-end")).toBeVisible();

// The old /privacy fell through to [[...slug]] and served the whole
// homepage; the Top-Level Pages band is the tell that it regressed.
await expect(page.getByRole("heading", { name: "Top-Level Pages" })).toHaveCount(0);
});

test("renders Hire Us project request flow", async ({ page }) => {
Expand Down
10 changes: 6 additions & 4 deletions apps/logicsrc-web/src/app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import { HomeInteractivity } from "@/components/home-interactivity";
// 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
// anything else.
// /about, /docs, /pricing, and /terms are now real routes (app/about, app/docs,
// app/pricing, app/terms); the rest still render the homepage SPA scrolled to
// their section.
// /about, /docs, /pricing, /privacy, and /terms are now real routes
// (app/about, app/docs, app/pricing, app/privacy, app/terms); the rest still
// render the homepage SPA scrolled to their section. Every key left here MUST
// have a matching section id in `renderPageMarkup` -- /privacy used to be
// listed without one, so it served the whole homepage and scrolled to a card
// that only described the page that did not exist.
const ROUTE_META: Record<string, { title: string; description: string }> = {
openspec: {
title: "LogicSRC vs OpenSpec.dev · LogicSRC",
Expand All @@ -24,7 +27,6 @@ const ROUTE_META: Record<string, { title: string; description: string }> = {
title: "Hire Us · LogicSRC",
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $400/hour for accepted work, paid via CoinPay.",
},
privacy: { title: "Privacy · LogicSRC", description: "LogicSRC privacy notes." },
"agent-swarm": {
title: "AgentSwarm · LogicSRC",
description: "Provider-neutral agent orchestration with model routing, cost controls, and GitHub integration.",
Expand Down
162 changes: 162 additions & 0 deletions apps/logicsrc-web/src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import type { ReactNode } from "react";
import type { Metadata } from "next";
import { SiteShell } from "@/components/site-shell";

export const metadata: Metadata = {
title: "Privacy · LogicSRC",
description:
"What logicsrc.com collects and what it does not: privacy-friendly analytics, the Hire Us project form, the CoinPay sign-in cookie, and the boundary that keeps credential values off our servers.",
alternates: { canonical: "/privacy" },
};

export default function PrivacyPage(): ReactNode {
return (
<SiteShell active="Privacy">
<article className="band" style={{ maxWidth: "48rem" }}>
<div className="section-head">
<h2>Privacy</h2>
<p>
This page describes what logicsrc.com does with data. It covers the
marketing site and the credentials app; it does not restrict how you
use the specification, schemas, or CLI, which run on your own
machines and report nothing back to us.
</p>
</div>

<div className="blog-content" style={{ lineHeight: 1.7, marginTop: "1.5rem" }}>
<h3>Who is responsible</h3>
<p>
Profullstack, Inc. operates logicsrc.com and is responsible for the
data described here. For any privacy question, or to ask us to
delete something you sent us, email{" "}
<a href="mailto:privacy@profullstack.com">privacy@profullstack.com</a>
. Security reports go to{" "}
<a href="mailto:security@profullstack.com">security@profullstack.com</a>{" "}
(see <a href="/.well-known/security.txt">security.txt</a>).
</p>

<h3>There is no account required to read this site</h3>
<p>
Browsing the specification, docs, blog, and schema pages requires no
account, no sign-in, and no cookie. We do not run advertising
trackers, we do not sell or share data with data brokers, and we do
not build advertising profiles.
</p>

<h3>Analytics</h3>
<p>
Page views are measured with{" "}
<a href="https://crawlproof.com" rel="noreferrer">CrawlProof</a>, a
privacy-friendly analytics service that records aggregate page
traffic — pages visited, referrer, rough geography, and device
class. It does not set advertising cookies and does not follow you
across other sites. We use it to see which specs and docs people
actually read.
</p>

<h3>The Hire Us form</h3>
<p>
If you submit a project through <a href="/hire-us">Hire Us</a>, you
send us two things: the contact address you type, and your
description of the work. We use them only to evaluate and reply to
your request, and to scope an engagement if it is a fit. We do not
add the address to a marketing list. Ask us to delete a request and
we will.
</p>

<h3>Payments and the CoinPay sign-in</h3>
<p>
Paid work is invoiced through CoinPay. If you
connect a CoinPay account, we complete an OAuth sign-in and store
the resulting session in a signed, <code>HttpOnly</code>,{" "}
<code>SameSite=Lax</code> cookie so the site can show you as
connected. That session holds your CoinPay identifier and the
profile fields CoinPay returns (typically name and email). Clearing
your cookies ends it.
</p>
<p>
Card numbers and cryptocurrency wallet credentials are entered on
CoinPay, not here. We never receive them. CoinPay&apos;s handling of
your payment data is governed by CoinPay&apos;s own privacy policy.
</p>

<h3>Credential Sharing: what we never receive</h3>
<p>
The LogicSRC credentials app exists to sync secrets between
providers, so the boundary matters. Secret <em>values</em> are
encrypted end-to-end on your device before they are stored. We hold
ciphertext we cannot read. Audit records — who synced which key
name, to which provider, when, and the resulting key fingerprint —
are stored in readable form so that a sync is reviewable; key names
and fingerprints are recorded, secret values are not.
</p>
<p>
The <code>logicsrc</code> CLI and TUI run locally. Running a spec
validation, a schema check, or a credentials dry run sends nothing
to us. Only commands that explicitly talk to a hosted endpoint — for
example <code>logicsrc login</code> — make a network call.
</p>

<h3>Server logs</h3>
<p>
Like any web service, our hosting produces operational logs
containing IP addresses, timestamps, request paths, and user-agent
strings. They are used to keep the service running and to
investigate abuse and outages, and they are not used to profile
visitors.
</p>

<h3>Cookies</h3>
<p>
We set cookies only for functions you initiate: a short-lived
state cookie during a CoinPay OAuth round trip, and the CoinPay
session cookie described above. There are no advertising or
cross-site tracking cookies. The site also registers a service
worker for offline page caching; it stores pages in your own
browser and sends us nothing.
</p>

<h3>Third parties we rely on</h3>
<p>
Our hosting provider, our database provider, CrawlProof for
analytics, and CoinPay for payments process data on our behalf in
order to run the service. We do not sell personal data, and we do
not share it with anyone else except where we are legally required
to.
</p>

<h3>Retention</h3>
<p>
Project requests are kept while an engagement is live and afterward
only as long as we need them for tax and accounting records.
Operational logs roll off on our provider&apos;s normal schedule.
Analytics data is aggregate and is not tied back to you.
</p>

<h3>Your choices</h3>
<p>
You can ask us for a copy of what we hold about you, ask us to
correct it, or ask us to delete it — write to{" "}
<a href="mailto:privacy@profullstack.com">privacy@profullstack.com</a>{" "}
and we will act on it. Deleting an encrypted credential removes the
ciphertext; the corresponding audit record is retained, because an
audit trail that can be edited is not an audit trail.
</p>

<h3>Children</h3>
<p>
This is a developer tool and is not directed at children. We do not
knowingly collect data from anyone under 16.
</p>

<h3>Changes</h3>
<p>
We will update this page as the hosted products grow. Material
changes will be noted in the <a href="/blog">blog</a>. See also our{" "}
<a href="/terms">Terms</a>.
</p>
</div>
</article>
</SiteShell>
);
}
7 changes: 4 additions & 3 deletions apps/logicsrc-web/src/components/home-interactivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ export function HomeInteractivity(): null {
} else if (pathname === "/agentbyte") {
document.querySelector("#agentbyte")?.scrollIntoView();
} else {
// Only the paths [[...slug]] still serves from the homepage belong here.
// /docs, /blog, /about, /privacy, and /terms are real routes now, and
// listing them just aimed scrollIntoView at a Top-Level Pages card.
const pageRoute = pathname.slice(1);
if (
["docs", "blog", "openspec", "credential-sharing", "hire-us", "about", "terms", "privacy"].includes(pageRoute)
) {
if (["openspec", "credential-sharing", "hire-us"].includes(pageRoute)) {
document.querySelector(`#${pageRoute}`)?.scrollIntoView();
}
}
Expand Down
11 changes: 8 additions & 3 deletions apps/logicsrc-web/src/lib/page-markup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ const hireUsWork = [
{ name: "Open infrastructure", detail: "Portable code and specs first: no closed workflow lock-in, no one-off agent scripts that cannot be audited." }
];

// The stable top-level routes, each a real page. `id` is the route segment, so
// the card links straight to it. The rendered article ids are prefixed
// (`page-docs`) because three of these -- openspec, credential-sharing, hire-us
// -- are also section ids further up this same document, and duplicate ids made
// the in-page scroll targets ambiguous.
const pages = [
{ id: "docs", title: "Docs", detail: "Specification guides, CLI conventions, schemas, plugin contracts, SDK conventions, and MCP resources." },
{ id: "blog", title: "Blog", detail: "Project notes for LogicSRC, AgentSwarm, AgentByte, OpenSpec workflows, and reference implementations." },
Expand All @@ -73,7 +78,7 @@ const pages = [
{ id: "hire-us", title: "Hire Us", detail: "$400/hour LogicSRC work on open infrastructure, specs, AI agent workflows, and reference implementations paid through CoinPay after project acceptance." },
{ id: "about", title: "About", detail: "LogicSRC is the Profullstack open specification project for human and AI agent coordination." },
{ id: "terms", title: "Terms", detail: "Terms of engagement: the $400/hour rate, what is billable, how approved hours are invoiced, the 10-hour minimum, cancellation, acceptable use, and reference implementation boundaries." },
{ id: "privacy", title: "Privacy", detail: "Draft privacy notes will cover telemetry, audit events, identity data, and hosted-product data boundaries." }
{ id: "privacy", title: "Privacy", detail: "What this site collects and what it does not: analytics, the Hire Us form, the CoinPay sign-in cookie, and where credential data does and does not go." }
];

const comparisonRows = [
Expand Down Expand Up @@ -419,10 +424,10 @@ COINPAY_STATUS=pending_acceptance</code></pre>
</div>
<div class="implementation-list">
${pages.map((page) => `
<article id="${page.id}">
<article id="page-${page.id}">
<span></span>
<div>
<h3>/${page.id} · ${page.title}</h3>
<h3><a href="/${page.id}">/${page.id} · ${page.title}</a></h3>
<p>${page.detail}</p>
</div>
</article>
Expand Down
Loading