diff --git a/CHANGELOG.md b/CHANGELOG.md index ca90670..5bd7ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ Every version listed here must correspond to a slice in [`PLAN.md`](./PLAN.md) w --- +## [0.9.7] — 2026-05-28 + +### Added +- **Privacy Policy and Terms of Service.** Plain-language legal pages at `/privacy` and `/terms`, linked from a new site-wide footer. + +--- + ## [0.9.6] — 2026-05-28 ### Added diff --git a/PLAN.md b/PLAN.md index e903498..73a6dbb 100644 --- a/PLAN.md +++ b/PLAN.md @@ -45,7 +45,7 @@ | **v0.9.4** | DB pool size env-tunable + real back-nav spinner fix | ✅ shipped | | **v0.9.5** | Security review + hardening (OAuth scope ↓ `read:user`, HTTP security headers) | ✅ shipped | | **v0.9.6** | Load-test harness (warm /analyze; full 100 RPS run = operator step) | ✅ shipped | -| **v0.9.7** | Privacy policy + terms (legal docs) | pending | +| **v0.9.7** | Privacy policy + terms + global footer | ✅ shipped | | **v1.0.0** | Public launch | pending | --- @@ -688,11 +688,20 @@ The narrative-mode CHECK constraint was a third drift in the same family — the --- -## v0.9.7 — Legal docs (deferred) +## v0.9.7 — Privacy + Terms (shipped 2026-05-28) -**Goal:** Privacy policy + terms in `docs/legal/`. Link from frontend footer. +**Goal:** Plain-language Privacy Policy + Terms of Service pages, linked from a new global footer. -**Exit criteria:** TBD when the slice begins. +**Delivered:** Static TSX pages `/privacy` + `/terms` (shared `LegalProse` wrapper), a global `SiteFooter`, content grounded in the app's real data practices (GitHub `read:user`, Neon, Upstash, Groq, Sentry, PostHog), India governing law, 13+, contact shaansatsangi.cse@gmail.com. Not legal advice — flagged for professional review before launch. + +**Design spec:** [`docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md`](./docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md). +**Sub-plan:** [`docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md`](./docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md). + +**Exit criteria:** +- [x] `/privacy` + `/terms` render via `LegalProse`; metadata + "Last updated 2026-05-28". +- [x] Global `SiteFooter` links both (+ GitHub); landing hero intact; mobile verified. +- [x] Smoke tests pass (3); frontend lint/tsc/test/build clean. +- [x] Docs ritual + version bump to 0.9.7; tag + release. --- diff --git a/README.md b/README.md index e1aae9c..0120e7b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Engineering insight first. AI flavor second. Scoring is deterministic and explai ## Status -Pre-alpha. Latest shipped release is **v0.9.6** (a reusable load-test harness for the warm `/analyze` path; the full 100 RPS run is an operator step). v0.9.5 before it ran a full pre-launch security audit — no high/critical findings — tightening the GitHub OAuth scope to read-only and adding HTTP security headers; v0.9.4 made the DB connection pool size env-tunable and genuinely fixed the back-nav search spinner; v0.9.3 added deletable `/me` history with undo, a golden "creator" scorecard for the project's creator account, and a first (incomplete) attempt at the back-nav spinner fix. Live at https://skill-issue-tau.vercel.app — GitHub OAuth sign-in, Neon Postgres persistence, `/me` history, opt-in `/share/[slug]` public links. The AI narrative layer (Roast + Mentor) runs on **Groq** (`llama-3.3-70b-versatile`). v0.7.0 added Upstash Redis caching (warm `/analyze` ≤ 200 ms); v0.7.2 prod-certified the perf budget (CLS 0.080 → **0** structurally, perf 90 → 94, LCP 2,804 → 2,773 ms); v0.8.0 shipped Sentry (FE+BE), PostHog (events + web vitals), structlog JSON logging, on-voice 404, and a full axe a11y pass; v0.8.1 ships the nightly cron with bearer auth; v0.8.2 pairs it with the manual force-refresh button on `/me`; v0.8.3 hotfixes the empty-repo crash; v0.8.4 fixes the silent narrative misattribution; v0.8.5 closes the post-deploy-Sentry loop with a pre-merge CI gate; v0.8.6 closes v0.7.1's deferred share-page caching; v0.8.7 modernizes project config; v0.9.0 opens Beta hardening with bounded GH fan-out; v0.9.1 closes the /me N+1 + adds per-namespace Report cache versioning; v0.9.2 adds rate limiting (per-IP for anonymous, higher per-user caps for signed-in) on `/analyze` and `/narrative`; v0.9.3 adds deletable `/me` history with undo, attempts the back-nav search-spinner fix, and gilds the creator's scorecard. v0.9.4 makes the DB connection pool size env-tunable (defaults unchanged — RUM showed no pool exhaustion) and lands the real back-nav spinner fix (the v0.9.3 attempt addressed the wrong mechanism); v0.9.5 runs a full pre-launch security audit (no high/critical findings), tightens the OAuth scope to `read:user`, and adds HTTP security headers; v0.9.6 adds a reusable load-test harness for the warm `/analyze` path (the full 100 RPS run is an operator step). **v0.9.7 — privacy policy + terms** is next. See [`CHANGELOG.md`](./CHANGELOG.md) for shipped slices, [`PLAN.md`](./PLAN.md) for the full roadmap, and [`docs/PROGRESS_LOG.md`](./docs/PROGRESS_LOG.md) for the most recent session handoff. +Pre-alpha. Latest shipped release is **v0.9.7** (a Privacy Policy and Terms of Service, linked from a new site-wide footer). v0.9.6 before it added a reusable load-test harness for the warm `/analyze` path (full 100 RPS run is an operator step); v0.9.5 ran a full pre-launch security audit — no high/critical findings — tightening the GitHub OAuth scope to read-only and adding HTTP security headers; v0.9.4 made the DB connection pool size env-tunable and genuinely fixed the back-nav search spinner; v0.9.3 added deletable `/me` history with undo, a golden "creator" scorecard for the project's creator account, and a first (incomplete) attempt at the back-nav spinner fix. Live at https://skill-issue-tau.vercel.app — GitHub OAuth sign-in, Neon Postgres persistence, `/me` history, opt-in `/share/[slug]` public links. The AI narrative layer (Roast + Mentor) runs on **Groq** (`llama-3.3-70b-versatile`). v0.7.0 added Upstash Redis caching (warm `/analyze` ≤ 200 ms); v0.7.2 prod-certified the perf budget (CLS 0.080 → **0** structurally, perf 90 → 94, LCP 2,804 → 2,773 ms); v0.8.0 shipped Sentry (FE+BE), PostHog (events + web vitals), structlog JSON logging, on-voice 404, and a full axe a11y pass; v0.8.1 ships the nightly cron with bearer auth; v0.8.2 pairs it with the manual force-refresh button on `/me`; v0.8.3 hotfixes the empty-repo crash; v0.8.4 fixes the silent narrative misattribution; v0.8.5 closes the post-deploy-Sentry loop with a pre-merge CI gate; v0.8.6 closes v0.7.1's deferred share-page caching; v0.8.7 modernizes project config; v0.9.0 opens Beta hardening with bounded GH fan-out; v0.9.1 closes the /me N+1 + adds per-namespace Report cache versioning; v0.9.2 adds rate limiting (per-IP for anonymous, higher per-user caps for signed-in) on `/analyze` and `/narrative`; v0.9.3 adds deletable `/me` history with undo, attempts the back-nav search-spinner fix, and gilds the creator's scorecard. v0.9.4 makes the DB connection pool size env-tunable (defaults unchanged — RUM showed no pool exhaustion) and lands the real back-nav spinner fix (the v0.9.3 attempt addressed the wrong mechanism); v0.9.5 runs a full pre-launch security audit (no high/critical findings), tightens the OAuth scope to `read:user`, and adds HTTP security headers; v0.9.6 adds a reusable load-test harness for the warm `/analyze` path (the full 100 RPS run is an operator step); v0.9.7 adds a Privacy Policy and Terms of Service, linked from a new global footer. **v1.0.0 — public launch** is next. See [`CHANGELOG.md`](./CHANGELOG.md) for shipped slices, [`PLAN.md`](./PLAN.md) for the full roadmap, and [`docs/PROGRESS_LOG.md`](./docs/PROGRESS_LOG.md) for the most recent session handoff. --- @@ -76,7 +76,7 @@ cp .env.example .env # then edit .env and add your GITHUB_TOKEN and OPENA uv run uvicorn app.main:app --reload --port 8000 ``` -Verify: `curl http://localhost:8000/health` → `{"status":"ok","version":"0.9.6","db":"up"|"down","cache":"up"|"down"|"unconfigured"}`. The `db` field reports DB reachability when `DATABASE_URL` is configured; the `cache` field reports Upstash reachability (`unconfigured` when `UPSTASH_REDIS_REST_URL` isn't set — perfectly fine for local dev, the in-process fallback covers it). +Verify: `curl http://localhost:8000/health` → `{"status":"ok","version":"0.9.7","db":"up"|"down","cache":"up"|"down"|"unconfigured"}`. The `db` field reports DB reachability when `DATABASE_URL` is configured; the `cache` field reports Upstash reachability (`unconfigured` when `UPSTASH_REDIS_REST_URL` isn't set — perfectly fine for local dev, the in-process fallback covers it). Hit the analyzer: `curl http://localhost:8000/analyze/octocat`. ### Frontend (`:3000`) diff --git a/backend/app/settings.py b/backend/app/settings.py index d758622..fe7ff14 100644 --- a/backend/app/settings.py +++ b/backend/app/settings.py @@ -2,7 +2,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict -VERSION = "0.9.6" +VERSION = "0.9.7" class Settings(BaseSettings): diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 7e2f000..d1043bb 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "skill-issue-backend" -version = "0.9.6" +version = "0.9.7" description = "Skill Issue backend — FastAPI service that ingests a GitHub profile and returns a deterministic engineering report." readme = "README.md" authors = [ diff --git a/backend/uv.lock b/backend/uv.lock index dde96b9..cca62ef 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -906,7 +906,7 @@ fastapi = [ [[package]] name = "skill-issue-backend" -version = "0.9.6" +version = "0.9.7" source = { virtual = "." } dependencies = [ { name = "alembic" }, diff --git a/docs/PROGRESS_LOG.md b/docs/PROGRESS_LOG.md index 61eb2eb..40a6410 100644 --- a/docs/PROGRESS_LOG.md +++ b/docs/PROGRESS_LOG.md @@ -19,6 +19,35 @@ Format: --- +## 2026-05-28 — Claude (Opus 4.7) — v0.9.7 shipped (privacy + terms) + +**Slice:** v0.9.7 — the final pre-1.0 slice. Privacy Policy + Terms of Service pages + a new global footer. + +**Done:** +- **`/privacy` + `/terms`** — static TSX server-component pages (prerender as `○ Static`) using a shared `LegalProse`/`LegalSection` wrapper (`frontend/src/components/legal-prose.tsx`). Content is lightweight + honest, grounded in the app's real data flows (GitHub `read:user`, saved analyses in Neon, IP for rate-limiting, Upstash caches, Groq narrative, Sentry/PostHog) — India governing law, 13+, contact shaansatsangi.cse@gmail.com. +- **Global `SiteFooter`** (`frontend/src/components/site-footer.tsx`) wired into `app/layout.tsx` (`mt-auto`, bottom of the flex-col body): Privacy · Terms · GitHub. +- **3 smoke tests** (`legal-pages.test.tsx`): each page heading + contact/governing-law text + footer links. Frontend vitest 54 → 57. +- `docs/legal/README.md` pointer (single source of truth = the TSX pages; no markdown duplicate to drift). Docs ritual + version bump to 0.9.7. + +**Decisions:** +- **Static TSX over markdown** — no rendering dependency, full design control, SEO/PPR-friendly, single source of truth. +- **Static footer year, not `new Date()`** — under Cache Components (`cacheComponents: true`), `new Date().getFullYear()` in a prerendered server component trips the Next 16 prerender guard (needs a Suspense boundary). The first implementer worked around it with a `"use client"` `CopyrightYear` + Suspense; simplified to a hardcoded `© 2026` (YAGNI — a client component + boundary for a constant is over-engineering; the legal pages already carry a dated "Last updated"). +- **Lightweight, India, 13+, contact shaansatsangi.cse@gmail.com** per the user's brainstorm answers. Operator = Shaan Satsangi (individual). +- **Not legal advice** — drafts grounded in real practices; flagged for professional review before public launch. + +**Learned / surprises:** +- **Next 16 + Cache Components blocks `new Date()` in prerendered server components** (and even a client component using it without a Suspense boundary above). For trivial dynamic values like a copyright year, a static constant is the clean fix rather than a Suspense+client dance. + +**Verified:** +- Frontend `lint` + `tsc` clean; vitest 57 passed; `next build` clean with `/privacy` + `/terms` as `○ Static`. Backend untouched (290 still pass after the version bump). +- Footer visual check (landing hero intact, mobile stacking): operator quick-confirm on prod after deploy. + +**Blocked / open:** professional legal review recommended before relying on the docs at public launch. + +**Next:** v1.0.0 — public launch. + +--- + ## 2026-05-28 — Claude (Opus 4.7) — v0.9.6 shipped (load-test harness) **Slice:** v0.9.6. Reusable backend load-test harness + runbook; the full 100 RPS validation run is an operator step (hardware-gated). Split from the original v0.9.5 "security review + load test"; legal docs are now v0.9.7. diff --git a/docs/legal/README.md b/docs/legal/README.md new file mode 100644 index 0000000..609a492 --- /dev/null +++ b/docs/legal/README.md @@ -0,0 +1,11 @@ +# Legal + +The Privacy Policy and Terms of Service are maintained as rendered pages (single +source of truth — no markdown duplicate to drift): + +- Privacy Policy: [`frontend/src/app/privacy/page.tsx`](../../frontend/src/app/privacy/page.tsx) → `/privacy` +- Terms of Service: [`frontend/src/app/terms/page.tsx`](../../frontend/src/app/terms/page.tsx) → `/terms` + +Last updated: 2026-05-28. Lightweight, plain-language drafts grounded in the +app's actual data practices — **not legal advice**; have a professional review +before relying on them. diff --git a/docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md b/docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md new file mode 100644 index 0000000..dd526d5 --- /dev/null +++ b/docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md @@ -0,0 +1,562 @@ +# v0.9.7 — Privacy + Terms Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Ship styled, indexable `/privacy` and `/terms` pages (lightweight, honest, grounded in the app's real data practices) linked from a new global footer. + +**Architecture:** Static TSX server-component pages using a shared `LegalProse`/`LegalSection` presentational wrapper; a new global `SiteFooter` in the root layout. No new dependency, no backend change. + +**Tech Stack:** Next.js 16 App Router, React 19, Tailwind v4, TypeScript, vitest + Testing Library. + +**Spec:** [`docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md`](../specs/2026-05-28-v0.9.7-legal-docs-design.md). + +--- + +## File structure + +| File | Responsibility | Action | +| --- | --- | --- | +| `frontend/src/components/legal-prose.tsx` | `LegalProse` (page shell) + `LegalSection` (styled section) | Create | +| `frontend/src/components/site-footer.tsx` | Global footer with Privacy · Terms · GitHub links | Create | +| `frontend/src/app/layout.tsx` | Render `` at the bottom of `` | Modify | +| `frontend/src/app/privacy/page.tsx` | Privacy Policy page + metadata | Create | +| `frontend/src/app/terms/page.tsx` | Terms of Service page + metadata | Create | +| `frontend/src/components/__tests__/legal-pages.test.tsx` | Smoke tests (pages render; footer links) | Create | +| `docs/legal/README.md` | Pointer to the rendered pages | Create | +| Version literals + CHANGELOG + PLAN + PROGRESS_LOG | Release ritual (0.9.6 → 0.9.7) | Modify | + +All frontend commands run from `frontend/`. + +--- + +### Task 1: Shared prose wrapper + global footer + +**Files:** +- Create: `frontend/src/components/legal-prose.tsx`, `frontend/src/components/site-footer.tsx` +- Modify: `frontend/src/app/layout.tsx` + +- [ ] **Step 1: Create `legal-prose.tsx`** + +```tsx +import type { ReactNode } from "react"; + +export function LegalProse({ + title, + lastUpdated, + children, +}: { + title: string; + lastUpdated: string; + children: ReactNode; +}) { + return ( +
+

{title}

+

Last updated {lastUpdated}

+
+ {children} +
+
+ ); +} + +export function LegalSection({ heading, children }: { heading: string; children: ReactNode }) { + return ( +
+

{heading}

+ {children} +
+ ); +} +``` + +- [ ] **Step 2: Create `site-footer.tsx`** + +```tsx +import Link from "next/link"; + +export function SiteFooter() { + return ( + + ); +} +``` + +- [ ] **Step 3: Wire the footer into the layout** + +In `frontend/src/app/layout.tsx`: add the import `import { SiteFooter } from "@/components/site-footer";` alongside the other component imports, and render `` as the LAST child of ``, after the `` closing tag. The `` should read: + +```tsx + + + + {children} + + + +``` + +- [ ] **Step 4: Typecheck + build (verify the footer doesn't break the layout)** + +Run: `npx tsc --noEmit` +Expected: clean. + +Run: `npm run build` +Expected: build succeeds; `/` still listed (static), no new errors. + +- [ ] **Step 5: Visual check (manual)** + +Start `npm run dev`, open `/` — confirm the landing hero is still centered and the footer sits unobtrusively at the bottom; check at a mobile width (~375px) that the footer stacks (column) and reads cleanly. (If the dev server can't run in this environment, note it — the build + later test cover structure; visual is the operator's quick confirm.) + +- [ ] **Step 6: Commit** + +```bash +git add frontend/src/components/legal-prose.tsx frontend/src/components/site-footer.tsx frontend/src/app/layout.tsx +git commit -m "feat(v0.9.7): global site footer + legal-prose wrapper" +``` + +--- + +### Task 2: Privacy Policy page + +**Files:** +- Create: `frontend/src/app/privacy/page.tsx` + +- [ ] **Step 1: Create the page** + +```tsx +import type { Metadata } from "next"; +import { LegalProse, LegalSection } from "@/components/legal-prose"; + +export const metadata: Metadata = { + title: "Privacy Policy — Skill Issue", + description: "What Skill Issue collects, how it's used, and your choices.", +}; + +const EMAIL = "shaansatsangi.cse@gmail.com"; + +export default function PrivacyPage() { + return ( + +

+ Skill Issue is operated by Shaan Satsangi (“we”). This policy explains, in plain + language, what we collect, why, and the choices you have. We collect as little as we need to + run the service. +

+ + +
    +
  • + GitHub profile (if you sign in). We use + GitHub OAuth with read-only read:user scope to read your public profile + (login, name, avatar). Your GitHub access token is encrypted at rest. +
  • +
  • + Analyses and history. When signed in, the + analyses you run are saved to your history until you delete them. +
  • +
  • + IP address. Used transiently to rate-limit + requests and prevent abuse. +
  • +
  • + Usage and error data. Aggregate product + analytics and error reports help us keep the service working. +
  • +
  • + A session cookie. Keeps you signed in. +
  • +
+
+ + +

+ To generate and display your report, save your history when you’re signed in, prevent + abuse, and improve reliability. We do not sell your data or use it for advertising. +

+
+ + +

We rely on a few service providers to run Skill Issue:

+
    +
  • GitHub — the source of the public data we analyze.
  • +
  • Vercel — hosting.
  • +
  • Neon — database (accounts, saved analyses).
  • +
  • Upstash — short-lived caching and rate-limit counters.
  • +
  • Groq — generates the AI narrative from your report.
  • +
  • Sentry — error monitoring.
  • +
  • PostHog — product analytics.
  • +
+
+ + +

+ Sign-in sessions expire after about 30 days. Saved analyses stay until you delete them. + Caches are short-lived and expire automatically. +

+
+ + +

+ You can delete saved analyses from your history, sign out at any time, and revoke + Skill Issue’s access from your{" "} + + GitHub application settings + + . +

+
+ + +

+ We use a session cookie to keep you signed in and a short-lived cookie during the GitHub + sign-in flow. We do not use advertising or cross-site tracking cookies. +

+
+ + +

Skill Issue is intended for users aged 13 and older, consistent with GitHub.

+
+ + +

+ We may update this policy; the “last updated” date above reflects the latest + version. +

+
+ + +

+ Questions? Email{" "} + + {EMAIL} + + . +

+
+
+ ); +} +``` + +- [ ] **Step 2: Typecheck** + +Run: `npx tsc --noEmit` +Expected: clean. + +- [ ] **Step 3: Commit** + +```bash +git add frontend/src/app/privacy/page.tsx +git commit -m "feat(v0.9.7): privacy policy page" +``` + +--- + +### Task 3: Terms of Service page + +**Files:** +- Create: `frontend/src/app/terms/page.tsx` + +- [ ] **Step 1: Create the page** + +```tsx +import type { Metadata } from "next"; +import { LegalProse, LegalSection } from "@/components/legal-prose"; + +export const metadata: Metadata = { + title: "Terms of Service — Skill Issue", + description: "The terms for using Skill Issue.", +}; + +const EMAIL = "shaansatsangi.cse@gmail.com"; + +export default function TermsPage() { + return ( + +

+ By using Skill Issue (operated by Shaan Satsangi), you agree to these terms. If you + don’t agree, please don’t use the service. +

+ + +

+ Skill Issue produces an opinionated, automated analysis of a public GitHub profile for + insight and entertainment. The scores and commentary are generated by software and may be + incomplete or wrong. Skill Issue is not an + authoritative hiring, credit, employment, or background-check tool, and should not be used + to make decisions about people. +

+
+ + +

+ Signing in is optional and uses GitHub OAuth. You are responsible for activity under your + GitHub account and for keeping it secure. +

+
+ + +

You agree not to:

+
    +
  • abuse, overload, or attempt to disrupt the service;
  • +
  • scrape it or access it through automated means outside normal use;
  • +
  • circumvent rate limits or security measures;
  • +
  • use it to harass others or for unlawful purposes.
  • +
+
+ + +

+ The service, its design, and its scoring system belong to us. GitHub data shown in a report + belongs to its respective owners and GitHub; we present it for analysis. +

+
+ + +

+ The service is provided “as is” and “as available,” without + warranties of any kind. Scores are subjective, automated estimates and are not guarantees + of skill, quality, or fitness for any purpose. +

+
+ + +

+ To the maximum extent permitted by law, we are not liable for any indirect, incidental, or + consequential damages arising from your use of the service. +

+
+ + +

+ We may suspend or limit access that abuses the service. You can stop using it and delete + your saved data at any time. +

+
+ + +

These terms are governed by the laws of India.

+
+ + +

+ We may update these terms; the “last updated” date above reflects the latest + version. Continued use means you accept the changes. +

+
+ + +

+ Questions? Email{" "} + + {EMAIL} + + . +

+
+
+ ); +} +``` + +- [ ] **Step 2: Typecheck** + +Run: `npx tsc --noEmit` +Expected: clean. + +- [ ] **Step 3: Commit** + +```bash +git add frontend/src/app/terms/page.tsx +git commit -m "feat(v0.9.7): terms of service page" +``` + +--- + +### Task 4: Smoke tests + docs/legal pointer + +**Files:** +- Create: `frontend/src/components/__tests__/legal-pages.test.tsx`, `docs/legal/README.md` + +- [ ] **Step 1: Write the tests** + +```tsx +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import PrivacyPage from "@/app/privacy/page"; +import TermsPage from "@/app/terms/page"; +import { SiteFooter } from "@/components/site-footer"; + +describe("legal pages", () => { + it("privacy page shows its heading and the contact email", () => { + render(); + expect( + screen.getByRole("heading", { name: /privacy policy/i, level: 1 }), + ).toBeInTheDocument(); + expect(screen.getByText("shaansatsangi.cse@gmail.com")).toBeInTheDocument(); + }); + + it("terms page shows its heading and the governing law", () => { + render(); + expect( + screen.getByRole("heading", { name: /terms of service/i, level: 1 }), + ).toBeInTheDocument(); + expect(screen.getByText(/laws of India/i)).toBeInTheDocument(); + }); + + it("footer links to privacy and terms", () => { + render(); + expect(screen.getByRole("link", { name: /privacy/i })).toHaveAttribute("href", "/privacy"); + expect(screen.getByRole("link", { name: /terms/i })).toHaveAttribute("href", "/terms"); + }); +}); +``` + +- [ ] **Step 2: Run the tests** + +Run: `npm run test:run -- legal-pages` +Expected: 3 passed. (If `next/link` needs no special handling, this works under the existing happy-dom setup; the pages are sync server components with no async or server-only APIs, so RTL renders them directly.) + +- [ ] **Step 3: Create `docs/legal/README.md`** + +```markdown +# Legal + +The Privacy Policy and Terms of Service are maintained as rendered pages (single +source of truth — no markdown duplicate to drift): + +- Privacy Policy: [`frontend/src/app/privacy/page.tsx`](../../frontend/src/app/privacy/page.tsx) → `/privacy` +- Terms of Service: [`frontend/src/app/terms/page.tsx`](../../frontend/src/app/terms/page.tsx) → `/terms` + +Last updated: 2026-05-28. Lightweight, plain-language drafts grounded in the +app's actual data practices — **not legal advice**; have a professional review +before relying on them. +``` + +- [ ] **Step 4: Commit** + +```bash +git add frontend/src/components/__tests__/legal-pages.test.tsx docs/legal/README.md +git commit -m "test(v0.9.7): legal page smoke tests + docs/legal pointer" +``` + +--- + +### Task 5: Docs ritual + version bump + ship + +**Files:** +- Modify: `backend/pyproject.toml:3`, `backend/app/settings.py:5`, `frontend/package.json:3`, `frontend/src/app/page.tsx:26`, `frontend/src/components/results-view.tsx:355`, `README.md` (status lead + running list + health curl), `CHANGELOG.md`, `PLAN.md` (v0.9.7 row + section), `docs/PROGRESS_LOG.md`, `backend/uv.lock` + +- [ ] **Step 1: Bump version literals** + +`backend/pyproject.toml` line 3 `version = "0.9.6"` → `"0.9.7"`; `backend/app/settings.py` line 5 `VERSION = "0.9.6"` → `"0.9.7"`; `frontend/package.json` line 3 `"version": "0.9.6",` → `"0.9.7",`; `frontend/src/app/page.tsx` line 26 `... · v0.9.6` → `v0.9.7`; `frontend/src/components/results-view.tsx` line 355 `... Protocol v0.9.6` → `v0.9.7`. + +- [ ] **Step 2: Update README** + +`README.md` line 46: change the lead `Latest shipped release is **v0.9.6** (...)` to name **v0.9.7** (privacy policy + terms of service pages with a global footer), demote v0.9.6 to "before it," and change the trailing pointer from `**v0.9.7 — privacy policy + terms** is next.` to `v0.9.7 adds a Privacy Policy and Terms of Service (linked from a new global footer). **v1.0.0 — public launch** is next.` Bump the health-curl `"version":"0.9.6"` → `"0.9.7"` (line ~79). + +- [ ] **Step 3: Add the CHANGELOG entry** + +In `CHANGELOG.md`, insert below the preamble `---` and above `## [0.9.6]`: + +```markdown +## [0.9.7] — 2026-05-28 + +### Added +- **Privacy Policy and Terms of Service.** Plain-language legal pages at `/privacy` and `/terms`, linked from a new site-wide footer. + +--- +``` + +- [ ] **Step 4: Update PLAN.md** + +Version-map row `| **v0.9.7** | Privacy policy + terms (legal docs) | pending |` → `| **v0.9.7** | Privacy policy + terms + global footer | ✅ shipped |`. + +Replace the `## v0.9.7 — Legal docs (deferred)` section body (Goal + "Exit criteria: TBD") with: + +```markdown +## v0.9.7 — Privacy + Terms (shipped 2026-05-28) + +**Goal:** Plain-language Privacy Policy + Terms of Service pages, linked from a new global footer. + +**Delivered:** Static TSX pages `/privacy` + `/terms` (shared `LegalProse` wrapper), a global `SiteFooter`, content grounded in the app's real data practices (GitHub `read:user`, Neon, Upstash, Groq, Sentry, PostHog), India governing law, 13+, contact shaansatsangi.cse@gmail.com. Not legal advice — flagged for professional review before launch. + +**Design spec:** [`docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md`](./docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md). +**Sub-plan:** [`docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md`](./docs/superpowers/plans/2026-05-28-v0.9.7-legal-docs.md). + +**Exit criteria:** +- [x] `/privacy` + `/terms` render via `LegalProse`; metadata + "Last updated 2026-05-28". +- [x] Global `SiteFooter` links both (+ GitHub); landing hero intact; mobile verified. +- [x] Smoke tests pass; frontend lint/tsc/test/build clean. +- [x] Docs ritual + version bump to 0.9.7; tag + release. +``` + +- [ ] **Step 5: Re-sync `uv.lock`** + +Run: `cd backend && uv lock` +Expected: only `skill-issue-backend 0.9.6 → 0.9.7` changes. + +- [ ] **Step 6: Add the PROGRESS_LOG entry** + +In `docs/PROGRESS_LOG.md`, add a new top entry per the file's format: header `## 2026-05-28 — Claude (Opus 4.7) — v0.9.7 shipped (privacy + terms)`; Slice v0.9.7; Done (the two pages + footer + LegalProse + tests + docs/legal pointer); Decisions (static TSX over markdown — no dep, single source; lightweight India/13+ per user; global footer added since none existed; not-legal-advice flagged); Verified (lint/tsc/test/build + visual); Blocked/open (professional legal review recommended pre-launch); Next (v1.0.0 public launch). + +- [ ] **Step 7: Full verification** + +Run: `cd frontend && npm run lint && npx tsc --noEmit && npm run test:run && npm run build` +Expected: lint/tsc clean; vitest 54 + 3 new = 57 passed; build succeeds with `/privacy` and `/terms` listed (static). + +Run: `cd backend && uv run pytest -q --no-header` +Expected: 290 passed (backend untouched; confirms the version bump didn't break anything). + +- [ ] **Step 8: Commit** + +```bash +git add backend/pyproject.toml backend/app/settings.py frontend/package.json frontend/src/app/page.tsx frontend/src/components/results-view.tsx README.md CHANGELOG.md PLAN.md docs/PROGRESS_LOG.md backend/uv.lock +git commit -m "chore(v0.9.7): bump version + docs ritual (legal docs)" +``` + +- [ ] **Step 9: Push, PR, CI, merge, smoke (confirm before tag)** + +Push the branch, open a PR, wait for CI green, merge to `main`. Prod smoke: `curl …/health` → `version: 0.9.7`; load `/privacy` + `/terms` on prod. Then **pause for user confirmation** before `git tag v0.9.7 && git push origin v0.9.7`. + +--- + +## Self-review notes + +- **Spec coverage:** content §5.1/§5.2 → Tasks 2–3; rendering §3 (TSX + LegalProse) → Tasks 1–3; footer §6.2/§7 → Task 1; docs/legal §3 → Task 4; tests §8 → Task 4; exit criteria §9 → Task 5. All covered. +- **Placeholder scan:** none — full page prose and component code included. +- **Type consistency:** `LegalProse({title,lastUpdated,children})` and `LegalSection({heading,children})` signatures are used identically in Tasks 2–3; `SiteFooter` exported (named) and imported as named in layout + test; pages default-exported and imported as default in the test. +- **No backend/data change:** "your choices" references the existing `/me` delete; no new endpoint. +- **Test count:** frontend 54 → 57 (+3). Backend unchanged at 290. diff --git a/docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md b/docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md new file mode 100644 index 0000000..6edb5be --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-v0.9.7-legal-docs-design.md @@ -0,0 +1,130 @@ +# v0.9.7 — Privacy policy + terms design spec + +**Status:** Designed. Implementation plan to follow under `docs/superpowers/plans/`. +**Date:** 2026-05-28. +**Author:** Claude (Opus 4.7) with Shaan. + +> **Not legal advice.** This spec defines lightweight, honest, plain-language legal pages grounded in the app's actual data practices. A professional should review them before relying on them for a public launch. + +--- + +## 1. Goal + +Ship a Privacy Policy and Terms of Service as styled, indexable pages on the frontend, linked from a new global footer. Content is tailored to what Skill Issue actually does — no boilerplate that overpromises. This is the final pre-1.0 slice. + +## 2. Locked facts (2026-05-28) + +| Field | Value | +| --- | --- | +| Operator | Shaan Satsangi (individual) | +| Contact | shaansatsangi.cse@gmail.com | +| Governing law | India | +| Minimum age | 13+ (matches GitHub) | +| Tone | Lightweight, honest, plain-language | +| Effective / "Last updated" | 2026-05-28 | + +## 3. Locked scope decisions (2026-05-28) + +| Decision | Choice | Why | +| --- | --- | --- | +| Format | **Static TSX pages** (`/privacy`, `/terms`), not markdown | No new dependency, full design control, single source of truth, SEO/PPR-friendly. | +| Shared styling | A small **`LegalProse`** wrapper component | DRY the prose/heading styling across both pages. | +| Footer | New global **`SiteFooter`** in the root layout | "Link from frontend footer" — none exists today. Minimal, subtle, responsive. | +| `docs/legal/` | A short **`README.md` pointer** to the rendered pages | Satisfies PLAN's `docs/legal/` mention without a prose duplicate that would drift. | +| Depth | Lightweight (no cookie-consent banner, no DSAR tooling, no GDPR/CCPA machinery) | The user's "lightweight, honest" choice; matches a solo pre-launch. | + +## 4. Operating context — what the app actually does with data + +Grounded in the codebase (so the Privacy content is truthful, not generic): +- **GitHub OAuth `read:user`** (v0.9.5): on sign-in we read the user's public profile (login, name, avatar). The access token is **AES-GCM encrypted at rest** in the `sessions` table. +- **Saved analyses + history** (`/me`): when signed in, analyses you run are persisted (Neon Postgres) until you delete them; deletion cascades runs + narratives. +- **IP address** for rate limiting (v0.9.2): hashed/keyed per UTC-hour bucket in Upstash; not stored long-term. +- **Caches** (Upstash): Report cache (~6 h TTL), GitHub-API cache, narrative cache — transient. +- **AI narrative**: report data is sent to **Groq** (OpenAI-compatible) to generate the Roast/Mentor text. +- **Analytics**: **PostHog** (events + web vitals); **Sentry** (error telemetry). +- **Hosting**: **Vercel**; **database**: **Neon**. +- **Cookies**: a session cookie (`si_session`, httpOnly, Secure, SameSite=Lax, ~30-day TTL) and a short-lived OAuth `state` cookie. No third-party advertising cookies. + +These map directly to the Privacy "what we collect / how we use it / subprocessors / retention" sections. + +## 5. Content outline + +### 5.1 Privacy Policy (`/privacy`) +1. **Intro** — who operates the service (Shaan Satsangi), effective date, plain-language promise. +2. **What we collect** — GitHub public profile (`read:user`); analyses you run + saved history (signed-in); IP address (rate limiting); product analytics + error telemetry; session cookie. +3. **How we use it** — produce + display reports, persist your history, prevent abuse, improve reliability. +4. **Third parties / subprocessors** — GitHub, Vercel, Neon, Upstash, Groq, Sentry, PostHog (each with a one-line purpose). +5. **Retention** — sessions ~30 days; saved analyses until you delete them; caches transient. +6. **Your choices** — delete saved analyses, sign out, revoke GitHub access (link to GitHub settings). +7. **Cookies** — session + OAuth-state only; no ad cookies. +8. **Children** — 13+. +9. **Changes** — we may update; "last updated" reflects the date. +10. **Contact** — shaansatsangi.cse@gmail.com. + +### 5.2 Terms of Service (`/terms`) +1. **Acceptance** — using the service = agreeing. +2. **What the service is** — opinionated, automated GitHub analysis for insight + entertainment; **explicitly not** an authoritative hiring, credit, or employment-decision tool; scores are subjective/automated and may be wrong. +3. **Accounts** — optional GitHub OAuth sign-in; you're responsible for your GitHub account. +4. **Acceptable use** — no abuse, automated scraping, circumventing rate limits, or attempting to disrupt the service. +5. **Intellectual property** — the service + its scoring are ours; GitHub data belongs to its owners/GitHub. +6. **Disclaimers** — provided "as is," no warranty, scores are subjective and automated. +7. **Limitation of liability** — to the extent permitted by law, not liable for damages arising from use. +8. **Termination** — we may suspend abusive use; you may stop + delete your data anytime. +9. **Governing law** — India. +10. **Changes** + **Contact** — shaansatsangi.cse@gmail.com. + +## 6. Surface area + +### 6.1 New files +| File | Responsibility | +| --- | --- | +| `frontend/src/components/legal-prose.tsx` | `LegalProse` — presentational wrapper: page title, "Last updated" line, consistent prose/heading/link styling. Takes `title`, `lastUpdated`, `children`. | +| `frontend/src/app/privacy/page.tsx` | Static server component; Privacy content (§5.1) inside `LegalProse`; page `metadata`. | +| `frontend/src/app/terms/page.tsx` | Static server component; Terms content (§5.2) inside `LegalProse`; page `metadata`. | +| `frontend/src/components/site-footer.tsx` | `SiteFooter` — global footer: `Privacy · Terms` + GitHub repo link; subtle, responsive. | +| `frontend/src/components/__tests__/legal-pages.test.tsx` | Smoke tests: each page renders, shows its heading + the contact email; footer renders both links. | +| `docs/legal/README.md` | Pointer to the rendered `/privacy` + `/terms` pages + last-updated note. | + +### 6.2 Modified files +| File | Change | +| --- | --- | +| `frontend/src/app/layout.tsx` | Render `` inside `` after the children/providers so it sits at the bottom (body is already `min-h-full flex flex-col`). | +| Version literals + CHANGELOG + PLAN + PROGRESS_LOG | Release ritual (0.9.6 → 0.9.7). | + +### 6.3 Untouched (intentionally) +- Backend — no API or data-handling change; this slice is documentation + frontend presentation. +- No new dependency (TSX, not markdown rendering). + +## 7. Footer + landing-layout consideration + +`layout.tsx` body is `min-h-full flex flex-col` with `` then content. Adding `` at the end keeps the footer at the bottom; the landing page's `flex-1` hero still centers in the remaining space. **Must verify** the full-height landing hero isn't visually disrupted and the footer reads well at mobile breakpoints (per the non-negotiable responsive rule). The footer is unobtrusive: small muted text, a thin top border, links to `/privacy`, `/terms`, and the GitHub repo. + +## 8. Tests + +Per the project norm (UI doesn't need 100% coverage — visual verification is fine), keep tests minimal: +- `legal-pages.test.tsx`: render `Privacy` page → asserts the "Privacy" heading + contact email present; render `Terms` page → asserts "Terms" heading + governing-law mention; render `SiteFooter` → asserts links to `/privacy` and `/terms`. +- Plus `next build` + a visual check of `/privacy`, `/terms`, and the footer on desktop + mobile. + +## 9. Exit criteria + +- [ ] `/privacy` and `/terms` render the §5 content via `LegalProse`, with page metadata and a "Last updated 2026-05-28" line. +- [ ] Global `SiteFooter` links to both pages (+ GitHub) and is in the root layout; landing hero intact; mobile verified. +- [ ] `docs/legal/README.md` points to the rendered pages. +- [ ] `legal-pages.test.tsx` passes; frontend `lint` + `tsc` + `test:run` + `build` clean. +- [ ] Docs ritual + version bump to 0.9.7; PLAN v0.9.7 row flipped ✅; tag `v0.9.7` + release. + +## 10. Out of scope +- Cookie-consent banner / preference center. +- DSAR (data-subject-access-request) tooling or a self-serve "download my data." +- GDPR/CCPA-specific rights sections and a formal subprocessor agreement. +- Backend changes (no new data collection or deletion endpoints — `/me` delete already exists and is referenced under "your choices"). +- A markdown-rendering pipeline. + +## 11. Implementation ordering +1. `LegalProse` wrapper + `SiteFooter` component (+ wire footer into `layout.tsx`); verify landing hero + mobile. +2. `/privacy` page (content + metadata). +3. `/terms` page (content + metadata). +4. `legal-pages.test.tsx` smoke tests + `docs/legal/README.md`. +5. Docs ritual + version bump + ship (PR → CI → merge → prod smoke → confirm before tag). + +**Reversibility:** additive (new components/pages/footer); reverting removes the pages + footer with no data or backend impact. diff --git a/frontend/package.json b/frontend/package.json index a261c64..8d89e88 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.9.6", + "version": "0.9.7", "private": true, "scripts": { "dev": "next dev", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 55d4385..08f3dd3 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import { FramerProvider } from "@/components/framer-provider"; +import { SiteFooter } from "@/components/site-footer"; import { SiteHeader } from "@/components/site-header"; import { ObservabilityProvider } from "@/observability/provider"; import "./globals.css"; @@ -44,6 +45,7 @@ export default function RootLayout({ {children} + ); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index efe5837..efc3cee 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -23,7 +23,7 @@ export default function Home() { transition={{ delay: 0.2, duration: 0.5 }} className="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs font-medium uppercase tracking-wider text-muted-foreground" > - Deterministic engineering reports · v0.9.6 + Deterministic engineering reports · v0.9.7

