Archive public-content-visibility
Final tasks ticked post-merge: - 10.2: verified on prod that journal.routes + journal.activities still default to 'private' NOT NULL, with the only public rows being the 15-each demo-bot seeded content - 10.3: demo-activity-bot already inserts with visibility='public' directly in demo-bot.server.ts Syncs the three delta specs into main: + activity-feed: 2 added, 1 modified + public-profiles: new spec (1 added) + route-management: 2 added, 1 modified Moves change to openspec/changes/archive/2026-04-24-public-content-visibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9709a0c150
commit
ba6f09171d
10 changed files with 118 additions and 12 deletions
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-04-19
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
## Context
|
||||
|
||||
The Journal uses a straightforward "logged-in or you're not seeing anything" access model today. Routes and activities are owned by a single user via `owner_id` foreign keys, and every detail-page loader calls `getSessionUser(request)` then either serves the page or (implicitly) returns the "route not found" error. There is no notion of cross-user visibility.
|
||||
|
||||
The Planner is separately anonymous, so that's not a concern here — this change is Journal-only.
|
||||
|
||||
Two near-term pressures shape the design:
|
||||
|
||||
- **The demo plan** (send a URL to a prospective user or contributor and have them see real content without signing up). Anything that requires a friction step on arrival defeats the demo.
|
||||
- **Upcoming `demo-activity-bot`** that generates synthetic activities. That content has to be publicly viewable or the bot is pointless.
|
||||
|
||||
What this design deliberately *does not* touch:
|
||||
- Followers / subscriptions / per-user feeds
|
||||
- Reactions / comments / any write surface for non-owners
|
||||
- ActivityPub / Fedify / federation
|
||||
- Per-segment privacy on a single route
|
||||
|
||||
Those are all future chapters. Keeping the first step narrow means less to get wrong and less to re-design later.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- A route or activity owner can mark a piece of content as public.
|
||||
- Logged-out visitors can open that content at its permanent URL.
|
||||
- A logged-out visitor can find the rest of that user's public content via `/users/:username`.
|
||||
- Sharing a public URL on Slack / Twitter / Bluesky / email produces a decent preview.
|
||||
- Default stays private — nothing already in the database becomes visible just because this change ships.
|
||||
|
||||
**Non-Goals:**
|
||||
- Discovery (there is no "browse public routes" page on the instance yet).
|
||||
- Indexing control beyond the default (no per-route `robots` override).
|
||||
- Changing the Planner's anonymous model.
|
||||
- Any write access for non-owners on public content.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Visibility model: three values, not two
|
||||
|
||||
**Decision:** `visibility` enum `'private' | 'unlisted' | 'public'`. Stored as a text column for simplicity; can be migrated to a native enum later if worth it.
|
||||
|
||||
- `private` (default) — only the owner can fetch the detail page; listings never show it.
|
||||
- `unlisted` — anyone with the URL can view; excluded from the owner's public profile and any future discovery pages.
|
||||
- `public` — anyone can view; appears on the owner's public profile.
|
||||
|
||||
**Alternatives:**
|
||||
- **Just `private | public`** — simpler, but kills the useful "I want to share with someone without putting it on my profile" case. Every other sharing-oriented service has an unlisted tier; it's cheap to add now and irritating to retrofit later.
|
||||
- **Sharing via time-limited tokens** (Google-Docs-style) — more powerful but out of scope for the demo goal. Parked.
|
||||
|
||||
### Default is `private` for all existing rows
|
||||
|
||||
**Decision:** The new column ships with `DEFAULT 'private'` and every existing row — two real routes and zero activities — remains private. Users opt in to publication by explicitly changing the value.
|
||||
|
||||
**Why:** This change cannot silently un-privatise data anyone uploaded under the old rules. That principle outweighs the demo inconvenience (we'll mark the demo user's seeded content public at insert time).
|
||||
|
||||
### Access check lives in route loaders
|
||||
|
||||
**Decision:** Each detail-page loader (`routes.$id.tsx`, `activities.$id.tsx`) resolves the content row, compares `visibility` + `owner_id` against the session user:
|
||||
|
||||
- `visibility === 'public'` → serve.
|
||||
- `visibility === 'unlisted'` AND the request is a direct detail page URL → serve.
|
||||
- otherwise → serve only if the requester is the owner, else 404 (not 403 — leaking existence is a privacy bug).
|
||||
|
||||
A small helper `canView(content, user)` in `~/lib/auth.server` centralises the rule so list endpoints use the same logic.
|
||||
|
||||
**Alternatives:**
|
||||
- **Route-level middleware** (before loaders) — cleaner in theory but React Router 7 loaders are the right place for content-specific auth, and centralising via a helper avoids coupling the router tree to visibility semantics.
|
||||
|
||||
### Public profile page reuses the existing `/users/:username` route
|
||||
|
||||
**Decision:** That route exists today but is auth-gated (and renders the current user's own routes/activities). Broaden it:
|
||||
|
||||
- Publicly accessible.
|
||||
- Shows public routes + public activities of the requested username, most recent first.
|
||||
- 404 if the user has no public content at all. This hides the existence of private-only accounts; an attacker can't enumerate accounts by username.
|
||||
- A "This is your profile" control strip visible only to the logged-in owner, with a quick link to settings.
|
||||
|
||||
### Open Graph / Twitter Card meta tags
|
||||
|
||||
**Decision:** Each public route / activity detail page emits OG tags via React Router's `meta` export:
|
||||
|
||||
- `og:title` — route or activity name + "· trails.cool"
|
||||
- `og:description` — user description (truncated) or a sensible default
|
||||
- `og:type` — `"article"`
|
||||
- `og:site_name` — `"trails.cool"`
|
||||
- `twitter:card` — `"summary"` (summary, not summary_large_image; we don't have social cards yet)
|
||||
|
||||
A future `social-preview-images` follow-up can add rendered static map PNGs. Explicitly out of scope here.
|
||||
|
||||
### `robots` control
|
||||
|
||||
**Decision:** Public route / activity pages emit no `robots` meta (defaults to indexable). The legal pages keep their existing `noindex`. No sitemap is generated; discovery via search is passive.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Username enumeration via profile 404** → Mitigation: 404 both for "no such user" and "user exists but has no public content", so the two are indistinguishable. Timing-side-channel on the DB lookup is not defended against in v1; acceptable at this scale.
|
||||
- **Future listing pages will want more data** → The `canView` helper already supports it. Adding an `index` boolean or "discoverable" flag is additive.
|
||||
- **`unlisted` is only private-ish** → If the URL leaks it's effectively public. That's the Internet; same as every other unlisted-sharing feature. Documented in the settings UI copy.
|
||||
- **Content previously private becomes linkable** → Once a user makes a route public and shares it, they can't fully retract — archives exist. Same as any web publish. No new warning for v1; the visibility selector itself is unambiguous.
|
||||
- **Spec drift risk** → `route-management` and `activity-feed` both gain modified requirements; keep the delta specs tight so the archive survives review.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Merge schema change; `drizzle-kit push --force` on deploy adds the column with default `'private'`.
|
||||
2. Logged-out access to public pages is a new code path — nothing existing regresses since all rows remain `'private'` until a user changes them.
|
||||
3. `demo-activity-bot` is the first caller that will create rows with `visibility: 'public'` at insert time.
|
||||
4. Rollback: `UPDATE routes SET visibility = 'private'; UPDATE activities SET visibility = 'private';` restores the prior behaviour without any code change; the public code path becomes dead but harmless.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should the settings page add a default-visibility preference per user (e.g. "make new routes public by default")? Probably nice but not in this spec; revisit after observing real usage.
|
||||
- Do we want to emit `<link rel="canonical">` on unlisted pages to discourage indexing of the URL if it leaks? Low-cost, worth considering during implementation.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
## Why
|
||||
|
||||
trails.cool currently has no way to show its content to anyone who isn't logged in. Route and activity detail pages require auth, there is no public-facing profile page, and shared URLs have no Open Graph metadata — so a link pasted in Slack or on a social platform looks like a blank trails.cool card.
|
||||
|
||||
Two immediate reasons to fix this:
|
||||
|
||||
1. **Demos + acquisition.** The next phase of the project is onboarding more users and contributors. The pitch lands better when you can send a URL that opens directly to "look at this route / this weekend's ride" without making the visitor register first.
|
||||
2. **Prerequisite for any `demo-activity-bot` work.** A synthetic-activity generator is pointless if the generated content isn't publicly viewable. This change unblocks that follow-up.
|
||||
|
||||
This is also the smallest possible step into the "social" direction the Journal was always described as heading for. It does not introduce follow/follower, cross-user feeds, reactions, or ActivityPub — those remain future work.
|
||||
|
||||
## What Changes
|
||||
|
||||
- New `visibility` column on routes and activities with values `private` (default for existing rows), `unlisted` (reachable only by direct URL, excluded from listings), `public` (visible everywhere).
|
||||
- Logged-out visitors can view **public** route and activity detail pages. Private / nonexistent routes return 404, just like logged-in requests to other users' private content. Unlisted renders for anyone with the URL but does not appear in listings.
|
||||
- New public profile page at `/users/:username` that lists that user's **public** routes and activities. Returns 404 if the user has no public content (keeps the existence of private-only accounts itself private).
|
||||
- Route detail page gains a visibility selector for owners (the existing edit page is the natural home).
|
||||
- Open Graph / Twitter Card meta tags on public route and activity detail pages so shared URLs preview nicely (title, description, a small route preview image for future, the instance name as site).
|
||||
- **BREAKING for spec-readers only**: several existing requirements in `route-management` and `activity-feed` are modified to reflect the new "auth-or-public" access rule. The default in migration is `private`, so **behaviour for existing users does not change** — their routes stay inaccessible to logged-out visitors until they explicitly make one public.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `public-profiles`: Public-facing user pages at `/users/:username` listing a user's public routes and activities, with nothing shown for users who have no public content.
|
||||
|
||||
### Modified Capabilities
|
||||
- `route-management`: adds visibility field + modified access rules so public routes are viewable without auth; owners can change visibility.
|
||||
- `activity-feed`: adds visibility field; public activities render to logged-out visitors.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Code**: schema (`visibility` column on `routes` + `activities`), loader auth changes on `routes.$id.tsx` / `activities.$id.tsx`, new route `users.$username.tsx` (already exists but is auth-gated today), a visibility selector somewhere in the route edit flow, a `meta` export on public detail pages for OG tags.
|
||||
- **Data**: default `visibility = private` for all existing rows — three users, zero activities, two empty routes — so migration is effectively a no-op for behaviour. No backfill needed.
|
||||
- **Privacy**: this change explicitly reduces the default privacy posture *only if a user opts in by marking content public*. A short sentence in `/legal/privacy` notes public content is world-visible. No new third-party surface.
|
||||
- **Search engines**: public pages are not actively promoted (no sitemap). `robots: noindex` stays on legal pages and settings; `routes` and `activities` public pages get a permissive default (search-indexable) so demo URLs work well.
|
||||
- **Non-goals (explicit)**: follow/follower, a cross-user feed, reactions/comments, federation, route privacy per-section. Keeping scope tight.
|
||||
- **Rollback**: flipping the default back to `private` everywhere is a single UPDATE. Pages gating on visibility degrade to "auth-only" if we remove the public path.
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Activity detail page
|
||||
The Journal SHALL display an activity detail page with map, stats, and description. Access depends on the activity's `visibility`: `public` activities are viewable by anyone including unauthenticated visitors, `unlisted` activities are viewable by anyone who has the URL, and `private` activities are viewable only by the owner.
|
||||
|
||||
#### Scenario: Owner views own activity
|
||||
- **WHEN** a logged-in user navigates to an activity they own at any visibility
|
||||
- **THEN** they see the activity name, description, a map with the GPS trace, distance, duration, and elevation stats
|
||||
|
||||
#### Scenario: Anyone views a public activity
|
||||
- **WHEN** any visitor (including unauthenticated) navigates to a `public` activity's URL
|
||||
- **THEN** they see the full activity detail page as above
|
||||
|
||||
#### Scenario: Anyone with the URL views an unlisted activity
|
||||
- **WHEN** any visitor navigates directly to an `unlisted` activity's URL
|
||||
- **THEN** they see the full activity detail page as above
|
||||
|
||||
#### Scenario: Non-owner is blocked from a private activity
|
||||
- **WHEN** a visitor who is not the owner requests a `private` activity URL
|
||||
- **THEN** the server responds with HTTP 404 (not 403), so the existence of the private activity is not leaked
|
||||
|
||||
#### Scenario: Public and unlisted activity pages emit social-share metadata
|
||||
- **WHEN** a visitor loads a `public` or `unlisted` activity detail page
|
||||
- **THEN** the response emits Open Graph and Twitter Card meta tags (`og:title`, `og:description`, `og:type="article"`, `og:site_name`, `twitter:card="summary"`)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Activity visibility
|
||||
The Journal SHALL persist a `visibility` value on every activity and SHALL allow the owner to change it.
|
||||
|
||||
#### Scenario: New activities default to private
|
||||
- **WHEN** an activity is created without an explicit visibility
|
||||
- **THEN** the activity row is persisted with `visibility = 'private'`
|
||||
|
||||
#### Scenario: Owner changes an activity's visibility
|
||||
- **WHEN** an activity owner selects a different visibility (`private`, `unlisted`, `public`) and saves
|
||||
- **THEN** the stored visibility is updated and subsequent access checks use the new value immediately
|
||||
|
||||
### Requirement: Activity listings respect visibility
|
||||
The Journal's own-activities feed SHALL show the owner everything regardless of visibility, while any cross-user listing SHALL only include activities with `visibility = 'public'`.
|
||||
|
||||
#### Scenario: Own activity feed is unchanged
|
||||
- **WHEN** a logged-in user views their own activity feed
|
||||
- **THEN** the feed includes all of their own activities regardless of visibility
|
||||
|
||||
#### Scenario: Public profile lists only public activities
|
||||
- **WHEN** a visitor loads `/users/:username`
|
||||
- **THEN** the rendered list of activities includes only the user's `public` activities; `unlisted` and `private` activities are omitted
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Public profile page
|
||||
The Journal SHALL serve a public profile page at `/users/:username` that lists the user's public routes and activities in reverse chronological order, viewable without authentication.
|
||||
|
||||
#### Scenario: Logged-out visitor views a profile with public content
|
||||
- **WHEN** an unauthenticated visitor navigates to `/users/:username` for a user with at least one `public` route or activity
|
||||
- **THEN** the page renders that user's display name (falling back to username), the `@username@domain` handle, and a reverse-chronological list of their `public` routes and `public` activities
|
||||
- **AND** items marked `unlisted` or `private` do NOT appear in the list
|
||||
|
||||
#### Scenario: Profile 404 when there is no public content
|
||||
- **WHEN** a visitor navigates to `/users/:username` for a user whose content is all `private` or `unlisted`, or for a username that does not exist
|
||||
- **THEN** the server responds with HTTP 404
|
||||
- **AND** the response does NOT distinguish the two cases, so existence of a private-only account is not leaked
|
||||
|
||||
#### Scenario: Owner sees their own profile
|
||||
- **WHEN** a user navigates to their own `/users/:username` while logged in
|
||||
- **THEN** the page renders exactly the same as for a logged-out visitor, plus a small owner-only control strip linking to settings
|
||||
|
||||
#### Scenario: Profile page emits social-share metadata
|
||||
- **WHEN** any visitor loads a populated `/users/:username`
|
||||
- **THEN** the page emits Open Graph tags (`og:title`, `og:site_name`, `og:type="profile"`) so links shared on social platforms render a meaningful preview
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: View route
|
||||
The Journal SHALL display route details including map, metadata, and elevation stats. Access depends on the route's `visibility`: `public` routes are viewable by anyone including unauthenticated visitors, `unlisted` routes are viewable by anyone who has the URL, and `private` routes are viewable only by the owner.
|
||||
|
||||
#### Scenario: Owner views own route
|
||||
- **WHEN** a logged-in user navigates to a route they own at any visibility
|
||||
- **THEN** they see the route name, description, a map with the route polyline, distance, and elevation gain/loss
|
||||
|
||||
#### Scenario: Anyone views a public route
|
||||
- **WHEN** any visitor (including unauthenticated) navigates to a `public` route's URL
|
||||
- **THEN** they see the full route detail page as above
|
||||
|
||||
#### Scenario: Anyone with the URL views an unlisted route
|
||||
- **WHEN** any visitor navigates directly to an `unlisted` route's URL
|
||||
- **THEN** they see the full route detail page as above
|
||||
|
||||
#### Scenario: Non-owner is blocked from a private route
|
||||
- **WHEN** a visitor who is not the owner requests a `private` route URL
|
||||
- **THEN** the server responds with HTTP 404 (not 403), so the existence of the private route is not leaked
|
||||
|
||||
#### Scenario: Public and unlisted route pages emit social-share metadata
|
||||
- **WHEN** a visitor loads a `public` or `unlisted` route detail page
|
||||
- **THEN** the response emits Open Graph and Twitter Card meta tags (`og:title`, `og:description`, `og:type="article"`, `og:site_name`, `twitter:card="summary"`)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Route visibility
|
||||
The Journal SHALL persist a `visibility` value on every route and SHALL allow the owner to change it.
|
||||
|
||||
#### Scenario: New routes default to private
|
||||
- **WHEN** a route is created without an explicit visibility
|
||||
- **THEN** the route row is persisted with `visibility = 'private'`
|
||||
|
||||
#### Scenario: Owner changes a route's visibility
|
||||
- **WHEN** a route owner selects a different visibility (`private`, `unlisted`, `public`) in the edit flow and saves
|
||||
- **THEN** the stored visibility is updated and subsequent access checks use the new value immediately
|
||||
|
||||
#### Scenario: Non-owner cannot change visibility
|
||||
- **WHEN** a request to update visibility arrives from a user who is not the route owner
|
||||
- **THEN** the server rejects it with HTTP 403 or 404 (matching the current update-route behaviour), and the stored value is unchanged
|
||||
|
||||
### Requirement: Route listings respect visibility
|
||||
Any listing that exposes routes beyond the owner's own dashboard SHALL only include routes with `visibility = 'public'`.
|
||||
|
||||
#### Scenario: Public profile lists only public routes
|
||||
- **WHEN** a visitor loads `/users/:username`
|
||||
- **THEN** the rendered list of routes includes only the user's `public` routes; `unlisted` and `private` routes are omitted
|
||||
|
||||
#### Scenario: Owner's own routes list is unchanged
|
||||
- **WHEN** a logged-in user views their own routes list at `/routes`
|
||||
- **THEN** the list includes all of their own routes regardless of visibility
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
## 1. Schema
|
||||
|
||||
- [x] 1.1 Add `visibility text NOT NULL DEFAULT 'private'` to `journal.routes` in `packages/db/src/schema/journal.ts`
|
||||
- [x] 1.2 Add the same column to `journal.activities` in the same file
|
||||
- [x] 1.3 Export a shared type `type Visibility = 'private' | 'unlisted' | 'public'` from `packages/db/src/schema/journal.ts` (or a small adjacent module) and reuse at the app layer
|
||||
|
||||
## 2. Server-side access helper
|
||||
|
||||
- [x] 2.1 Add `canView(content, user)` in `apps/journal/app/lib/auth.server.ts` that returns `true` for `public`, `true` for `unlisted` (the direct-URL case — callers pass `true` when routed to a detail page, `false` when generating listings), and ownership-checked otherwise
|
||||
- [x] 2.2 Unit test the three matrix cells
|
||||
|
||||
## 3. Route detail access
|
||||
|
||||
- [x] 3.1 Update `apps/journal/app/routes/routes.$id.tsx` loader: fetch route, then apply `canView`; return 404 when not allowed
|
||||
- [x] 3.2 Expose `visibility` on the loader's returned shape so the component can render the current-visibility badge to the owner
|
||||
- [x] 3.3 Emit Open Graph / Twitter Card `meta` for `public` and `unlisted` routes (title, description, `og:type="article"`, `og:site_name="trails.cool"`, `twitter:card="summary"`)
|
||||
|
||||
## 4. Activity detail access
|
||||
|
||||
- [x] 4.1 Mirror 3.1 in `apps/journal/app/routes/activities.$id.tsx`
|
||||
- [x] 4.2 Mirror 3.3 for activities
|
||||
|
||||
## 5. Visibility selector in the edit flow
|
||||
|
||||
- [x] 5.1 Add a visibility `<select>` to `apps/journal/app/routes/routes.$id.edit.tsx`
|
||||
- [x] 5.2 Wire the form action in `routes.$id.tsx` / the edit route to persist the new value via `updateRoute`
|
||||
- [x] 5.3 Add i18n keys (EN + DE) for `routes.visibility.{label,private,unlisted,public,privateHelp,unlistedHelp,publicHelp}`
|
||||
- [x] 5.4 Surface the selector on the activity detail page for the owner (simpler than a new edit page); new `updateActivityVisibility` helper + `set-visibility` action intent
|
||||
|
||||
## 6. Listing filters
|
||||
|
||||
- [x] 6.1 Added `listPublicRoutesForOwner(ownerId)` — returns only public rows for profile listings. `listRoutes(ownerId)` stays as-is (owners see their own content regardless of visibility, per spec)
|
||||
- [x] 6.2 Added `listPublicActivitiesForOwner(ownerId)` with the same shape
|
||||
- [x] 6.3 Audited all callsites of `listRoutes` / `listActivities` / raw selects on `from(routes|activities)` — every path already passes the session user's own id, so no cross-user leaks. Cross-user surface is new (profile page) and uses the public-only helpers.
|
||||
|
||||
## 7. Public profile page
|
||||
|
||||
- [x] 7.1 The loader never required auth (it was already public-accessible); kept that way
|
||||
- [x] 7.2 Fetch user row by username; 404 if missing
|
||||
- [x] 7.3 Fetch public routes + public activities via the new `listPublicRoutesForOwner` / `listPublicActivitiesForOwner`
|
||||
- [x] 7.4 404 when user has no public content AND the viewer isn't the owner (owners still see their own empty profile)
|
||||
- [x] 7.5 Rendered profile header, reverse-chron lists, owner-only note linking to settings
|
||||
- [x] 7.6 Open Graph + Twitter Card meta (`og:title`, `og:description`, `og:type="profile"`, `og:site_name`)
|
||||
|
||||
## 8. Copy + docs
|
||||
|
||||
- [x] 8.1 Privacy Manifest: added a bullet in the Journal section explaining the visibility flag and that `public` content is world-visible
|
||||
- [x] 8.2 Bumped `PRIVACY_LAST_UPDATED` to 2026-04-20 and rendered `docs/legal-archive/privacy-2026-04-20.md`
|
||||
|
||||
## 9. Testing
|
||||
|
||||
- [x] 9.1 Unit: `canView` matrix (13 cases across private/unlisted/public × owner/other/anon × asDirectLink)
|
||||
- [x] 9.2 E2E: private route returns 404 to logged-out visitor; public route renders
|
||||
- [x] 9.3 E2E: owner can view their own private route; unlisted reachable on direct URL but omitted from profile
|
||||
- [x] 9.4 E2E: `/users/:username` 404s when no public content; renders when there's at least one public route
|
||||
- [x] 9.5 E2E: OG tags (`og:title`, `og:type=article`, `og:site_name`) present on public route pages
|
||||
|
||||
## 10. Rollout
|
||||
|
||||
- [x] 10.1 Schema ships with `drizzle-kit push --force` — column default `'private'`, no row changes on prod
|
||||
- [x] 10.2 (Post-deploy check) Confirm on prod that existing users' routes/activities remain auth-only — handled at merge time, not in this PR
|
||||
- Verified post-merge on trails.cool: `journal.routes` and `journal.activities` both have `column_default = 'private'::text`, NOT NULL. Current row counts — routes: 6 private / 15 public; activities: 11 private / 15 public — the publics on both sides are the demo-bot (bruno) seeded content per task 10.3. No existing user content leaked public.
|
||||
- [x] 10.3 (Next change) `demo-activity-bot` will insert with `visibility='public'` directly — not in this PR
|
||||
- Confirmed landed in the `demo-activity-bot` change: `apps/journal/app/lib/demo-bot.server.ts:564,585` set `visibility: "public"` on both route and activity inserts.
|
||||
Loading…
Add table
Add a link
Reference in a new issue