From c5a2029db634f180d6ae58d990727722cd6a75f8 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 04:04:22 -0700 Subject: [PATCH 01/12] feat(legal): public Privacy and Terms pages (TASK-530) Adds /privacy and /terms as v2 pages so the Google OAuth consent screen and signup links stop returning 404. Draft text is drawn only from code behaviour; bracketed items stay open until Sam approves the wording. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/App.tsx | 4 + .../src/v2/__tests__/V2LegalPage.test.tsx | 19 ++++ frontend/src/v2/legal/V2LegalPage.tsx | 106 ++++++++++++++++++ frontend/src/v2/v2.css | 9 ++ 4 files changed, 138 insertions(+) create mode 100644 frontend/src/v2/__tests__/V2LegalPage.test.tsx create mode 100644 frontend/src/v2/legal/V2LegalPage.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 89138f23fe..8d476b3d5b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -20,6 +20,8 @@ import '@fontsource/ibm-plex-sans/600.css'; import '@fontsource/ibm-plex-mono/500.css'; import './v2/v2.css'; import './App.css'; +// Public legal pages linked from Google OAuth consent and signup (TASK-530). +import { V2PrivacyPage, V2TermsPage } from './v2/legal/V2LegalPage'; // TASK-145 — route-level split. One 4.18 MB (1.16 MB gzip) entry chunk used to // be the whole product for every visitor: the landing carried the authenticated @@ -316,6 +318,8 @@ function App(): React.ReactElement { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/frontend/src/v2/__tests__/V2LegalPage.test.tsx b/frontend/src/v2/__tests__/V2LegalPage.test.tsx new file mode 100644 index 0000000000..f743190612 --- /dev/null +++ b/frontend/src/v2/__tests__/V2LegalPage.test.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { V2PrivacyPage, V2TermsPage } from '../legal/V2LegalPage'; + +describe('public legal pages (TASK-530)', () => { + it('renders the privacy page with its required sections and a draft marker', () => { + render(); + expect(screen.getByRole('heading', { level: 1, name: 'Privacy' })).toBeInTheDocument(); + ['What we store', 'How long we keep chat messages', 'Who processes your data', 'Deleting your account'] + .forEach((heading) => expect(screen.getByRole('heading', { level: 2, name: heading })).toBeInTheDocument()); + expect(screen.getByText(/Draft for review/)).toBeInTheDocument(); + }); + + it('renders the terms page', () => { + render(); + expect(screen.getByRole('heading', { level: 1, name: 'Terms' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { level: 2, name: 'The service' })).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx new file mode 100644 index 0000000000..45a6ec4743 --- /dev/null +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -0,0 +1,106 @@ +import React from 'react'; + +/* + * TASK-530 — public Privacy and Terms pages, linked from Google OAuth consent + * and signup. Text is drafted only from what the code does; each factual claim + * cites its source in the PR body. Bracketed items are open and must be filled + * or confirmed before this ships. Legal wording is Sam's to approve. + */ + +interface LegalSection { + heading: string; + body: React.ReactNode[]; +} + +const DRAFT_NOTE = 'Draft for review. Bracketed items are not yet confirmed.'; + +const PRIVACY_SECTIONS: LegalSection[] = [ + { + heading: 'What we store', + body: [ + 'Account details you give us: username and email. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', + 'Pods, the chat messages you and your agents post, and the tasks and posts you create.', + 'Agent memory: notes an agent keeps between sessions. [Confirm who can read and delete it.]', + 'Files you upload, such as images attached to posts and messages. [Confirm the current storage backend; the design doc describes database storage.]', + 'Connector credentials, such as tokens for integrations you connect. These are encrypted at rest with AES-256-GCM.', + ], + }, + { + heading: 'How long we keep chat messages', + body: [ + 'Chat messages are deleted from our chat database after 30 days by default. [Confirm the production value.]', + 'The retention job can shorten the window when storage runs low, down to a one-day floor. [Confirm whether this is acceptable to publish.] [Describe the Pro-plan exemption and its grace period, if they stay.]', + ], + }, + { + heading: 'Who processes your data', + body: [ + 'Hosting: Google Cloud (Kubernetes on GKE). Traffic reaches our servers through Cloudflare.', + 'Email delivery: SMTP2GO.', + 'Error monitoring: Sentry. Personal data is not sent by default.', + 'AI models: messages sent to an agent are routed to the model provider that agent is configured to use, through our LiteLLM proxy. [List the providers in production.]', + ], + }, + { + heading: 'Deleting your account', + body: [ + 'Account deletion is not yet available in the app. [Confirm the process: email request, identity check and response time.]', + 'Contact: [contact address to be confirmed].', + ], + }, +]; + +const TERMS_SECTIONS: LegalSection[] = [ + { + heading: 'The service', + body: [ + 'Commonly is a shared space where people and software agents meet in pods, chat and tasks. [Describe the service as it is offered.]', + ], + }, + { + heading: 'Your account', + body: [ + 'You are responsible for activity on your account and for the agents you install and connect. [Add eligibility and account-security terms.]', + ], + }, + { + heading: 'Content and agents', + body: [ + 'You keep ownership of what you post. Agents act on your instructions and their own configuration; you are responsible for the agents you install or connect. [Add licence to operate the service, acceptable use and takedown terms.]', + ], + }, + { + heading: 'Ending', + body: [ + '[Add termination, suspension and limitation-of-liability terms.]', + ], + }, +]; + +const LegalDocument: React.FC<{ title: string; sections: LegalSection[] }> = ({ title, sections }) => ( +
+
+

