Instead of trying to use workflow runner tools (act/act_runner), the script
now directly runs the docker-compose command that CI uses. This is:
- More accurate (exact same command as CI)
- Simpler (no additional tools needed)
- Faster (no workflow interpretation overhead)
- Easier to debug (direct access to service logs)
The CI workflow literally runs `docker compose -f docker-compose.ci.yml`, so
running that locally is the most accurate way to test.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
gitea/act_runner is a runner daemon that needs to connect to a Gitea instance,
not a local testing tool. nektos/act is the correct tool for running workflows
locally, and it's compatible with both GitHub Actions and Gitea Actions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created scripts/test-ci-locally.sh to test Gitea Actions workflows locally using act_runner
- Created docker-compose.ci.yml for containerized CI test environment
- Updated .gitea/workflows/magnitude.yml to use docker-compose for CI
- Added scripts/README.md documenting the CI testing approach
- Created reusable test helpers in tests/playwright/
This allows developers to run the exact same workflow that CI runs, locally,
making it much easier to debug CI failures without push cycles.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed critical bug where nodes 4+ wouldn't get 3D coordinates because
UMAP manifold learning requires seeing the complete dataset together.
Root Cause:
- Previous code only calculated coords for nodes WHERE coords_3d = NONE
- When creating nodes 4-5, only those 2 nodes were passed to UMAP
- UMAP requires minimum 3 points to define a manifold
- Result: "Not enough nodes to map (2/3)" error
Why Full Recalculation is Necessary:
- UMAP is a non-linear manifold learning algorithm
- It creates relative coordinates, not absolute positions
- Each UMAP run produces different coordinate systems
- No "fixed origin" exists - positions are only meaningful relative to each other
- Adding new data changes the manifold structure
Changes:
- Updated /app/api/calculate-graph/route.ts:
* Removed "AND coords_3d = NONE" filter from query
* Now fetches ALL nodes with embeddings every time
* Recalculates entire graph when triggered
* Updated comments and logging to reflect full recalculation
- Created docs/umap-recalculation-strategy.md:
* Comprehensive explanation of UMAP manifold learning
* Why incremental calculation doesn't work
* Trade-offs of full recalculation approach
* Performance characteristics (<100 nodes: <1.5s)
* Future optimization strategies for scale
- Added scripts/recalculate-all-coords.ts:
* Emergency script to manually fix production database
* Successfully recalculated all 5 nodes in production
UX Impact:
The thought galaxy now "reorganizes" when adding new nodes - existing
nodes will shift slightly. This is actually a feature, showing the
evolving structure of your knowledge graph as it grows.
Performance:
Full recalculation is O(n²) but acceptable for <100 nodes:
- 3 nodes: ~50ms
- 10 nodes: ~200ms
- 50 nodes: ~800ms
- 100 nodes: ~1.5s
For Ponderants MVP, this is perfectly acceptable. Future optimizations
documented if we reach 1000+ nodes per user.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
Created a migration script that removes and redefines the coords_3d field
to make it optional (TYPE option<array<number>>) in production database.
This fixes the issue where coords_3d was required but should be NONE
until UMAP coordinates are calculated.
Migration successfully executed on production database.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed OAuth client configuration to properly use localhost for client_id and 127.0.0.1 for redirect_uris per RFC 8252 and ATproto spec
- Added proper grapheme counting using RichText API instead of character length
- Fixed thread splitting to account for link suffix and thread indicators in grapheme limits
- Added GOOGLE_EMBEDDING_DIMENSIONS env var to all env files
- Added clear-nodes.ts utility script for database management
- Added galaxy node detail page route
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Reverted logo SVG to original viewBox
- Applied forum.variable to body for CSS variable
- Updated Save button to generate draft from conversation
- Logo size and font variables still need fixes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create apply-schema-prod.js for deploying schema to any SurrealDB instance
- Create deploy-schema.sh to easily deploy using .prod.env
- Add npm scripts: schema:apply (local) and schema:deploy (production)
Usage:
pnpm schema:deploy # Deploy to production using .prod.env
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace manual OAuth implementation with official @atproto/oauth-client-node library to properly support DPoP (Demonstrating Proof of Possession) authentication.
Changes:
- Added @atproto/oauth-client-node dependency
- Created OAuth state store (SurrealDB-backed) for CSRF protection
- Created OAuth session store (SurrealDB-backed) for token persistence
- Created OAuth client singleton with localhost exception for development
- Rewrote /api/auth/login to use client.authorize()
- Rewrote /api/auth/callback to use client.callback() with DPoP
- Updated lib/auth/session.ts with getAuthenticatedAgent() for ATproto API calls
- Updated db/schema.surql with oauth_state and oauth_session tables
- Added scripts/apply-schema.js for database schema management
- Created plans/oauth-dpop-implementation.md with detailed implementation plan
- Removed legacy lib/auth/atproto.ts and lib/auth/oauth-state.ts
- Updated .env to use localhost exception (removed BLUESKY_CLIENT_ID)
The OAuth client now handles:
- PKCE code generation and verification
- DPoP proof generation and signing
- Automatic token refresh
- Session persistence across server restarts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>