diff --git a/frontend/src/app/privacy/page.tsx b/frontend/src/app/privacy/page.tsx new file mode 100644 index 0000000..1290b94 --- /dev/null +++ b/frontend/src/app/privacy/page.tsx @@ -0,0 +1,137 @@ +import type { Metadata } from "next"; +import { LegalProse, LegalSection } from "@/components/legal-prose"; + +export const metadata: Metadata = { + title: "Privacy Policy — Skill Issue", + description: "What Skill Issue collects, how it's used, and your choices.", +}; + +const EMAIL = "shaansatsangi.cse@gmail.com"; + +export default function PrivacyPage() { + return ( + +

+ Skill Issue is operated by Shaan Satsangi (“we”). This policy explains, in plain + language, what we collect, why, and the choices you have. We collect as little as we need to + run the service. +

+ + +
    +
  • + GitHub profile (if you sign in). We use + GitHub OAuth with read-only read:user scope to read your public profile + (login, name, avatar). Your GitHub access token is encrypted at rest. +
  • +
  • + Analyses and history. When signed in, the + analyses you run are saved to your history until you delete them. +
  • +
  • + IP address. Used transiently to rate-limit + requests and prevent abuse. +
  • +
  • + Usage and error data. Aggregate product + analytics and error reports help us keep the service working. +
  • +
  • + A session cookie. Keeps you signed in. +
  • +
+
+ + +

+ To generate and display your report, save your history when you’re signed in, prevent + abuse, and improve reliability. We do not sell your data or use it for advertising. +

+
+ + +

We rely on a few service providers to run Skill Issue:

+
    +
  • + GitHub — the source of the public data we + analyze. +
  • +
  • + Vercel — hosting. +
  • +
  • + Neon — database (accounts, saved analyses). +
  • +
  • + Upstash — short-lived caching and + rate-limit counters. +
  • +
  • + Groq — generates the AI narrative from your + report. +
  • +
  • + Sentry — error monitoring. +
  • +
  • + PostHog — product analytics. +
  • +
+
+ + +

+ Sign-in sessions expire after about 30 days. Saved analyses stay until you delete them. + Caches are short-lived and expire automatically. +

+
+ + +

+ You can delete saved analyses from your history, sign out at any time, and revoke + Skill Issue’s access from your{" "} + + GitHub application settings + + . +

+
+ + +

+ We use a session cookie to keep you signed in and a short-lived cookie during the GitHub + sign-in flow. We do not use advertising or cross-site tracking cookies. +

+
+ + +

Skill Issue is intended for users aged 13 and older, consistent with GitHub.

+
+ + +

+ We may update this policy; the “last updated” date above reflects the latest + version. +

+
+ + +

+ Questions? Email{" "} + + {EMAIL} + + . +

+
+
+ ); +} diff --git a/frontend/src/app/terms/page.tsx b/frontend/src/app/terms/page.tsx new file mode 100644 index 0000000..9a42126 --- /dev/null +++ b/frontend/src/app/terms/page.tsx @@ -0,0 +1,100 @@ +import type { Metadata } from "next"; +import { LegalProse, LegalSection } from "@/components/legal-prose"; + +export const metadata: Metadata = { + title: "Terms of Service — Skill Issue", + description: "The terms for using Skill Issue.", +}; + +const EMAIL = "shaansatsangi.cse@gmail.com"; + +export default function TermsPage() { + return ( + +

+ By using Skill Issue (operated by Shaan Satsangi), you agree to these terms. If you + don’t agree, please don’t use the service. +

+ + +

+ Skill Issue produces an opinionated, automated analysis of a public GitHub profile for + insight and entertainment. The scores and commentary are generated by software and may be + incomplete or wrong. Skill Issue is not an + authoritative hiring, credit, employment, or background-check tool, and should not be used + to make decisions about people. +

+
+ + +

+ Signing in is optional and uses GitHub OAuth. You are responsible for activity under your + GitHub account and for keeping it secure. +

+
+ + +

You agree not to:

+
    +
  • abuse, overload, or attempt to disrupt the service;
  • +
  • scrape it or access it through automated means outside normal use;
  • +
  • circumvent rate limits or security measures;
  • +
  • use it to harass others or for unlawful purposes.
  • +
+
+ + +

+ The service, its design, and its scoring system belong to us. GitHub data shown in a report + belongs to its respective owners and GitHub; we present it for analysis. +

+
+ + +

+ The service is provided “as is” and “as available,” without + warranties of any kind. Scores are subjective, automated estimates and are not guarantees + of skill, quality, or fitness for any purpose. +

+
+ + +

+ To the maximum extent permitted by law, we are not liable for any indirect, incidental, or + consequential damages arising from your use of the service. +

+
+ + +

+ We may suspend or limit access that abuses the service. You can stop using it and delete + your saved data at any time. +

+
+ + +

These terms are governed by the laws of India.

+
+ + +

+ We may update these terms; the “last updated” date above reflects the latest + version. Continued use means you accept the changes. +

+
+ + +

+ Questions? Email{" "} + + {EMAIL} + + . +

+
+
+ ); +} diff --git a/frontend/src/components/__tests__/legal-pages.test.tsx b/frontend/src/components/__tests__/legal-pages.test.tsx new file mode 100644 index 0000000..f29a08a --- /dev/null +++ b/frontend/src/components/__tests__/legal-pages.test.tsx @@ -0,0 +1,29 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import PrivacyPage from "@/app/privacy/page"; +import TermsPage from "@/app/terms/page"; +import { SiteFooter } from "@/components/site-footer"; + +describe("legal pages", () => { + it("privacy page shows its heading and the contact email", () => { + render(); + expect( + screen.getByRole("heading", { name: /privacy policy/i, level: 1 }), + ).toBeInTheDocument(); + expect(screen.getByText("shaansatsangi.cse@gmail.com")).toBeInTheDocument(); + }); + + it("terms page shows its heading and the governing law", () => { + render(); + expect( + screen.getByRole("heading", { name: /terms of service/i, level: 1 }), + ).toBeInTheDocument(); + expect(screen.getByText(/laws of India/i)).toBeInTheDocument(); + }); + + it("footer links to privacy and terms", () => { + render(); + expect(screen.getByRole("link", { name: /privacy/i })).toHaveAttribute("href", "/privacy"); + expect(screen.getByRole("link", { name: /terms/i })).toHaveAttribute("href", "/terms"); + }); +}); diff --git a/frontend/src/components/legal-prose.tsx b/frontend/src/components/legal-prose.tsx new file mode 100644 index 0000000..b248608 --- /dev/null +++ b/frontend/src/components/legal-prose.tsx @@ -0,0 +1,30 @@ +import type { ReactNode } from "react"; + +export function LegalProse({ + title, + lastUpdated, + children, +}: { + title: string; + lastUpdated: string; + children: ReactNode; +}) { + return ( +
+

{title}

+

Last updated {lastUpdated}

+
+ {children} +
+
+ ); +} + +export function LegalSection({ heading, children }: { heading: string; children: ReactNode }) { + return ( +
+

{heading}

+ {children} +
+ ); +} diff --git a/frontend/src/components/results-view.tsx b/frontend/src/components/results-view.tsx index e92b6cb..e6470ab 100644 --- a/frontend/src/components/results-view.tsx +++ b/frontend/src/components/results-view.tsx @@ -352,7 +352,7 @@ export function ResultsView({
-

Skill Issue — GitHub Reputation Protocol v0.9.6

+

Skill Issue — GitHub Reputation Protocol v0.9.7

diff --git a/frontend/src/components/site-footer.tsx b/frontend/src/components/site-footer.tsx new file mode 100644 index 0000000..f032df6 --- /dev/null +++ b/frontend/src/components/site-footer.tsx @@ -0,0 +1,27 @@ +import Link from "next/link"; + +export function SiteFooter() { + return ( + + ); +}