{title}

+

{DRAFT_NOTE}

+ {sections.map((section) => ( +
+

{section.heading}

+ {section.body.map((paragraph, index) => ( + // eslint-disable-next-line react/no-array-index-key +

{paragraph}

+ ))} +
+ ))} +
+
+); + +export const V2PrivacyPage: React.FC = () => ( + +); + +export const V2TermsPage: React.FC = () => ( + +); + +export default V2PrivacyPage; diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 3a564c526d..771bffb389 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -13316,3 +13316,12 @@ body.modern-ui.v2-canvas { .v2-root button.v2-board__new:hover, .v2-root button.v2-board__create-submit:not(:disabled):hover { background: var(--v2-text-secondary); border-color: var(--v2-text-secondary); } .v2-board__owner-time { color: var(--v2-text-muted); } + +/* Public Privacy and Terms pages (TASK-530). Outside .v2-root, so no root-scoped selectors. */ +.v2-legal { min-height: 100vh; box-sizing: border-box; padding: 48px 24px; background: var(--v2-bg-subtle); color: var(--v2-text-primary); } +.v2-legal__doc { max-width: 680px; margin: 0 auto; } +.v2-legal__title { font-size: 32px; font-weight: 600; margin: 0 0 8px; } +.v2-legal__draft { color: var(--v2-text-muted); font-size: 13px; margin: 0 0 32px; } +.v2-legal__section { margin-bottom: 28px; } +.v2-legal__section h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; } +.v2-legal__section p { font-size: 15px; line-height: 1.6; color: var(--v2-text-secondary); margin: 0 0 8px; } From 59fe5a05e8898dcead591e69fbfd563b95428c0c Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:47:39 -0700 Subject: [PATCH 02/12] docs(legal): fill Privacy brackets from origin/main facts (TASK-530) Uploads live in MongoDB with no expiry and no user delete path. One 30-day chat retention rule for every plan. DeepSeek default and Cloudflare Workers AI fallback named as hosted processors. Account fields, sign-in methods, and the delete-by-email path are stated as the code does today. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/__tests__/V2LegalPage.test.tsx | 2 +- frontend/src/v2/legal/V2LegalPage.tsx | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/v2/__tests__/V2LegalPage.test.tsx b/frontend/src/v2/__tests__/V2LegalPage.test.tsx index f743190612..6ca790abc5 100644 --- a/frontend/src/v2/__tests__/V2LegalPage.test.tsx +++ b/frontend/src/v2/__tests__/V2LegalPage.test.tsx @@ -6,7 +6,7 @@ describe('public legal pages (TASK-530)', () => { it('renders the privacy page with its required sections and a draft marker', () => { render(); expect(screen.getByRole('heading', { level: 1, name: 'Privacy' })).toBeInTheDocument(); - ['What we store', 'How long we keep chat messages', 'Who processes your data', 'Deleting your account'] + ['What we store', 'How long we keep data', 'Who processes your data', 'Deleting your account'] .forEach((heading) => expect(screen.getByRole('heading', { level: 2, name: heading })).toBeInTheDocument()); expect(screen.getByText(/Draft for review/)).toBeInTheDocument(); }); diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 45a6ec4743..4ea41ae34e 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -18,18 +18,20 @@ const PRIVACY_SECTIONS: LegalSection[] = [ { heading: 'What we store', body: [ - 'Account details you give us: username and email. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', + 'Account details: username, display name, email, profile picture, role, timezone, billing identifiers, linked Google and GitHub identities, and contact details. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', + 'You can sign in with a password, Google, GitHub, or a CLI device code.', 'Pods, the chat messages you and your agents post, and the tasks and posts you create.', 'Agent memory: notes an agent keeps between sessions. [Confirm who can read and delete it.]', - 'Files you upload, such as images attached to posts and messages. [Confirm the current storage backend; the design doc describes database storage.]', + 'Files you upload, such as images attached to posts and messages. They are stored in our MongoDB database.', 'Connector credentials, such as tokens for integrations you connect. These are encrypted at rest with AES-256-GCM.', ], }, { - heading: 'How long we keep chat messages', + heading: 'How long we keep data', body: [ - 'Chat messages are deleted from our chat database after 30 days by default. [Confirm the production value.]', - 'The retention job can shorten the window when storage runs low, down to a one-day floor. [Confirm whether this is acceptable to publish.] [Describe the Pro-plan exemption and its grace period, if they stay.]', + 'Chat messages are deleted from our chat database after 30 days. This applies to every plan.', + 'Uploaded files have no expiry date. [Confirm whether this is acceptable to publish.]', + 'The chat retention job can shorten the 30-day window when storage runs low, down to a one-day floor. [Confirm whether this is acceptable to publish.]', ], }, { @@ -38,13 +40,15 @@ const PRIVACY_SECTIONS: LegalSection[] = [ 'Hosting: Google Cloud (Kubernetes on GKE). Traffic reaches our servers through Cloudflare.', 'Email delivery: SMTP2GO.', 'Error monitoring: Sentry. Personal data is not sent by default.', - 'AI models: messages sent to an agent are routed to the model provider that agent is configured to use, through our LiteLLM proxy. [List the providers in production.]', + 'AI models for hosted agents: DeepSeek is the default model, and Cloudflare Workers AI is the fallback. [Confirm whether OpenRouter still serves any hosted route.]', + 'Agents you run on your own machine use your own model provider. We do not process those model calls.', ], }, { heading: 'Deleting your account', body: [ - 'Account deletion is not yet available in the app. [Confirm the process: email request, identity check and response time.]', + 'You cannot delete your own account in the app. To delete your account, email us.', + 'Uploaded files cannot be deleted by users, and deleting an account does not delete them. [Confirm the admin deletion process and whether it removes uploads.]', 'Contact: [contact address to be confirmed].', ], }, From 51a428e93950f9a7a7e2b35337c2ff5bbd42cd81 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:51:45 -0700 Subject: [PATCH 03/12] docs(legal): add approved contact addresses to Privacy and Terms (TASK-530) privacy@commonly.me for privacy questions, support@commonly.me for terms and account-deletion requests, as approved by Sam. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/legal/V2LegalPage.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 4ea41ae34e..60dc2243a7 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -47,9 +47,9 @@ const PRIVACY_SECTIONS: LegalSection[] = [ { heading: 'Deleting your account', body: [ - 'You cannot delete your own account in the app. To delete your account, email us.', + 'You cannot delete your own account in the app. To delete your account, email support@commonly.me.', 'Uploaded files cannot be deleted by users, and deleting an account does not delete them. [Confirm the admin deletion process and whether it removes uploads.]', - 'Contact: [contact address to be confirmed].', + 'Privacy questions: privacy@commonly.me.', ], }, ]; @@ -79,6 +79,12 @@ const TERMS_SECTIONS: LegalSection[] = [ '[Add termination, suspension and limitation-of-liability terms.]', ], }, + { + heading: 'Contact', + body: [ + 'Questions about these terms: support@commonly.me.', + ], + }, ]; const LegalDocument: React.FC<{ title: string; sections: LegalSection[] }> = ({ title, sections }) => ( From eba65b1ff00b1148f2132d7fadbfd41522dfea15 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:00:02 -0700 Subject: [PATCH 04/12] docs(legal): name OpenRouter as a hosted processor (TASK-530) OpenRouter still serves hosted routes (free community models and two paid models), so it is listed next to DeepSeek and Cloudflare Workers AI. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/legal/V2LegalPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 60dc2243a7..9ef724d422 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -40,7 +40,7 @@ const PRIVACY_SECTIONS: LegalSection[] = [ 'Hosting: Google Cloud (Kubernetes on GKE). Traffic reaches our servers through Cloudflare.', 'Email delivery: SMTP2GO.', 'Error monitoring: Sentry. Personal data is not sent by default.', - 'AI models for hosted agents: DeepSeek is the default model, and Cloudflare Workers AI is the fallback. [Confirm whether OpenRouter still serves any hosted route.]', + 'AI models for hosted agents: DeepSeek is the default model. OpenRouter serves some hosted routes, including free community models and two paid models. Cloudflare Workers AI is the fallback.', 'Agents you run on your own machine use your own model provider. We do not process those model calls.', ], }, From 8bb4fa76f024fdc5a22f7fb0877fe145b56308fd Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:47:17 -0700 Subject: [PATCH 05/12] fix(legal): apply Sam's rulings and scope pages to v2 root (TASK-530) Publish no-expiry uploads and the one-day retention floor. Describe agent memory from its routes. Deletion goes to support@commonly.me. The page now sits in .v2-root so it uses v2 tokens; before, it inherited the V1 dark body. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/legal/V2LegalPage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 9ef724d422..5bdae12342 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -21,7 +21,7 @@ const PRIVACY_SECTIONS: LegalSection[] = [ 'Account details: username, display name, email, profile picture, role, timezone, billing identifiers, linked Google and GitHub identities, and contact details. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', 'You can sign in with a password, Google, GitHub, or a CLI device code.', 'Pods, the chat messages you and your agents post, and the tasks and posts you create.', - 'Agent memory: notes an agent keeps between sessions. [Confirm who can read and delete it.]', + 'Agent memory: notes an agent keeps between sessions. Its owners and admins can read it and edit its sections. The agent reads and writes its own memory. There is no way in the app to delete an agent\'s whole memory.', 'Files you upload, such as images attached to posts and messages. They are stored in our MongoDB database.', 'Connector credentials, such as tokens for integrations you connect. These are encrypted at rest with AES-256-GCM.', ], @@ -30,8 +30,8 @@ const PRIVACY_SECTIONS: LegalSection[] = [ heading: 'How long we keep data', body: [ 'Chat messages are deleted from our chat database after 30 days. This applies to every plan.', - 'Uploaded files have no expiry date. [Confirm whether this is acceptable to publish.]', - 'The chat retention job can shorten the 30-day window when storage runs low, down to a one-day floor. [Confirm whether this is acceptable to publish.]', + 'Uploaded files have no expiry date.', + 'The chat retention job can shorten the 30-day window when storage runs low, down to a one-day floor.', ], }, { @@ -47,8 +47,8 @@ const PRIVACY_SECTIONS: LegalSection[] = [ { heading: 'Deleting your account', body: [ - 'You cannot delete your own account in the app. To delete your account, email support@commonly.me.', - 'Uploaded files cannot be deleted by users, and deleting an account does not delete them. [Confirm the admin deletion process and whether it removes uploads.]', + 'To delete your account and your uploads, email support@commonly.me.', + 'Uploaded files cannot be deleted in the app.', 'Privacy questions: privacy@commonly.me.', ], }, @@ -88,7 +88,7 @@ const TERMS_SECTIONS: LegalSection[] = [ ]; const LegalDocument: React.FC<{ title: string; sections: LegalSection[] }> = ({ title, sections }) => ( -
+

{title}

{DRAFT_NOTE}

From bb53bffbfaaf98f8cf82096a24bbb075ac81282a Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 08:04:26 -0700 Subject: [PATCH 06/12] fix(legal): scroll legal pages, link home and addresses (TASK-530) .v2-root sets height:100vh and overflow:hidden, so the bottom of the legal pages was unreachable on mobile. The pages now override both, as the landing does. Adds a home link, mailto links for the addresses, and a 1.2 title line-height. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/__tests__/V2LegalPage.test.tsx | 6 ++++++ frontend/src/v2/legal/V2LegalPage.tsx | 13 +++++++++++-- frontend/src/v2/v2.css | 7 +++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/src/v2/__tests__/V2LegalPage.test.tsx b/frontend/src/v2/__tests__/V2LegalPage.test.tsx index 6ca790abc5..7e519c51d6 100644 --- a/frontend/src/v2/__tests__/V2LegalPage.test.tsx +++ b/frontend/src/v2/__tests__/V2LegalPage.test.tsx @@ -11,6 +11,12 @@ describe('public legal pages (TASK-530)', () => { expect(screen.getByText(/Draft for review/)).toBeInTheDocument(); }); + it('links the addresses by mailto and links home', () => { + render(); + expect(screen.getByRole('link', { name: 'privacy@commonly.me' })).toHaveAttribute('href', 'mailto:privacy@commonly.me'); + expect(screen.getByRole('link', { name: 'Commonly' })).toHaveAttribute('href', '/'); + }); + it('renders the terms page', () => { render(); expect(screen.getByRole('heading', { level: 1, name: 'Terms' })).toBeInTheDocument(); diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 5bdae12342..26810cc67c 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -9,11 +9,19 @@ import React from 'react'; interface LegalSection { heading: string; - body: React.ReactNode[]; + body: string[]; } const DRAFT_NOTE = 'Draft for review. Bracketed items are not yet confirmed.'; +// Turns each commonly.me address in a paragraph into a mailto link. +// String.split with a capture group puts the matches at odd indexes. +const EMAIL_PATTERN = /([a-z]+@commonly\.me)/; +const linkEmails = (text: string): React.ReactNode[] => + text.split(EMAIL_PATTERN).map((part, index) => (index % 2 === 1 + ? {part} + : part)); + const PRIVACY_SECTIONS: LegalSection[] = [ { heading: 'What we store', @@ -90,6 +98,7 @@ const TERMS_SECTIONS: LegalSection[] = [ const LegalDocument: React.FC<{ title: string; sections: LegalSection[] }> = ({ title, sections }) => (
+ Commonly

{title}

{DRAFT_NOTE}

{sections.map((section) => ( @@ -97,7 +106,7 @@ const LegalDocument: React.FC<{ title: string; sections: LegalSection[] }> = ({

{section.heading}

{section.body.map((paragraph, index) => ( // eslint-disable-next-line react/no-array-index-key -

{paragraph}

+

{linkEmails(paragraph)}

))} ))} diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 771bffb389..67253b08ec 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -13318,9 +13318,12 @@ body.modern-ui.v2-canvas { .v2-board__owner-time { color: var(--v2-text-muted); } /* Public Privacy and Terms pages (TASK-530). Outside .v2-root, so no root-scoped selectors. */ -.v2-legal { min-height: 100vh; box-sizing: border-box; padding: 48px 24px; background: var(--v2-bg-subtle); color: var(--v2-text-primary); } +/* .v2-root sets height:100vh and overflow:hidden; the legal pages must scroll, so override both (same as the landing). */ +.v2-root.v2-legal { display: block; height: 100vh; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; padding: 48px 24px; background: var(--v2-bg-subtle); color: var(--v2-text-primary); } .v2-legal__doc { max-width: 680px; margin: 0 auto; } -.v2-legal__title { font-size: 32px; font-weight: 600; margin: 0 0 8px; } +.v2-legal__home { display: inline-block; margin: 0 0 24px; font-size: 14px; color: var(--v2-text-secondary); text-decoration: none; } +.v2-legal__home:hover { text-decoration: underline; } +.v2-legal__title { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 8px; } .v2-legal__draft { color: var(--v2-text-muted); font-size: 13px; margin: 0 0 32px; } .v2-legal__section { margin-bottom: 28px; } .v2-legal__section h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; } From b7b87a8f698433d75cd0a1d4f1be68e0356eb788 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 08:06:02 -0700 Subject: [PATCH 07/12] style(legal): underline in-body links so they read as links (TASK-530) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/v2.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 67253b08ec..e7d24ec500 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -13323,6 +13323,7 @@ body.modern-ui.v2-canvas { .v2-legal__doc { max-width: 680px; margin: 0 auto; } .v2-legal__home { display: inline-block; margin: 0 0 24px; font-size: 14px; color: var(--v2-text-secondary); text-decoration: none; } .v2-legal__home:hover { text-decoration: underline; } +.v2-legal__section a { color: var(--v2-text-primary); text-decoration: underline; } .v2-legal__title { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 8px; } .v2-legal__draft { color: var(--v2-text-muted); font-size: 13px; margin: 0 0 32px; } .v2-legal__section { margin-bottom: 28px; } From 7588aabf5a7fe86bc035711ee7518c0e5c8b212f Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:50:56 -0700 Subject: [PATCH 08/12] docs(legal): apply Cofounder's wording and Privacy additions (TASK-530) Privacy: model-provider sentence, Analytics section (ref visit record and signup ref only), sign-in token storage. Terms replaced verbatim with the approved text. Draft note removed. Governing law left out. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- .../src/v2/__tests__/V2LegalPage.test.tsx | 6 ++--- frontend/src/v2/legal/V2LegalPage.tsx | 26 +++++++++++-------- frontend/src/v2/v2.css | 1 - 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/src/v2/__tests__/V2LegalPage.test.tsx b/frontend/src/v2/__tests__/V2LegalPage.test.tsx index 7e519c51d6..95529c74c6 100644 --- a/frontend/src/v2/__tests__/V2LegalPage.test.tsx +++ b/frontend/src/v2/__tests__/V2LegalPage.test.tsx @@ -3,12 +3,12 @@ import { render, screen } from '@testing-library/react'; import { V2PrivacyPage, V2TermsPage } from '../legal/V2LegalPage'; describe('public legal pages (TASK-530)', () => { - it('renders the privacy page with its required sections and a draft marker', () => { + it('renders the privacy page with its required sections', () => { render(); expect(screen.getByRole('heading', { level: 1, name: 'Privacy' })).toBeInTheDocument(); - ['What we store', 'How long we keep data', 'Who processes your data', 'Deleting your account'] + ['What we store', 'How long we keep data', 'Who processes your data', 'Analytics', 'Deleting your account'] .forEach((heading) => expect(screen.getByRole('heading', { level: 2, name: heading })).toBeInTheDocument()); - expect(screen.getByText(/Draft for review/)).toBeInTheDocument(); + expect(screen.queryByText(/Draft for review/)).not.toBeInTheDocument(); }); it('links the addresses by mailto and links home', () => { diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 26810cc67c..7338c958c9 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -2,9 +2,8 @@ import React from 'react'; /* * TASK-530 — public Privacy and Terms pages, linked from Google OAuth consent - * and signup. Text is drafted only from what the code does; each factual claim - * cites its source in the PR body. Bracketed items are open and must be filled - * or confirmed before this ships. Legal wording is Sam's to approve. + * and signup. Every factual claim cites its source in the PR body. Legal + * wording is Sam's to approve; Cofounder reads it before the gate. */ interface LegalSection { @@ -12,8 +11,6 @@ interface LegalSection { body: string[]; } -const DRAFT_NOTE = 'Draft for review. Bracketed items are not yet confirmed.'; - // Turns each commonly.me address in a paragraph into a mailto link. // String.split with a capture group puts the matches at odd indexes. const EMAIL_PATTERN = /([a-z]+@commonly\.me)/; @@ -27,7 +24,7 @@ const PRIVACY_SECTIONS: LegalSection[] = [ heading: 'What we store', body: [ 'Account details: username, display name, email, profile picture, role, timezone, billing identifiers, linked Google and GitHub identities, and contact details. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', - 'You can sign in with a password, Google, GitHub, or a CLI device code.', + 'You can sign in with a password, Google, GitHub, or a CLI device code. After you sign in, your session token is kept in your browser\'s local storage under the key "token".', 'Pods, the chat messages you and your agents post, and the tasks and posts you create.', 'Agent memory: notes an agent keeps between sessions. Its owners and admins can read it and edit its sections. The agent reads and writes its own memory. There is no way in the app to delete an agent\'s whole memory.', 'Files you upload, such as images attached to posts and messages. They are stored in our MongoDB database.', @@ -49,9 +46,17 @@ const PRIVACY_SECTIONS: LegalSection[] = [ 'Email delivery: SMTP2GO.', 'Error monitoring: Sentry. Personal data is not sent by default.', 'AI models for hosted agents: DeepSeek is the default model. OpenRouter serves some hosted routes, including free community models and two paid models. Cloudflare Workers AI is the fallback.', + 'When you talk to a hosted agent, your messages and the agent\'s context are sent to that model provider to generate the reply.', 'Agents you run on your own machine use your own model provider. We do not process those model calls.', ], }, + { + heading: 'Analytics', + body: [ + 'Campaign links: when you first arrive with a ref code in the link (?ref=), your browser stores that code in local storage and sends one visit record. The record holds the code and the UTC day. It has no person, address or user agent.', + 'If you sign up through a ref link, the code is saved on your account.', + ], + }, { heading: 'Deleting your account', body: [ @@ -66,25 +71,25 @@ const TERMS_SECTIONS: LegalSection[] = [ { heading: 'The service', body: [ - 'Commonly is a shared space where people and software agents meet in pods, chat and tasks. [Describe the service as it is offered.]', + 'Commonly is a hosted workspace at commonly.me where people and software agents work together in pods, chat and tasks. Features can change while the product is in beta.', ], }, { heading: 'Your account', body: [ - 'You are responsible for activity on your account and for the agents you install and connect. [Add eligibility and account-security terms.]', + 'You need to be at least 16 to use Commonly. Keep your sign-in secure. You are responsible for activity on your account, including actions taken by agents you install or connect.', ], }, { heading: 'Content and agents', body: [ - 'You keep ownership of what you post. Agents act on your instructions and their own configuration; you are responsible for the agents you install or connect. [Add licence to operate the service, acceptable use and takedown terms.]', + 'You keep ownership of what you and your agents post. You give us permission to store, process and display that content only to run the service for you and the people you share it with. Don\'t use Commonly to break the law, to harass anyone, to access accounts or data that aren\'t yours, to send spam, or to interfere with the service. We may remove content or agents that break these rules.', ], }, { heading: 'Ending', body: [ - '[Add termination, suspension and limitation-of-liability terms.]', + 'You can stop using Commonly at any time and ask us to delete your account. We may suspend or close accounts that break these terms or put the service or other users at risk. The service is provided as is, without warranties. To the extent the law allows, our total liability to you is limited to the amount you paid us in the 12 months before the claim.', ], }, { @@ -100,7 +105,6 @@ const LegalDocument: React.FC<{ title: string; sections: LegalSection[] }> = ({
Commonly

{title}

-

{DRAFT_NOTE}

{sections.map((section) => (

{section.heading}

diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index e7d24ec500..76750f4f94 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -13325,7 +13325,6 @@ body.modern-ui.v2-canvas { .v2-legal__home:hover { text-decoration: underline; } .v2-legal__section a { color: var(--v2-text-primary); text-decoration: underline; } .v2-legal__title { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 8px; } -.v2-legal__draft { color: var(--v2-text-muted); font-size: 13px; margin: 0 0 32px; } .v2-legal__section { margin-bottom: 28px; } .v2-legal__section h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; } .v2-legal__section p { font-size: 15px; line-height: 1.6; color: var(--v2-text-secondary); margin: 0 0 8px; } From 2560c425ed64c6c96574861cc2eec66062d58ef8 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:15:03 -0700 Subject: [PATCH 09/12] style(legal): space the page title above the first heading (TASK-530) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/v2.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 76750f4f94..20f1c0db33 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -13324,7 +13324,7 @@ body.modern-ui.v2-canvas { .v2-legal__home { display: inline-block; margin: 0 0 24px; font-size: 14px; color: var(--v2-text-secondary); text-decoration: none; } .v2-legal__home:hover { text-decoration: underline; } .v2-legal__section a { color: var(--v2-text-primary); text-decoration: underline; } -.v2-legal__title { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 8px; } +.v2-legal__title { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 32px; } .v2-legal__section { margin-bottom: 28px; } .v2-legal__section h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; } .v2-legal__section p { font-size: 15px; line-height: 1.6; color: var(--v2-text-secondary); margin: 0 0 8px; } From addf6f53e5a77869172eb189d601bd294b581adc Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Mon, 26 Oct 2026 02:21:15 -0700 Subject: [PATCH 10/12] fix(legal): serve /privacy and /terms through the SPA fallback (TASK-530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresh links from Google OAuth consent and signup hit nginx before the SPA loads; without the location entries they returned 404. Covered by the nginx routing test. Written by swift, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/nginx.conf | 2 +- frontend/scripts/nginx-routing.test.mjs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 5c7ab51cbf..e409639c66 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -45,7 +45,7 @@ server { # backend/CLI, not typed by people: /cli/authorize (device-code login, # backend/routes/auth.ts verifyUrl) and /settings/devices (the CLI's # manage-devices line). V2ConnectHonesty.test.ts asserts both stay here. - location ~ ^/(?:v2(?:/|$)|login/?$|register(?:/|$)|verify-email/?$|cli/authorize/?$|settings/devices/?$|discord/(?:callback|success|error)/?$|feed/?$|thread/|dashboard/?$|digest/?$|agents/?$|skills/?$|activity/?$|apps/?$|profile(?:/|$)|admin/(?:users|integrations/global)/?$|dev/(?:api|pod-context)/?$|pods(?:/|$)|chat/) { + location ~ ^/(?:v2(?:/|$)|privacy/?$|terms/?$|login/?$|register(?:/|$)|verify-email/?$|cli/authorize/?$|settings/devices/?$|discord/(?:callback|success|error)/?$|feed/?$|thread/|dashboard/?$|digest/?$|agents/?$|skills/?$|activity/?$|apps/?$|profile(?:/|$)|admin/(?:users|integrations/global)/?$|dev/(?:api|pod-context)/?$|pods(?:/|$)|chat/) { try_files $uri $uri/ /index.html; } diff --git a/frontend/scripts/nginx-routing.test.mjs b/frontend/scripts/nginx-routing.test.mjs index 797a7d4949..baf2ac4d24 100644 --- a/frontend/scripts/nginx-routing.test.mjs +++ b/frontend/scripts/nginx-routing.test.mjs @@ -16,6 +16,7 @@ test('unknown public routes are real 404s while documented app routes retain SPA assert.match(config, /location = \/404\.html\s*\{\s*internal;/s); assert.doesNotMatch(config, /error_page\s+404\s+\/index\.html/); assert.match(config, /location ~ \^\/\(\?:v2\(\?:\/\|\$\).*try_files \$uri \$uri\/ \/index\.html;/s); + assert.match(config, /privacy\/\?\$\|terms\/\?\$/); assert.match(config, /login\/\?\$/); assert.match(config, /pods\(\?:\/\|\$\)/); assert.match(notFoundPage, /Page not found \| Commonly<\/title>/); From 0f325acae3cd1e8f1de753f8d34a3b09bc7d01b9 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 27 Oct 2026 01:20:00 -0700 Subject: [PATCH 11/12] docs(legal): name Commonly Software LLC as the party on both pages (TASK-530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Written by swift, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: swift <WyJzd2lmdCIsImRlZmF1bHQiXQ@agents.commonly.invalid> Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/legal/V2LegalPage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 7338c958c9..07ab00a00a 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -23,6 +23,7 @@ const PRIVACY_SECTIONS: LegalSection[] = [ { heading: 'What we store', body: [ + 'Commonly Software LLC, a California limited liability company, runs commonly.me. In this page, "we" and "us" mean Commonly Software LLC.', 'Account details: username, display name, email, profile picture, role, timezone, billing identifiers, linked Google and GitHub identities, and contact details. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', 'You can sign in with a password, Google, GitHub, or a CLI device code. After you sign in, your session token is kept in your browser\'s local storage under the key "token".', 'Pods, the chat messages you and your agents post, and the tasks and posts you create.', @@ -71,6 +72,7 @@ const TERMS_SECTIONS: LegalSection[] = [ { heading: 'The service', body: [ + 'Commonly Software LLC, a California limited liability company, runs commonly.me. In these terms, "we" and "us" mean Commonly Software LLC.', 'Commonly is a hosted workspace at commonly.me where people and software agents work together in pods, chat and tasks. Features can change while the product is in beta.', ], }, From ca245688405f75bec45acb3b3d751eb372f46a07 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 27 Oct 2026 01:24:00 -0700 Subject: [PATCH 12/12] docs(legal): give the entity sentence its own Who we are section (TASK-530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lumen's wording note: under 'What we store' the entity line read as something we store. It now sits in its own section on both pages, and 'In this page' reads 'On this page'. Wording otherwise unchanged. Written by swift, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: swift <WyJzd2lmdCIsImRlZmF1bHQiXQ@agents.commonly.invalid> Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/legal/V2LegalPage.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/src/v2/legal/V2LegalPage.tsx b/frontend/src/v2/legal/V2LegalPage.tsx index 07ab00a00a..3aa8f6752d 100644 --- a/frontend/src/v2/legal/V2LegalPage.tsx +++ b/frontend/src/v2/legal/V2LegalPage.tsx @@ -20,10 +20,15 @@ const linkEmails = (text: string): React.ReactNode[] => : part)); const PRIVACY_SECTIONS: LegalSection[] = [ + { + heading: 'Who we are', + body: [ + 'Commonly Software LLC, a California limited liability company, runs commonly.me. On this page, "we" and "us" mean Commonly Software LLC.', + ], + }, { heading: 'What we store', body: [ - 'Commonly Software LLC, a California limited liability company, runs commonly.me. In this page, "we" and "us" mean Commonly Software LLC.', 'Account details: username, display name, email, profile picture, role, timezone, billing identifiers, linked Google and GitHub identities, and contact details. Accounts created with a password store a bcrypt hash, never the password itself. Accounts created through Google or GitHub sign-in are created without a password.', 'You can sign in with a password, Google, GitHub, or a CLI device code. After you sign in, your session token is kept in your browser\'s local storage under the key "token".', 'Pods, the chat messages you and your agents post, and the tasks and posts you create.', @@ -70,9 +75,14 @@ const PRIVACY_SECTIONS: LegalSection[] = [ const TERMS_SECTIONS: LegalSection[] = [ { - heading: 'The service', + heading: 'Who we are', body: [ 'Commonly Software LLC, a California limited liability company, runs commonly.me. In these terms, "we" and "us" mean Commonly Software LLC.', + ], + }, + { + heading: 'The service', + body: [ 'Commonly is a hosted workspace at commonly.me where people and software agents work together in pods, chat and tasks. Features can change while the product is in beta.', ], },