trails/docs/legal-archive
Ullrich Schäfer 5905cdadea
Apply public-content-visibility: visibility flag + public profile
Implements the public-content-visibility OpenSpec change. Adds the
smallest social surface that lets us demo the product to logged-out
visitors without user signup.

Schema:
- `visibility text NOT NULL DEFAULT 'private'` on routes + activities.
- Shared Visibility type exported from the schema module.

Access:
- New canView(content, viewer, { asDirectLink }) helper in auth.server.ts
  centralises the rule: public → anyone; unlisted → anyone on direct
  link; private → owner only.
- routes.$id and activities.$id loaders return 404 (not 403) when
  canView rejects, so existence of private content isn't leaked.
- Detail pages emit Open Graph + Twitter Card meta on public/unlisted
  content only.

Editing:
- Visibility <select> on routes/:id/edit with owner-only access.
- Activity detail page gets a small visibility form + set-visibility
  action intent (no separate activity-edit page needed).
- EN + DE i18n under routes.visibility.* and activities.visibility.*.

Listings:
- Listing helpers listPublicRoutesForOwner / listPublicActivitiesForOwner
  for cross-user queries. Existing owner-scoped listRoutes/listActivities
  stay — owners see their own content regardless of visibility.

Public profile:
- /users/:username is now truly public. Renders the user's public
  routes + activities, 404s when no public content exists AND viewer
  isn't the owner (prevents account enumeration).
- Owner sees a short "this is your profile" note linking to settings.
- Open Graph meta (og:type=profile) for shareable preview.

Privacy manifest:
- Added a bullet noting public content is world-visible on profile
  and indexable by search engines.
- Bumped PRIVACY_LAST_UPDATED to 2026-04-20 + rendered legal-archive
  snapshot.

Tests:
- 13 unit tests for canView covering the full matrix.
- 6 E2E tests in e2e/public-content.test.ts covering:
  - Private route → 404 for logged-out visitor
  - Public route → reachable + OG tags present (og:title, og:type,
    og:site_name)
  - Owner still sees own private content
  - Profile 404 when no public content
  - Profile renders when at least one public route exists
  - Unlisted route reachable via direct URL but hidden from profile
- Test file runs serially (describe.configure mode=serial) to avoid
  WebAuthn virtual-authenticator races under Playwright's default
  parallel workers.
- New public-content Playwright project added to config.

Rollout safety: every existing row in prod keeps visibility='private'
by default — nothing becomes visible to outsiders until an owner
explicitly opts in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 09:11:39 +02:00
..
imprint-2026-04-19.md Seed docs/legal-archive with Terms / Privacy / Imprint snapshots 2026-04-19 07:51:41 +02:00
privacy-2026-04-19.md Seed docs/legal-archive with Terms / Privacy / Imprint snapshots 2026-04-19 07:51:41 +02:00
privacy-2026-04-20.md Apply public-content-visibility: visibility flag + public profile 2026-04-19 09:11:39 +02:00
README.md Convert render-legal to TypeScript 2026-04-19 07:52:53 +02:00
terms-2026-04-19.md Seed docs/legal-archive with Terms / Privacy / Imprint snapshots 2026-04-19 07:51:41 +02:00

Legal archive

Frozen snapshots of the Terms of Service, Privacy Policy, and Impressum at each version. Kept so we can always answer "what did the Terms / Privacy text say on a given date" without digging through git blame.

Why

  • Terms: users accept a specific version at registration (users.terms_accepted_at + users.terms_version). The version string is the date in this folder — so there is always a file here matching every value that exists in that column.
  • Privacy: users don't "accept" a privacy policy, but GDPR Art. 13/14 requires us to tell users how their data is processed at the time it's processed. If a regulator or user asks what the policy said on YYYY-MM-DD, this folder answers.
  • Impressum: less legally critical, but free to snapshot for symmetry.

When to add a file

Whenever any of these three texts change materially, run the snapshot step as part of the change:

  1. Bump TERMS_VERSION / PRIVACY_LAST_UPDATED in apps/journal/app/lib/legal.ts to today's date.
  2. Re-render the affected page(s) into this folder as <doc>-YYYY-MM-DD.md.
  3. Commit the bump and the new snapshot in the same PR.

A trivial wording tweak or typo fix does not need a new snapshot; only changes that affect meaning / behaviour / purposes / third parties / legal basis / retention / etc.

File naming

<doc>-YYYY-MM-DD.md where <doc> is one of terms, privacy, imprint, and the date matches the Last updated line in the corresponding legal page on the day the snapshot was taken.

How to render

Snapshots are extracted from the TSX source by stripping JSX tags / attributes and resolving the operator.* placeholders plus the constants from apps/journal/app/lib/legal.ts. One-liner (from repo root):

node --experimental-strip-types scripts/render-legal.ts <doc> \
  > docs/legal-archive/<doc>-YYYY-MM-DD.md

where <doc> is one of terms, privacy, imprint.