Files
app/plans/10-public-galaxy-viewing.md
Albert d656b06113 feat: Make galaxy viewable without login requirement
Implemented public galaxy viewing feature that allows unauthenticated
users to view public thought galaxies via the ?user={did} parameter,
while maintaining privacy controls for node-level visibility.

Changes:
- Updated /api/galaxy/route.ts to support public access:
  * Accept ?user={did} query parameter for viewing specific user's galaxy
  * Show all nodes (including private) for authenticated user viewing own galaxy
  * Filter to only public nodes when viewing someone else's galaxy
  * Return empty state with helpful message when not authenticated
  * Filter links to only show connections between visible nodes

- Added is_public field to database schema:
  * Updated db/schema.surql with DEFAULT true (public by default)
  * Created migration script scripts/add-is-public-field.ts
  * Aligns with ATproto's public-by-default philosophy

- Enhanced ThoughtGalaxy component:
  * Support viewing galaxies via ?user={did} parameter
  * Display user info banner when viewing public galaxy
  * Show appropriate empty state messages based on context
  * Refetch data when user parameter changes

- Created comprehensive Magnitude tests:
  * Test public galaxy viewing without authentication
  * Verify private nodes are hidden from public view
  * Test own galaxy access requires authentication
  * Validate invalid user DID handling
  * Test user info display and navigation between galaxies

- Documented implementation plan in plans/10-public-galaxy-viewing.md

This implements the "public by default" model while allowing future
node-level privacy controls. All canonical data remains on the user's
ATproto PDS, with SurrealDB serving as a high-performance cache.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 00:36:16 +00:00

8.9 KiB

Plan: Make Galaxy Viewable Without Login Requirement

Status

  • Priority: HIGH (User-requested)
  • Status: In Progress
  • Created: 2025-01-10

Problem Statement

Currently, the galaxy visualization (/galaxy) requires user authentication via JWT cookie. This prevents:

  1. Public sharing of thought galaxies
  2. First-time visitors from seeing example galaxies
  3. Social media link previews from working properly
  4. Search engines from indexing public thought networks

The galaxy should be publicly viewable while still respecting user privacy preferences.

Current Implementation Analysis

Authentication Check

app/api/galaxy/route.ts (lines 27-38):

const surrealJwt = cookieStore.get('ponderants-auth')?.value;

if (!surrealJwt) {
  return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}

const userSession = verifySurrealJwt(surrealJwt);
if (!userSession) {
  return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 });
}

Data Query

Currently queries WHERE user_did = $userDid (line 49), showing only the authenticated user's nodes.

Design Decisions

All galaxies are publicly viewable, but users can mark individual nodes as private.

Pros:

  • Simple implementation
  • Encourages public knowledge sharing
  • Better for SEO and discovery
  • Aligns with "decentralized social" vision

Cons:

  • Users might accidentally share private thoughts
  • Requires clear UI indicators for node visibility

URL Structure:

  • /galaxy - current user's galaxy (if logged in) or landing page
  • /galaxy/{user_did} or /galaxy?user={user_did} - specific user's public galaxy

Option 2: Opt-in Public Galleries

Galaxies are private by default, users must explicitly make them public.

Pros:

  • More privacy-conscious
  • Users have full control

Cons:

  • Reduces discovery and sharing
  • More complex implementation
  • Goes against ATproto's "public by default" philosophy

Decision: We'll implement Option 1 - Public by default, with optional private nodes.

Implementation Plan

Phase 1: Update API to Support Public Access

1.1 Modify /api/galaxy/route.ts

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const targetUserDid = searchParams.get('user');

  const cookieStore = await cookies();
  const surrealJwt = cookieStore.get('ponderants-auth')?.value;

  // Determine which user's galaxy to show
  let userDid: string;
  let isOwnGalaxy = false;

  if (targetUserDid) {
    // Viewing someone else's public galaxy
    userDid = targetUserDid;
  } else if (surrealJwt) {
    // Viewing own galaxy (authenticated)
    const userSession = verifySurrealJwt(surrealJwt);
    if (!userSession) {
      return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 });
    }
    userDid = userSession.did;
    isOwnGalaxy = true;
  } else {
    // No target user and not authenticated - return empty galaxy with message
    return NextResponse.json({
      nodes: [],
      links: [],
      message: 'Log in to view your galaxy, or visit a public galaxy via ?user={did}'
    });
  }

  // Query nodes
  const nodesQuery = `
    SELECT id, title, body, user_did, atp_uri, coords_3d
    FROM node
    WHERE user_did = $userDid
    AND coords_3d != NONE
    ${isOwnGalaxy ? '' : 'AND is_public = true'}
  `;

  // ... rest of implementation
}

1.2 Add is_public Field to Node Schema

  • Default: true (public by default)
  • Users can mark individual nodes as private
  • Private nodes are only visible to the owner

Phase 2: Update Database Schema

2.1 Add is_public Column to node Table

DEFINE FIELD is_public ON TABLE node TYPE bool DEFAULT true;

