mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 14:37:26 +00:00
fix(web): the Top-Level Pages cards led nowhere, and /privacy was not a page (#114)
The "Top-Level Pages" band advertises eight stable routes, but the cards were plain <h3> text with no anchors -- nothing on that band was clickable. Wrap each card title in a link to its route. /privacy was the worst of the eight. It had no page and no homepage section, so it fell through to [[...slug]], which served the entire homepage (82KB, byte-identical to /openspec, /credential-sharing, and /hire-us) and then scrolled to the card that merely described the page that did not exist. Give it a real page covering what the site actually does: CrawlProof analytics, the Hire Us form, the CoinPay OAuth session cookie, and the credshare boundary -- ciphertext and salted-hash fingerprints are stored, secret values never reach the server. The cards also reused the ids openspec, credential-sharing, and hire-us, which already name sections further up the same document. Duplicate ids made those scroll targets ambiguous, so the cards are now page-<route>. With that, the scroll list in home-interactivity only needs the three routes [[...slug]] still serves; docs, blog, about, terms, and privacy are real routes and were only ever aiming scrollIntoView at a card. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bb09c145cd
commit
f9ebf9b342
5 changed files with 212 additions and 17 deletions
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -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.",
|
||||
|
|
|
|||
162
apps/logicsrc-web/src/app/privacy/page.tsx
Normal file
162
apps/logicsrc-web/src/app/privacy/page.tsx
Normal file
|
|
@ -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's handling of
|
||||
your payment data is governed by CoinPay'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'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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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." },
|
||||
|
|
@ -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 = [
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue