From 9a904d9f691cbe0dddcb184d65e12b2f9215cea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Wed, 25 Mar 2026 03:11:07 +0100 Subject: [PATCH 1/3] Sentry improvements: context, tracing, source maps, privacy manifest - Set Sentry user context (id, username) in Journal root for all routes - Tag Planner errors with session_id - Use reactRouterV7BrowserTracingIntegration for route-aware traces - Hidden source maps (no sourceMappingURL in bundles, .map deleted after upload to Sentry) - Privacy manifest at /privacy documenting all data collection - robots.txt blocking all bots on both apps - Suppress i18next promotional console log Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/journal/app/entry.client.tsx | 13 ++- apps/journal/app/root.tsx | 18 +++- apps/journal/app/routes/home.tsx | 6 ++ apps/journal/app/routes/privacy.tsx | 98 +++++++++++++++++++ apps/journal/public/robots.txt | 2 + apps/journal/vite.config.ts | 3 +- apps/planner/app/components/SessionView.tsx | 4 +- apps/planner/app/entry.client.tsx | 13 ++- apps/planner/public/robots.txt | 2 + apps/planner/vite.config.ts | 3 +- .../sentry-improvements/.openspec.yaml | 2 + .../changes/sentry-improvements/design.md | 38 +++++++ .../changes/sentry-improvements/proposal.md | 28 ++++++ .../specs/infrastructure/spec.md | 24 +++++ openspec/changes/sentry-improvements/tasks.md | 24 +++++ packages/i18n/src/index.ts | 1 + 16 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 apps/journal/app/routes/privacy.tsx create mode 100644 apps/journal/public/robots.txt create mode 100644 apps/planner/public/robots.txt create mode 100644 openspec/changes/sentry-improvements/.openspec.yaml create mode 100644 openspec/changes/sentry-improvements/design.md create mode 100644 openspec/changes/sentry-improvements/proposal.md create mode 100644 openspec/changes/sentry-improvements/specs/infrastructure/spec.md create mode 100644 openspec/changes/sentry-improvements/tasks.md diff --git a/apps/journal/app/entry.client.tsx b/apps/journal/app/entry.client.tsx index 449a4a7..1efd2c4 100644 --- a/apps/journal/app/entry.client.tsx +++ b/apps/journal/app/entry.client.tsx @@ -1,14 +1,25 @@ import * as Sentry from "@sentry/react"; +import { useEffect } from "react"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; +import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from "react-router"; const sentryEnvironment = import.meta.env.VITE_SENTRY_ENVIRONMENT ?? (import.meta.env.PROD ? "production" : "development"); Sentry.init({ dsn: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728", - integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], + integrations: [ + Sentry.reactRouterV7BrowserTracingIntegration({ + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + Sentry.replayIntegration(), + ], environment: sentryEnvironment, tracesSampleRate: sentryEnvironment === "ci" ? 0 : 1.0, replaysSessionSampleRate: sentryEnvironment === "ci" ? 0 : 1.0, diff --git a/apps/journal/app/root.tsx b/apps/journal/app/root.tsx index 6802b50..51db198 100644 --- a/apps/journal/app/root.tsx +++ b/apps/journal/app/root.tsx @@ -1,9 +1,11 @@ +import { useEffect } from "react"; import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from "react-router"; import type { LinksFunction } from "react-router"; import type { Route } from "./+types/root"; import * as Sentry from "@sentry/react"; import { useTranslation } from "react-i18next"; import { initI18n } from "@trails-cool/i18n"; +import { getSessionUser } from "~/lib/auth.server"; import stylesheet from "@trails-cool/ui/styles.css?url"; initI18n(); @@ -28,7 +30,21 @@ export function Layout({ children }: { children: React.ReactNode }) { ); } -export default function App() { +export async function loader({ request }: Route.LoaderArgs) { + const user = await getSessionUser(request); + return { user: user ? { id: user.id, username: user.username } : null }; +} + +export default function App({ loaderData }: Route.ComponentProps) { + const user = loaderData?.user; + useEffect(() => { + if (user) { + Sentry.setUser({ id: user.id, username: user.username }); + } else { + Sentry.setUser(null); + } + }, [user]); + return ; } diff --git a/apps/journal/app/routes/home.tsx b/apps/journal/app/routes/home.tsx index 196ad10..b32dd7b 100644 --- a/apps/journal/app/routes/home.tsx +++ b/apps/journal/app/routes/home.tsx @@ -125,6 +125,12 @@ export default function Home({ loaderData }: Route.ComponentProps) { )} + + ); } diff --git a/apps/journal/app/routes/privacy.tsx b/apps/journal/app/routes/privacy.tsx new file mode 100644 index 0000000..b0c1fd0 --- /dev/null +++ b/apps/journal/app/routes/privacy.tsx @@ -0,0 +1,98 @@ +import type { Route } from "./+types/privacy"; + +export function meta(_args: Route.MetaArgs) { + return [{ title: "Privacy — trails.cool" }]; +} + +export default function PrivacyPage() { + return ( +
+

Privacy Manifest

+

+ trails.cool is committed to privacy by design. This manifest documents + everything we collect, why, and how you can control it. +

+ +
+

Planner (planner.trails.cool)

+

+ The Planner collects no personal data. Sessions are anonymous — + there are no user accounts, no tracking, and no analytics on your routes. +

+
    +
  • No cookies (except ephemeral session state)
  • +
  • No user accounts or login
  • +
  • No route data is stored permanently without your action
  • +
  • Session data is automatically deleted after 7 days of inactivity
  • +
+
+ +
+

Journal (trails.cool)

+

+ The Journal stores the data you explicitly provide: +

+
    +
  • Account data: email, username, display name, passkey credentials
  • +
  • Routes: name, description, GPX data, geometry
  • +
  • Activities: title, description, date, linked routes
  • +
+

+ All your data is exportable at any time in open formats (GPX, JSON). + You can self-host your own instance and migrate your data completely. +

+
+ +
+

Error Tracking (Sentry)

+

+ Both apps use Sentry for + error monitoring. This helps us find and fix bugs quickly. +

+

What Sentry collects:

+
    +
  • Error details: stack traces, error messages, browser/OS info
  • +
  • Performance traces: page load times, route navigation timing
  • +
  • Session replays on error: a recording of the session leading up to an error (DOM snapshots, not video)
  • +
  • User context (Journal only): user ID and username are attached to errors for debugging
  • +
  • Session ID (Planner only): the anonymous session ID is attached to errors
  • +
+

What Sentry does NOT collect:

+
    +
  • Route or GPX data
  • +
  • Passwords or passkey credentials
  • +
  • Form input contents (masked in replays)
  • +
+

Data retention:

+

+ Sentry data is retained for 90 days and then automatically deleted. + Sentry's servers are hosted in the EU (Frankfurt). +

+
+ +
+

Third Parties

+
    +
  • Sentry (Functional Software Inc.) — error tracking, as described above
  • +
  • OpenStreetMap — map tiles are loaded from OSM tile servers. OSM's privacy policy applies to tile requests.
  • +
  • BRouter — routing requests are processed by our self-hosted BRouter instance. No data is sent to third parties for routing.
  • +
+
+ +
+

What We Don't Do

+
    +
  • We don't sell data
  • +
  • We don't show ads
  • +
  • We don't build user profiles
  • +
  • We don't use tracking pixels or analytics
  • +
  • We don't share data with anyone except as listed above
  • +
+
+ +

+ Last updated: March 2026. If this manifest changes, we'll note it here. +

+
+ ); +} diff --git a/apps/journal/public/robots.txt b/apps/journal/public/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/apps/journal/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/apps/journal/vite.config.ts b/apps/journal/vite.config.ts index 44fe7d1..e799794 100644 --- a/apps/journal/vite.config.ts +++ b/apps/journal/vite.config.ts @@ -6,7 +6,7 @@ import path from "node:path"; export default defineConfig({ build: { - sourcemap: true, + sourcemap: "hidden", }, plugins: [ tailwindcss(), @@ -15,6 +15,7 @@ export default defineConfig({ org: "trails-qq", project: "journal", release: { name: process.env.SENTRY_RELEASE }, + sourcemaps: { filesToDeleteAfterUpload: ["./build/**/*.map"] }, disable: !process.env.SENTRY_AUTH_TOKEN, }), ], diff --git a/apps/planner/app/components/SessionView.tsx b/apps/planner/app/components/SessionView.tsx index b7b6251..bf56abc 100644 --- a/apps/planner/app/components/SessionView.tsx +++ b/apps/planner/app/components/SessionView.tsx @@ -1,5 +1,6 @@ -import { Suspense, lazy, useState, useCallback } from "react"; +import { Suspense, lazy, useState, useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import * as Sentry from "@sentry/react"; import { useYjs } from "~/lib/use-yjs"; import { useRouting } from "~/lib/use-routing"; import { ProfileSelector } from "~/components/ProfileSelector"; @@ -27,6 +28,7 @@ interface SessionViewProps { export function SessionView({ sessionId, callbackUrl, callbackToken, returnUrl, initialWaypoints }: SessionViewProps) { const { t } = useTranslation("planner"); + useEffect(() => { Sentry.setTag("session_id", sessionId); }, [sessionId]); const yjs = useYjs(sessionId, initialWaypoints); const { isHost, computing, routeStats, requestRoute } = useRouting(yjs); const [highlightPosition, setHighlightPosition] = useState<[number, number] | null>(null); diff --git a/apps/planner/app/entry.client.tsx b/apps/planner/app/entry.client.tsx index b266770..b425aa0 100644 --- a/apps/planner/app/entry.client.tsx +++ b/apps/planner/app/entry.client.tsx @@ -1,14 +1,25 @@ import * as Sentry from "@sentry/react"; +import { useEffect } from "react"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; +import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from "react-router"; const sentryEnvironment = import.meta.env.VITE_SENTRY_ENVIRONMENT ?? (import.meta.env.PROD ? "production" : "development"); Sentry.init({ dsn: "https://5215134cd78d5e6c199e29300b8425af@o4509530546634752.ingest.de.sentry.io/4511102546608208", - integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], + integrations: [ + Sentry.reactRouterV7BrowserTracingIntegration({ + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + Sentry.replayIntegration(), + ], environment: sentryEnvironment, tracesSampleRate: sentryEnvironment === "ci" ? 0 : 1.0, replaysSessionSampleRate: sentryEnvironment === "ci" ? 0 : 1.0, diff --git a/apps/planner/public/robots.txt b/apps/planner/public/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/apps/planner/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/apps/planner/vite.config.ts b/apps/planner/vite.config.ts index a97a32a..cb4d8a8 100644 --- a/apps/planner/vite.config.ts +++ b/apps/planner/vite.config.ts @@ -7,7 +7,7 @@ import { yjsDevPlugin } from "./app/lib/vite-yjs-plugin.ts"; export default defineConfig({ build: { - sourcemap: true, + sourcemap: "hidden", }, plugins: [ tailwindcss(), @@ -17,6 +17,7 @@ export default defineConfig({ org: "trails-qq", project: "planner", release: { name: process.env.SENTRY_RELEASE }, + sourcemaps: { filesToDeleteAfterUpload: ["./build/**/*.map"] }, disable: !process.env.SENTRY_AUTH_TOKEN, }), ], diff --git a/openspec/changes/sentry-improvements/.openspec.yaml b/openspec/changes/sentry-improvements/.openspec.yaml new file mode 100644 index 0000000..40c5540 --- /dev/null +++ b/openspec/changes/sentry-improvements/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-25 diff --git a/openspec/changes/sentry-improvements/design.md b/openspec/changes/sentry-improvements/design.md new file mode 100644 index 0000000..ee91875 --- /dev/null +++ b/openspec/changes/sentry-improvements/design.md @@ -0,0 +1,38 @@ +## Context + +Both apps have basic Sentry (`@sentry/react` + `@sentry/node`) with source map uploads, release tracking, and environment tagging. Errors appear in Sentry but lack user/session context, making triage slow. Source maps are also served publicly. + +## Goals / Non-Goals + +**Goals:** +- Identify which user or session caused an error without guessing +- Route-level performance traces (not just page loads) +- Stop serving source maps to browsers + +**Non-Goals:** +- Custom Sentry dashboards or alert rules (configure in Sentry UI) +- Server-side performance instrumentation beyond request handling +- Profiling integration + +## Decisions + +### D1: Set user context via Sentry.setUser in root loader + +Journal's root loader already fetches the session user. Call `Sentry.setUser({ id, username })` on the client when authenticated, `Sentry.setUser(null)` when not. Done in root.tsx since it runs on every navigation. + +### D2: Tag planner errors with session ID via Sentry.setTag + +In `SessionView`, call `Sentry.setTag("session_id", sessionId)` on mount. This tags all subsequent errors with the active session. + +### D3: Use reactRouterV7BrowserTracingIntegration + +Sentry provides `Sentry.reactRouterV7BrowserTracingIntegration` which hooks into React Router's navigation to create route-aware spans. Replace the generic `browserTracingIntegration()` in both `entry.client.tsx` files. Requires passing `useEffect` and `useLocation` from react-router. + +### D4: Hidden source maps via Vite config + +Change `build.sourcemap` from `true` to `"hidden"`. This generates `.map` files for the Sentry plugin to upload but doesn't add `//# sourceMappingURL` comments to the bundles. Browsers won't request the map files. The Sentry plugin's `sourcemaps.filesToDeleteAfterUpload` option can also clean them from the build output. + +## Risks / Trade-offs + +- **D3 couples Sentry to React Router version** → Acceptable; we already depend on both. If we upgrade React Router, we upgrade the Sentry integration too. +- **D4 makes browser debugging harder in production** → Acceptable; errors go to Sentry with full source context. DevTools debugging in production was never a supported workflow. diff --git a/openspec/changes/sentry-improvements/proposal.md b/openspec/changes/sentry-improvements/proposal.md new file mode 100644 index 0000000..9c0dd38 --- /dev/null +++ b/openspec/changes/sentry-improvements/proposal.md @@ -0,0 +1,28 @@ +## Why + +Sentry is deployed but errors lack context — we don't know which user or session triggered them, route-level performance is invisible, and source maps are served to clients unnecessarily. These gaps make debugging harder and leak internal details. + +## What Changes + +- Set Sentry user context when a Journal user is authenticated (user ID, username) +- Tag Planner errors with the session ID for debugging +- Replace generic `browserTracingIntegration` with `reactRouterV7BrowserTracingIntegration` for route-aware performance traces +- Configure Vite to produce source maps as `hidden` (uploaded to Sentry but not referenced in the bundle, so browsers don't fetch them) +- Wrap server-side request handlers with Sentry error capturing for unhandled exceptions + +## Capabilities + +### New Capabilities + +(None — this enhances the existing Sentry integration, no new behavioral capabilities.) + +### Modified Capabilities + +- `infrastructure`: Adding Sentry context enrichment and source map handling to the deployment pipeline + +## Impact + +- **Files**: `entry.client.tsx` (both apps), `entry.server.tsx` (journal), `server.ts` (planner), `vite.config.ts` (both apps), Journal root loader or layout +- **Dependencies**: No new packages — `@sentry/react` and `@sentry/node` already support all features +- **APIs**: No API changes +- **Security**: Source maps no longer served to clients (currently `.map` files are publicly accessible) diff --git a/openspec/changes/sentry-improvements/specs/infrastructure/spec.md b/openspec/changes/sentry-improvements/specs/infrastructure/spec.md new file mode 100644 index 0000000..478e701 --- /dev/null +++ b/openspec/changes/sentry-improvements/specs/infrastructure/spec.md @@ -0,0 +1,24 @@ +## MODIFIED Requirements + +### Requirement: Sentry error tracking +The system SHALL enrich Sentry events with user and session context, use route-aware tracing, and prevent source maps from being served to clients. + +#### Scenario: Journal error includes user context +- **WHEN** an authenticated Journal user triggers an error +- **THEN** the Sentry event SHALL include the user's ID and username + +#### Scenario: Journal error without user context +- **WHEN** an unauthenticated visitor triggers an error +- **THEN** the Sentry event SHALL have no user context (Sentry.setUser(null)) + +#### Scenario: Planner error includes session ID +- **WHEN** an error occurs during a Planner session +- **THEN** the Sentry event SHALL include a `session_id` tag with the active session ID + +#### Scenario: Route-level performance traces +- **WHEN** a user navigates between routes in either app +- **THEN** Sentry SHALL create a transaction span named after the route pattern (e.g., `/routes/:id`) + +#### Scenario: Source maps not served to clients +- **WHEN** a client requests a `.map` file from the production server +- **THEN** the server SHALL return 404 (source maps are uploaded to Sentry during build, not shipped in the bundle) diff --git a/openspec/changes/sentry-improvements/tasks.md b/openspec/changes/sentry-improvements/tasks.md new file mode 100644 index 0000000..f13cff5 --- /dev/null +++ b/openspec/changes/sentry-improvements/tasks.md @@ -0,0 +1,24 @@ +## 1. User & Session Context + +- [x] 1.1 Set Sentry user context (id, username) in Journal root loader/component when authenticated, clear when not +- [x] 1.2 Set Sentry `session_id` tag in Planner's SessionView on mount + +## 2. Route-Aware Tracing + +- [x] 2.1 Replace `browserTracingIntegration()` with `reactRouterV7BrowserTracingIntegration` in planner entry.client.tsx +- [x] 2.2 Replace `browserTracingIntegration()` with `reactRouterV7BrowserTracingIntegration` in journal entry.client.tsx + +## 3. Source Map Hardening + +- [x] 3.1 Change `build.sourcemap` from `true` to `"hidden"` in both vite.config.ts files +- [x] 3.2 Add `sourcemaps.filesToDeleteAfterUpload` to the Sentry Vite plugin config to clean up .map files after upload + +## 4. Privacy Manifest + +- [x] 4.1 Create `/privacy` route in Journal with a user-visible privacy manifest documenting: what Sentry collects (errors, traces, session replays), what the Planner does NOT collect, data retention, and third-party disclosure (Sentry) +- [x] 4.2 Add link to privacy manifest in Journal footer/home page + +## 5. Verify + +- [x] 5.1 Test locally: build, check no `.map` files remain in build output (or no sourceMappingURL in bundles) +- [x] 5.2 Test locally: trigger error in Journal while logged in, verify Sentry.setUser was called (check devtools) diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index 807b0d9..a50dca9 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -23,6 +23,7 @@ export function initI18n() { interpolation: { escapeValue: false, }, + showSupportNotice: false, }); } From 9d51fd6b3669b3fac5f108d67c271f1dafc03477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Wed, 25 Mar 2026 03:13:36 +0100 Subject: [PATCH 2/3] Archive sentry-improvements, sync specs Sync Sentry error tracking requirement (5 scenarios) to main infrastructure spec. Archive completed change. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../.openspec.yaml | 0 .../2026-03-25-sentry-improvements}/design.md | 0 .../proposal.md | 0 .../specs/infrastructure/spec.md | 0 .../2026-03-25-sentry-improvements}/tasks.md | 0 openspec/specs/infrastructure/spec.md | 23 +++++++++++++++++++ 6 files changed, 23 insertions(+) rename openspec/changes/{sentry-improvements => archive/2026-03-25-sentry-improvements}/.openspec.yaml (100%) rename openspec/changes/{sentry-improvements => archive/2026-03-25-sentry-improvements}/design.md (100%) rename openspec/changes/{sentry-improvements => archive/2026-03-25-sentry-improvements}/proposal.md (100%) rename openspec/changes/{sentry-improvements => archive/2026-03-25-sentry-improvements}/specs/infrastructure/spec.md (100%) rename openspec/changes/{sentry-improvements => archive/2026-03-25-sentry-improvements}/tasks.md (100%) diff --git a/openspec/changes/sentry-improvements/.openspec.yaml b/openspec/changes/archive/2026-03-25-sentry-improvements/.openspec.yaml similarity index 100% rename from openspec/changes/sentry-improvements/.openspec.yaml rename to openspec/changes/archive/2026-03-25-sentry-improvements/.openspec.yaml diff --git a/openspec/changes/sentry-improvements/design.md b/openspec/changes/archive/2026-03-25-sentry-improvements/design.md similarity index 100% rename from openspec/changes/sentry-improvements/design.md rename to openspec/changes/archive/2026-03-25-sentry-improvements/design.md diff --git a/openspec/changes/sentry-improvements/proposal.md b/openspec/changes/archive/2026-03-25-sentry-improvements/proposal.md similarity index 100% rename from openspec/changes/sentry-improvements/proposal.md rename to openspec/changes/archive/2026-03-25-sentry-improvements/proposal.md diff --git a/openspec/changes/sentry-improvements/specs/infrastructure/spec.md b/openspec/changes/archive/2026-03-25-sentry-improvements/specs/infrastructure/spec.md similarity index 100% rename from openspec/changes/sentry-improvements/specs/infrastructure/spec.md rename to openspec/changes/archive/2026-03-25-sentry-improvements/specs/infrastructure/spec.md diff --git a/openspec/changes/sentry-improvements/tasks.md b/openspec/changes/archive/2026-03-25-sentry-improvements/tasks.md similarity index 100% rename from openspec/changes/sentry-improvements/tasks.md rename to openspec/changes/archive/2026-03-25-sentry-improvements/tasks.md diff --git a/openspec/specs/infrastructure/spec.md b/openspec/specs/infrastructure/spec.md index 69342ba..7399863 100644 --- a/openspec/specs/infrastructure/spec.md +++ b/openspec/specs/infrastructure/spec.md @@ -63,3 +63,26 @@ The infrastructure SHALL configure DNS and TLS for trails.cool and planner.trail #### Scenario: HTTPS access - **WHEN** a user navigates to https://trails.cool - **THEN** the connection is secured with a valid TLS certificate + +### Requirement: Sentry error tracking +The system SHALL enrich Sentry events with user and session context, use route-aware tracing, and prevent source maps from being served to clients. + +#### Scenario: Journal error includes user context +- **WHEN** an authenticated Journal user triggers an error +- **THEN** the Sentry event SHALL include the user's ID and username + +#### Scenario: Journal error without user context +- **WHEN** an unauthenticated visitor triggers an error +- **THEN** the Sentry event SHALL have no user context (Sentry.setUser(null)) + +#### Scenario: Planner error includes session ID +- **WHEN** an error occurs during a Planner session +- **THEN** the Sentry event SHALL include a `session_id` tag with the active session ID + +#### Scenario: Route-level performance traces +- **WHEN** a user navigates between routes in either app +- **THEN** Sentry SHALL create a transaction span named after the route pattern (e.g., `/routes/:id`) + +#### Scenario: Source maps not served to clients +- **WHEN** a client requests a `.map` file from the production server +- **THEN** the server SHALL return 404 (source maps are uploaded to Sentry during build, not shipped in the bundle) From 4a721fc586d44a4ccf0c85ab8317b629ff5c04f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Wed, 25 Mar 2026 03:15:38 +0100 Subject: [PATCH 3/3] Add privacy route to journal route config Route was missing from the explicit routes.ts, so typegen didn't generate types for it. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/journal/app/routes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/journal/app/routes.ts b/apps/journal/app/routes.ts index 050a85a..1e9bb96 100644 --- a/apps/journal/app/routes.ts +++ b/apps/journal/app/routes.ts @@ -19,4 +19,5 @@ export default [ route("activities/new", "routes/activities.new.tsx"), route("activities/:id", "routes/activities.$id.tsx"), route("users/:username", "routes/users.$username.tsx"), + route("privacy", "routes/privacy.tsx"), ] satisfies RouteConfig;