2.2 Create Migration Script

scripts/add-is-public-field.ts:

import { connectToDB } from '@/lib/db';

async function migrate() {
  const db = await connectToDB();

  // Add field definition
  await db.query(`
    DEFINE FIELD is_public ON TABLE node TYPE bool DEFAULT true;
  `);

  // Set existing nodes to public
  await db.query(`
    UPDATE node SET is_public = true WHERE is_public = NONE;
  `);

  console.log('Migration complete: Added is_public field');
}

migrate();

Phase 3: Update Frontend Components

3.1 Update ThoughtGalaxy.tsx

// Support viewing other users' galaxies
const { searchParams } = useSearchParams();
const targetUser = searchParams.get('user');

useEffect(() => {
  async function fetchData() {
    const url = targetUser
      ? `/api/galaxy?user=${targetUser}`
      : '/api/galaxy';

    const response = await fetch(url, {
      credentials: 'include',
    });

    // ... rest of implementation
  }

  fetchData();
}, [targetUser]);

3.2 Add User Info Display

When viewing another user's galaxy, show:

  • User's Bluesky handle
  • Link to their profile
  • Number of public nodes

3.3 Update /galaxy Page

Add support for URL parameter: /galaxy?user=did:plc:xxxxx

Phase 4: Navigation & User Experience

4.1 Landing Experience for Non-Authenticated Users

When visiting /galaxy without login:

  • Show a sample/demo galaxy (could be a curated example)
  • Display call-to-action: "Create your own thought galaxy"
  • Provide login button

4.2 Add "Share Galaxy" Feature

Add button to copy shareable link:

const shareUrl = `${window.location.origin}/galaxy?user=${userDid}`;

Phase 5: Privacy Controls (Future Enhancement)

5.1 Node-Level Privacy Toggle

In node editor, add checkbox:

<Checkbox
  label="Make this node public"
  checked={isPublic}
  onChange={(e) => setIsPublic(e.currentTarget.checked)}
/>

5.2 Bulk Privacy Management

Settings page to:

  • Make all nodes private/public
  • Set default for new nodes
  • Filter and update specific nodes

Security Considerations

1. Data Exposure

  • Risk: Users accidentally share sensitive information
  • Mitigation:
    • Clear visual indicators for public/private nodes
    • Confirmation dialog when publishing nodes
    • Easy way to make nodes private retroactively

2. API Abuse

  • Risk: Scraping or excessive requests to public galaxies
  • Mitigation:
    • Rate limiting on /api/galaxy
    • Caching layer for public galaxies
    • Consider CDN for popular galaxies

3. Privacy Violations

  • Risk: Viewing history tracking or surveillance
  • Mitigation:
    • No analytics on public galaxy views
    • No "who viewed my galaxy" feature
    • Respect DNT headers

Testing Plan

Magnitude Tests

Test 1: Public Galaxy Viewing Without Auth

test('Unauthenticated users can view public galaxies', async (agent) => {
  await agent.act('Navigate to /galaxy?user=did:plc:example123');
  await agent.check('The galaxy visualization is displayed');
  await agent.check('Public nodes are visible');
  await agent.act('Click on a public node');
  await agent.check('Node details are displayed');
});

Test 2: Private Nodes Hidden from Public View

test('Private nodes are not visible in public galaxy', async (agent) => {
  // ... implementation
});

Test 3: Own Galaxy Requires Auth

test('Accessing own galaxy without target user requires authentication', async (agent) => {
  await agent.act('Navigate to /galaxy');
  await agent.check('Login prompt or empty state is displayed');
});

Manual Testing Checklist

  • Visit /galaxy without login → see landing page
  • Visit /galaxy?user={valid_did} → see public nodes
  • Visit /galaxy?user={invalid_did} → see error message
  • Log in and visit /galaxy → see own galaxy (including private nodes)
  • Share galaxy link → recipient can view public nodes
  • Mark node as private → confirm it disappears from public view

Implementation Steps

  1. Create database migration for is_public field
  2. Update API route to support public access
  3. Update ThoughtGalaxy component to handle URL parameters
  4. Add user info display for public galaxies
  5. Test with manual checks
  6. Write Magnitude tests
  7. Update documentation
  8. Create PR with changes

Acceptance Criteria

Unauthenticated users can view public galaxies via ?user= parameter Authenticated users see their own galaxy at /galaxy (no param) Private nodes are only visible to the owner Public nodes are visible to everyone Clear error messages for invalid user DIDs Shareable URLs work correctly All tests pass

Notes

  • This aligns with ATproto's philosophy of public-by-default, user-controlled data
  • Future enhancement: Node-level privacy controls in UI
  • Consider adding Open Graph meta tags for social media previews
  • May want to add a "featured galaxies" page for discovery
  • app/api/galaxy/route.ts - Galaxy API endpoint
  • components/ThoughtGalaxy.tsx - 3D visualization component
  • app/galaxy/page.tsx - Galaxy page component
  • lib/db/schema.surql - Database schema