commits
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure Vite SSR to externalize libsql native modules so they're
loaded from node_modules at runtime.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mark @libsql native modules as external so they're loaded from
node_modules at runtime instead of bundled.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use Proxy to defer database client creation until first access,
avoiding connection errors during SvelteKit's build-time analysis.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mount volume at /var/lib/litefs as recommended by Fly.io LiteFS docs.
The FUSE mount at /litefs is virtual and created by LiteFS.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove [processes] section that conflicted with LiteFS exec
- Use single volume at /data for LiteFS data storage
- LiteFS handles running the ingester via exec config
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ci.yaml: lint, type check, tests (runs on all branches/PRs)
- cd.yaml: deploy to Fly.io (runs only on main)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SvelteKit adapter-node needs HOST=0.0.0.0 to accept connections
from Fly's proxy.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fly.io only supports one volume per machine. Consolidate:
- LiteFS data at /data/litefs (persistent)
- Local DB at /data/local.db (persistent)
- FUSE mount at /litefs (virtual, created by LiteFS)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set CI=true for pnpm prune to skip confirmation prompt.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The .npmrc contains auto-install-peers=false which must match
what's recorded in the lockfile.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pin pnpm@10.7.1 via packageManager field to ensure Docker builds
use the same version as local development.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Secrets from repo settings are exposed as env vars directly.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add flyctl to CI dependencies
- Deploy webapp on push to main using FLY_API_KEY
- Deploy ingester on push to main using FLY_INGESTER_API_KEY
- Fix fly.toml mounts array syntax
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use SKIP_BROWSER_TESTS=1 to conditionally exclude the browser test
project in vite.config.ts, avoiding Playwright initialization entirely.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Nixpkgs chromium crashes in the nixery CI environment even with
--no-sandbox. Since there are only 2 browser tests vs 123 server
tests, skip browser tests in CI for now.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Chromium crashes in containerized environments without sandbox disabled.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
'which' isn't available in nixery, use bash built-in instead.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure vitest to use system chromium via PLAYWRIGHT_CHROMIUM_PATH
environment variable. This allows browser tests to run in CI using
nixpkgs chromium instead of Playwright's bundled browsers.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD and add explicit step to
install Chromium for Playwright browser tests.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document how to set up continuous deployment using flyctl from
nixpkgs in Tangled Spindle workflows. Includes example workflow
and required secrets.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add helper functions to properly type form action results for
pending posts/comments. This avoids Prettier stripping generic
type parameters when they appear in Svelte templates.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add eslint-disable comments for intentional @html usage (sanitized snippets)
- Add keys to each blocks in admin page
- Fix incomplete Omit<> type arguments in pending stores
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .claude/ to prettierignore
- Configure underscore-prefixed vars as intentionally unused
- Disable svelte/no-navigation-without-resolve (absolute paths work)
- Set @html and each-key rules to warn (intentional usage)
- Disable @typescript-eslint/no-explicit-any in test files
- Fix unused imports and variables
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .npmrc with auto-install-peers=false to prevent pnpm from
auto-installing better-sqlite3 as a peer dependency
- Update package.json pnpm config to ignore missing better-sqlite3 peer
- Add gnused to nixpkgs for lex-cli
- Create data directory before running migrations in CI
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Switch drizzle configs from sqlite to turso dialect
- Remove better-sqlite3 dependency (was only needed for sqlite dialect)
- Fixes CI: libsql doesn't require native compilation
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
corepack enable fails in nix environment due to symlink conflicts.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The 'when' block requires a list with 'event' arrays, not nested maps.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add admin moderation panel with hide/unhide for posts and comments
- Add user profile pages with posts/comments tabs
- Add report functionality for posts and comments
- Extract reusable components: Tabs, PostTitle, Modal
- Add alpha badge with modal explaining breaking changes
- Set up Tangled Spindle CI pipeline (lint, typecheck, tests)
- Switch from db:push to proper Drizzle migrations
- Extract shared search queries with FTS snippet config
- Add tests for ingester handler, profiles, ranking, auth flows
- Update README and CLAUDE.md documentation
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The docs folder contains internal implementation plans and is now
gitignored. Files remain on disk for local reference.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add instrumentation.server.ts for SvelteKit auto-instrumentation
- Add ingester/instrumentation.ts with Jetstream-specific metrics
- Instrument Drizzle/libsql database queries via @kubiks/otel-drizzle
- Add cursor.ts for cleaner cursor management in ingester
- Configure SvelteKit experimental tracing and instrumentation
- Export traces and metrics to Honeycomb via HTTP/protobuf
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Security fixes:
- Add domain format validation and LIKE escaping in /from/[domain]
- Add AT URI format validation in vote API (format, length, collection match)
- Add in-memory rate limiting for API endpoints (60/min vote, 30/min search)
Code deduplication:
- Extract post queries to src/lib/server/queries/posts.ts
- Extract enrichment logic to src/lib/server/enrichment.ts
- Extract FTS5 sanitization to src/lib/server/search/sanitize.ts
Files reduced:
- src/routes/+page.server.ts: 69 -> 23 lines
- src/routes/new/+page.server.ts: 59 -> 20 lines
- src/routes/from/[domain]/+page.server.ts: 65 -> 38 lines
New test coverage: 64 tests passing
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Improve FTS5 query sanitization to strip boolean operators (OR, AND,
NOT, NEAR) and special characters to prevent injection
- Add URL protocol whitelist to reject javascript: and data: URLs
- Convert service worker from TypeScript to JavaScript for dev compatibility
- Fix apple-touch-icon path in app.html
New test coverage:
- 21 FTS5 sanitization tests
- 11 vote API validation tests
- 3 security tests for URL protocols and XSS payloads
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Service worker with network-first caching for pages
- Cache-first for static assets, auto cache versioning
- SVG icons for PWA manifest (regular + maskable)
- ShareButton component using Web Share API with clipboard fallback
- Pagination for home and /new pages (30 posts per page)
- Skeleton loading components for posts and comments
- Updated tests for new pagination data shape
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- FTS5 virtual tables for posts (title, text, url) and comments (text)
- Auto-sync triggers for insert/update/delete operations
- Search API endpoint at /api/search with type filtering
- Search results page with tabbed posts/comments view
- SearchBox component in header (desktop) and mobile menu
- Highlighted snippets using FTS5 snippet() function
- Prefix matching for partial word searches
- Setup script: pnpm db:fts
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Optimistic UI for posts and comments with pending stores
- Poll for confirmation every 2s while items are pending
- Filter duplicates when pending items appear in real data
- Hot score ranking for posts (homepage) and comments (post detail)
- Extract ranking algorithm to shared $lib/utils/ranking.ts
- Split database architecture: contentDb (LiteFS) + localDb (auth/votes)
- Separate drizzle configs for content and local databases
- Add dev:ingester script for local development
- Update CLAUDE.md and implementation plan with local dev instructions
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create $lib/types.ts with AuthorProfile type (uses Pick from lexicon types)
- Create $lib/server/profiles.ts with fetchProfile, fetchProfiles, getProfileOrFallback
- Create $lib/utils/formatting.ts with formatTimeAgo (compact option), getDomain
- Update all route files to use shared profile helpers
- Update Svelte components to use shared formatting utilities
This eliminates duplicate code across:
- 4 files with profile fetching logic
- 4 files with AuthorProfile/UserProfile interfaces
- 2 files with formatTimeAgo implementations
- 2 files with getDomain implementations
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add votes table to schema with user/target tracking
- Add voteCount column to posts and comments tables
- Create /api/vote endpoint with optimistic write pattern
- Build VoteButton component with optimistic UI updates
- Add PostList component for reusable post listings
- Integrate voting into home, /new, and post detail pages
- Remove dark mode toggle (now follows system preference)
- Reduce mobile horizontal padding for better space usage
- Fix dark mode CSS to use prefers-color-scheme media queries
Voting is upvote-only to keep things simple - no karma system,
content naturally ages out. Users can toggle their vote on/off.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Monospace 'p' in a rounded square with violet gradient.
Clean, modern look distinct from other AT Protocol apps.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The name "papili" references Papilioninae butterflies, so use a
violet/purple color scheme that evokes butterfly wings.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace blue accent with amber color scheme (HN-inspired)
- Simplify header to minimal text-based navigation
- Make post list more compact and text-focused
- Remove avatars from homepage, just show handles
- Add "new" and "discuss" links for future features
- Add ATProto footer credit
- Update login and submit pages to match new style
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fly.io only supports one volume per machine. Consolidate:
- LiteFS data at /data/litefs (persistent)
- Local DB at /data/local.db (persistent)
- FUSE mount at /litefs (virtual, created by LiteFS)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure vitest to use system chromium via PLAYWRIGHT_CHROMIUM_PATH
environment variable. This allows browser tests to run in CI using
nixpkgs chromium instead of Playwright's bundled browsers.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .claude/ to prettierignore
- Configure underscore-prefixed vars as intentionally unused
- Disable svelte/no-navigation-without-resolve (absolute paths work)
- Set @html and each-key rules to warn (intentional usage)
- Disable @typescript-eslint/no-explicit-any in test files
- Fix unused imports and variables
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .npmrc with auto-install-peers=false to prevent pnpm from
auto-installing better-sqlite3 as a peer dependency
- Update package.json pnpm config to ignore missing better-sqlite3 peer
- Add gnused to nixpkgs for lex-cli
- Create data directory before running migrations in CI
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add admin moderation panel with hide/unhide for posts and comments
- Add user profile pages with posts/comments tabs
- Add report functionality for posts and comments
- Extract reusable components: Tabs, PostTitle, Modal
- Add alpha badge with modal explaining breaking changes
- Set up Tangled Spindle CI pipeline (lint, typecheck, tests)
- Switch from db:push to proper Drizzle migrations
- Extract shared search queries with FTS snippet config
- Add tests for ingester handler, profiles, ranking, auth flows
- Update README and CLAUDE.md documentation
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add instrumentation.server.ts for SvelteKit auto-instrumentation
- Add ingester/instrumentation.ts with Jetstream-specific metrics
- Instrument Drizzle/libsql database queries via @kubiks/otel-drizzle
- Add cursor.ts for cleaner cursor management in ingester
- Configure SvelteKit experimental tracing and instrumentation
- Export traces and metrics to Honeycomb via HTTP/protobuf
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Security fixes:
- Add domain format validation and LIKE escaping in /from/[domain]
- Add AT URI format validation in vote API (format, length, collection match)
- Add in-memory rate limiting for API endpoints (60/min vote, 30/min search)
Code deduplication:
- Extract post queries to src/lib/server/queries/posts.ts
- Extract enrichment logic to src/lib/server/enrichment.ts
- Extract FTS5 sanitization to src/lib/server/search/sanitize.ts
Files reduced:
- src/routes/+page.server.ts: 69 -> 23 lines
- src/routes/new/+page.server.ts: 59 -> 20 lines
- src/routes/from/[domain]/+page.server.ts: 65 -> 38 lines
New test coverage: 64 tests passing
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Improve FTS5 query sanitization to strip boolean operators (OR, AND,
NOT, NEAR) and special characters to prevent injection
- Add URL protocol whitelist to reject javascript: and data: URLs
- Convert service worker from TypeScript to JavaScript for dev compatibility
- Fix apple-touch-icon path in app.html
New test coverage:
- 21 FTS5 sanitization tests
- 11 vote API validation tests
- 3 security tests for URL protocols and XSS payloads
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Service worker with network-first caching for pages
- Cache-first for static assets, auto cache versioning
- SVG icons for PWA manifest (regular + maskable)
- ShareButton component using Web Share API with clipboard fallback
- Pagination for home and /new pages (30 posts per page)
- Skeleton loading components for posts and comments
- Updated tests for new pagination data shape
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- FTS5 virtual tables for posts (title, text, url) and comments (text)
- Auto-sync triggers for insert/update/delete operations
- Search API endpoint at /api/search with type filtering
- Search results page with tabbed posts/comments view
- SearchBox component in header (desktop) and mobile menu
- Highlighted snippets using FTS5 snippet() function
- Prefix matching for partial word searches
- Setup script: pnpm db:fts
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Optimistic UI for posts and comments with pending stores
- Poll for confirmation every 2s while items are pending
- Filter duplicates when pending items appear in real data
- Hot score ranking for posts (homepage) and comments (post detail)
- Extract ranking algorithm to shared $lib/utils/ranking.ts
- Split database architecture: contentDb (LiteFS) + localDb (auth/votes)
- Separate drizzle configs for content and local databases
- Add dev:ingester script for local development
- Update CLAUDE.md and implementation plan with local dev instructions
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create $lib/types.ts with AuthorProfile type (uses Pick from lexicon types)
- Create $lib/server/profiles.ts with fetchProfile, fetchProfiles, getProfileOrFallback
- Create $lib/utils/formatting.ts with formatTimeAgo (compact option), getDomain
- Update all route files to use shared profile helpers
- Update Svelte components to use shared formatting utilities
This eliminates duplicate code across:
- 4 files with profile fetching logic
- 4 files with AuthorProfile/UserProfile interfaces
- 2 files with formatTimeAgo implementations
- 2 files with getDomain implementations
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add votes table to schema with user/target tracking
- Add voteCount column to posts and comments tables
- Create /api/vote endpoint with optimistic write pattern
- Build VoteButton component with optimistic UI updates
- Add PostList component for reusable post listings
- Integrate voting into home, /new, and post detail pages
- Remove dark mode toggle (now follows system preference)
- Reduce mobile horizontal padding for better space usage
- Fix dark mode CSS to use prefers-color-scheme media queries
Voting is upvote-only to keep things simple - no karma system,
content naturally ages out. Users can toggle their vote on/off.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace blue accent with amber color scheme (HN-inspired)
- Simplify header to minimal text-based navigation
- Make post list more compact and text-focused
- Remove avatars from homepage, just show handles
- Add "new" and "discuss" links for future features
- Add ATProto footer credit
- Update login and submit pages to match new style
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>