commits
The "migrate accounts to this PDS" message made no sense when viewing
your own self-hosted PDS data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The self-hosting guide doesn't explain what a PDS is - Dan's "A Social
Filesystem" article has a section that actually answers the question.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Don't overwrite user's explicit filter state from URL params when
applying the default "hide invalid apps" filter after validation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- fix icon movement when selected by using absolute positioning for labels
- add pulsing animation on selected state to invite second-click interaction
- remove parentheses from "what is a PDS?" link for cleaner appearance
- add text cursor on record content to indicate text is selectable
- prevent collection content clicks from closing the expanded section
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ATProto API returns a cursor even when fewer records than the limit are
returned. Now we only show "load more" when records.length equals the
page size (10), indicating there may be more records to fetch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Project migrated to pure JS/Vite in 6a5c766. These docs are no longer relevant.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This reverts commit 6c6e10799ff2ede2ce497e127d255773270bf9f2.
Previous Claude session created docs referencing a Rust backend
that doesn't exist. This is a pure JavaScript/Vite project.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Calculate max radius based on actual reserved UI areas:
- Header: 80px for POV indicator and filter buttons
- Footer: 120px for guestbook controls
- Sides: 60px for app labels
This properly fills available space while respecting UI elements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed minRadiusForSpacing calculation that could push apps off screen
when there are many apps. Now caps radius to viewport bounds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Set up identity click handler and filter panel immediately
- Run URL validation in background without blocking UI
- After validation completes, automatically hide invalid apps
- Users can now interact with PDS panel while domains resolve
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Not all non-Bluesky PDSes are self-hosted - some are third-party
providers like selfhosted.social. Use generic "independent PDS"
terminology instead of assuming self-hosting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove version fetching from health endpoint (not useful info)
- Add pdsmoover.com link for both hosting types:
- Self-hosted: link to their PDS on pdsmoover for account migrations
- Bluesky-hosted: link to pdsmoover to migrate to self-hosted
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restore missing keyframes:
- neon-flicker animation for guestbook sign text
- pov-subtle-flicker animation for POV indicator
- Both with dark mode variants for enhanced glow effects
Add PDS hosting status card to identity sidebar:
- Detects Bluesky-hosted vs self-hosted PDSes
- Bluesky-hosted: shows encouragement to self-host with link to guide
- Self-hosted: acknowledges setup and fetches PDS version via /xrpc/_health
- Color-coded cards (blue for Bluesky, purple for self-hosted)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The povHandle element was never being set after the client-side
migration. Now displays the viewed user's handle with a link to
their Bluesky profile.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make renderVisualization async and await validateAppUrls to ensure
validation completes before filter panel initializes
- Rewrite validateAppUrls with two-phase approach:
1. Check if domain resolves as ATProto handle (fast, no CORS issues)
2. Fall back to HEAD request for non-ATProto domains
- Mark domains as invalid on any fetch error (not just Chrome-specific
error strings) for cross-browser compatibility
🤖 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>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add @atcute/oauth-browser-client integration for ATProto OAuth
- Create oauth.js module with login, callback handling, session management
- Update guestbook-ui.js with login/sign modals and proper auth flow
- Add CSS styling for login and sign forms
- Update oauth-client-metadata.json for wisp.place domain
- Configure Vite to inject OAuth environment variables for dev/prod
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use no-cors HEAD requests to check if app domains are actually
reachable. Only mark as invalid for DNS/connection failures
(ERR_NAME_NOT_RESOLVED, ERR_CONNECTION_REFUSED, timeout).
CORS blocks indicate the server exists, so don't mark invalid.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wisp appends query strings to redirect targets, breaking file lookup.
Rely on view/index.html directory indexing instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wisp's cleanUrls and _redirects have issues with query params.
Using a directory with index.html 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>
🤖 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>
Prepare for wisp.place subdomain hosting where cleanUrls works
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
wisp.place DID-path hosting doesn't support _redirects or cleanUrls
🤖 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>
- Remove Rust/Axum backend (Cargo.toml, src/*.rs, Dockerfile, fly.toml)
- Add Vite build system with MPA configuration
- Split monolithic HTML into modular ES modules:
- src/landing/: Landing page with atmosphere visualization
- src/view/: Main app with ATProto visualization, filters, MST viewer
- Add tangled CI for wisp.place deployment
- Fix avatar centering and app circle spacing
- Simplify domain validation (trust reversed ATProto namespaces)
🤖 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>
search handles as you type using the Bluesky search API, showing
avatars, display names, and handles in a dropdown. selecting a
result navigates directly to that profile.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add hide=... URL param to share links with filters applied
- URL params take precedence over localStorage
- wrap filter + watch live buttons in container that stacks vertically on mobile (<768px)
- adjust filter panel and firehose toast positions for mobile
- filter button with panel to toggle app visibility
- "all" shows all apps, "valid" hides unresolvable NSIDs, "none" hides all
- defaults to "valid" mode on first load
- persists filter preferences in localStorage per user
- visible apps reposition evenly around the circle when filtering
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
previously, any delete event (like unliking a post) would immediately
remove the app circle from the UI. now we lazily check the PDS to see
if the namespace still has records before removing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
clippy compiles the code to analyze it, so it needs gcc for linking
and openssl.dev + pkg-config for dependencies. use shell glob pattern
instead of find command to set PKG_CONFIG_PATH.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
cargo fmt and clippy don't compile dependencies, so they don't need
openssl, pkg-config, or gcc. the PKG_CONFIG_PATH setup wasn't working
anyway (find command not available in nixery containers).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
clippy correctly identified that calling .last() on split('/') needlessly
iterates the entire iterator. since split() returns a DoubleEndedIterator,
we can use .next_back() to get the last element directly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
nixery containers don't automatically set PKG_CONFIG_PATH like nix-shell does.
dynamically find the openssl.dev path in /nix/store and set PKG_CONFIG_PATH
to point to its pkgconfig directory before running cargo commands.
tested locally in nixos/nix docker container with the same dependencies.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
the openssl.dev output provides development headers and pkg-config files
needed by cargo to build openssl-sys crate. the base openssl package only
contains runtime libraries.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
users can now leave an optional message when signing the guestbook. messages are displayed in handwritten font alongside the signature metadata.
- backend: added text field to visit records and signature struct
- frontend: added message input modal with 280 char limit
- docs: updated lexicon documentation with new field and credits to @thisismissem.social and @essentialrandom.bsky.social
inspired by https://github.com/FujoWebDev/lexicon-guestbook
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add pre-commit configuration with cargo fmt and clippy checks
- Remove obsolete .tangled/workflows/deploy.yaml
- Fix all clippy warnings:
- Replace redundant closures with function references
- Use .first() instead of .get(0)
- Use .or_default() instead of .or_insert_with(Vec::new)
- Use .unsigned_abs() instead of .abs() as u32
- Use .div_ceil() instead of manual ceiling division
- Simplify iterator patterns with .flatten()
- Remove needless return statements
- Replace useless format!() with .to_string()
- Use array literals instead of vec![] where appropriate
- Remove redundant field names in struct initialization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace in-memory cache with UFOs API for persistent global state
- Split architecture: query page owner's PDS for button state, use UFOs for global list
- Fix unauthenticated user flow to trigger identity confirmation on button click
- Add signature count display to guestbook modal
- Fix font consistency across all guestbook text (unified monospace)
- Implement optimistic cache updates for sign/unsign actions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
cleaned up all debug logging from app.js while preserving essential error logging with console.error. removed verbose firehose event logs, guestbook state logs, and app circle management logs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update POV indicator: change "point of view:" to "point of view of" and make handle a clickable Bluesky profile link
- Fix guestbook button avatar display: always show page owner's avatar, not authenticated user's avatar
- Add dynamic guestbook sign text: shows "you already signed" when page owner has signed, "sign the guest list" otherwise
- Update authentication success toast to mention both sign and unsign actions
- Add CSS styling for clickable POV handle with hover effects
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- populate global signatures cache from existing visit records on page load
- make guestbook truly global - shows all visitors regardless of page
- check page owner signature status without authentication
- refine particle animations: slower speed, gentler easing, fade in/out
- make PDS pulse more subtle with proper centering transform
- fix pdsls.dev links to include at:// prefix and /app.at-me.visit path
- update justfile to watch static directory for template changes
- remove tangled.org logo from info modal
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- add drop shadow to guestbook avatar button
- show authenticated user's avatar in sign button (not page owner's)
- add confirmation modal for unauthenticated users: "are you @handle?"
- implement global guestbook signatures cache (all users, not per-DID)
- invalidate cache on sign/delete operations
- return handle and avatar in /api/auth/status
- add "point of view: @handle" indicator on left side with neon styling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
adds full-screen modal to view all guestbook signatures with avatars and timestamps. fixes critical firehose bug where guestbook collection wasn't being watched if it didn't exist yet.
backend changes:
- always include app.at-me.visit in watched collections (routes.rs:1184-1187)
- add /api/guestbook/signatures endpoint with caching (routes.rs:981-1096)
- cache invalidation on sign/unsign for real-time updates
frontend changes:
- add view guestbook button (👥 icon) positioned left of sign button
- full-screen modal with loading, empty, and error states
- fetch and display signatures sorted by most recent first
- modal shows handle, avatar, and formatted timestamp for each signer
fixes:
- guestbook collection now tracked even before first signature
- real-time particle animations work on first sign/unsign
- dynamic circle appearance/removal works correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
firehose now registers ALL collections from user's PDS instead of hardcoded list:
- firehose endpoint fetches collections from PDS via describeRepo
- passes collections to get_or_create_broadcaster()
- registers ingesters for every collection the user has
- removes hardcoded BSKY_COLLECTIONS constant (dead code)
before: only listened to hardcoded bluesky collections
after: listens to all collections (bluesky, whitewind, tangled, guestbook, etc.)
this fixes the original bug where non-bluesky apps wouldn't show real-time updates. now any app writing to your PDS will be visualized when watch live is enabled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
app circle lifecycle:
- automatically remove app circle when delete particle animation completes
- added removeAppCircle() function to clean up DOM and globalApps state
- repositions remaining circles smoothly after removal
fixed dynamically added circles:
- app circles created via firehose now properly fetch and display records
- clicking circle loads record count from PDS
- expanding records shows full data with copy functionality
- fixes "loading..." state that never resolved
improved toast notifications:
- hide "view record" link for delete events (no record to view)
- collection names now use inline code formatting for better readability
- code style: monospace background, subtle padding, reduced font size
- changed to innerHTML to support formatted content
this completes the guestbook circle lifecycle: create → visualize → interact → delete → remove
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
the firehose toast notification now behaves consistently on both desktop and mobile:
- uses max-width: min(300px, calc(100vw - 2rem)) to respect both size limit and viewport
- width: max-content keeps toast compact, only as wide as needed
- removed mobile-specific left/right/max-width overrides that caused full-width stretch
- toast remains right-aligned and compact on all screen sizes
before: mobile toast spanned entire screen width, looked oversized
after: mobile toast is same compact size as desktop, just positioned lower to avoid button overlap
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
users can now "sign" the guestbook by writing app.at-me.visit records to their own PDS via OAuth authentication. changes are visualized in real-time through firehose integration with particle animations.
authentication & authorization:
- OAuth flow using atrium-oauth with proper scopes (repo:app.at-me.visit)
- session management via actix-session to track auth state
- redirect flow that returns users to page after authentication
- agent caching by DID to avoid re-authentication
api endpoints:
- POST /api/sign-guestbook - creates visit record in user's PDS
- DELETE /api/sign-guestbook - removes visit record (unsign)
- GET /api/auth/status - checks authentication and existing records
- /login and /callback - OAuth authentication flow
real-time visualization:
- firehose integration watches for new visit records
- particle animation flows from app circle to PDS on record creation
- dynamic app circle creation when new apps write to your PDS
- toast notifications showing firehose events
- proper namespace handling (app.at-me → display as at-me.app)
critical bug fix:
- firehose was only listening for hardcoded Bluesky collections (app.bsky.*)
- added app.at-me.visit to registered collections in src/firehose.rs
- this was preventing any non-Bluesky app events from appearing
- remaining: should register ALL collections dynamically, not hardcoded list
utilities:
- scripts/delete_visits.py - delete all visit records from your PDS
- just clean-up-my-visits - quick command to run cleanup script
- uses uv for python dependencies with inline script metadata
frontend improvements:
- guestbook button with 3 states (not authenticated, ready, signed)
- watch prompt modal encouraging users to enable live mode
- extensive debug logging for troubleshooting (to be removed)
- dynamic app circle creation in real-time
technical details:
- extracted constants to src/constants.rs (collections, buffer sizes)
- updated dependencies: actix-session, atrium-oauth
- DID-filtered jetstream connections per user
- proper error handling for OAuth and API calls
known issues to address:
- app.js is 1800+ lines, needs refactoring into modules
- remove debug console.logs before production
- better loading states during OAuth flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- fix icon movement when selected by using absolute positioning for labels
- add pulsing animation on selected state to invite second-click interaction
- remove parentheses from "what is a PDS?" link for cleaner appearance
- add text cursor on record content to indicate text is selectable
- prevent collection content clicks from closing the expanded section
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ATProto API returns a cursor even when fewer records than the limit are
returned. Now we only show "load more" when records.length equals the
page size (10), indicating there may be more records to fetch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Calculate max radius based on actual reserved UI areas:
- Header: 80px for POV indicator and filter buttons
- Footer: 120px for guestbook controls
- Sides: 60px for app labels
This properly fills available space while respecting UI elements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Set up identity click handler and filter panel immediately
- Run URL validation in background without blocking UI
- After validation completes, automatically hide invalid apps
- Users can now interact with PDS panel while domains resolve
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove version fetching from health endpoint (not useful info)
- Add pdsmoover.com link for both hosting types:
- Self-hosted: link to their PDS on pdsmoover for account migrations
- Bluesky-hosted: link to pdsmoover to migrate to self-hosted
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restore missing keyframes:
- neon-flicker animation for guestbook sign text
- pov-subtle-flicker animation for POV indicator
- Both with dark mode variants for enhanced glow effects
Add PDS hosting status card to identity sidebar:
- Detects Bluesky-hosted vs self-hosted PDSes
- Bluesky-hosted: shows encouragement to self-host with link to guide
- Self-hosted: acknowledges setup and fetches PDS version via /xrpc/_health
- Color-coded cards (blue for Bluesky, purple for self-hosted)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make renderVisualization async and await validateAppUrls to ensure
validation completes before filter panel initializes
- Rewrite validateAppUrls with two-phase approach:
1. Check if domain resolves as ATProto handle (fast, no CORS issues)
2. Fall back to HEAD request for non-ATProto domains
- Mark domains as invalid on any fetch error (not just Chrome-specific
error strings) for cross-browser compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add @atcute/oauth-browser-client integration for ATProto OAuth
- Create oauth.js module with login, callback handling, session management
- Update guestbook-ui.js with login/sign modals and proper auth flow
- Add CSS styling for login and sign forms
- Update oauth-client-metadata.json for wisp.place domain
- Configure Vite to inject OAuth environment variables for dev/prod
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use no-cors HEAD requests to check if app domains are actually
reachable. Only mark as invalid for DNS/connection failures
(ERR_NAME_NOT_RESOLVED, ERR_CONNECTION_REFUSED, timeout).
CORS blocks indicate the server exists, so don't mark invalid.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove Rust/Axum backend (Cargo.toml, src/*.rs, Dockerfile, fly.toml)
- Add Vite build system with MPA configuration
- Split monolithic HTML into modular ES modules:
- src/landing/: Landing page with atmosphere visualization
- src/view/: Main app with ATProto visualization, filters, MST viewer
- Add tangled CI for wisp.place deployment
- Fix avatar centering and app circle spacing
- Simplify domain validation (trust reversed ATProto namespaces)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- filter button with panel to toggle app visibility
- "all" shows all apps, "valid" hides unresolvable NSIDs, "none" hides all
- defaults to "valid" mode on first load
- persists filter preferences in localStorage per user
- visible apps reposition evenly around the circle when filtering
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
cargo fmt and clippy don't compile dependencies, so they don't need
openssl, pkg-config, or gcc. the PKG_CONFIG_PATH setup wasn't working
anyway (find command not available in nixery containers).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
clippy correctly identified that calling .last() on split('/') needlessly
iterates the entire iterator. since split() returns a DoubleEndedIterator,
we can use .next_back() to get the last element directly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
nixery containers don't automatically set PKG_CONFIG_PATH like nix-shell does.
dynamically find the openssl.dev path in /nix/store and set PKG_CONFIG_PATH
to point to its pkgconfig directory before running cargo commands.
tested locally in nixos/nix docker container with the same dependencies.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
users can now leave an optional message when signing the guestbook. messages are displayed in handwritten font alongside the signature metadata.
- backend: added text field to visit records and signature struct
- frontend: added message input modal with 280 char limit
- docs: updated lexicon documentation with new field and credits to @thisismissem.social and @essentialrandom.bsky.social
inspired by https://github.com/FujoWebDev/lexicon-guestbook
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add pre-commit configuration with cargo fmt and clippy checks
- Remove obsolete .tangled/workflows/deploy.yaml
- Fix all clippy warnings:
- Replace redundant closures with function references
- Use .first() instead of .get(0)
- Use .or_default() instead of .or_insert_with(Vec::new)
- Use .unsigned_abs() instead of .abs() as u32
- Use .div_ceil() instead of manual ceiling division
- Simplify iterator patterns with .flatten()
- Remove needless return statements
- Replace useless format!() with .to_string()
- Use array literals instead of vec![] where appropriate
- Remove redundant field names in struct initialization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace in-memory cache with UFOs API for persistent global state
- Split architecture: query page owner's PDS for button state, use UFOs for global list
- Fix unauthenticated user flow to trigger identity confirmation on button click
- Add signature count display to guestbook modal
- Fix font consistency across all guestbook text (unified monospace)
- Implement optimistic cache updates for sign/unsign actions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
cleaned up all debug logging from app.js while preserving essential error logging with console.error. removed verbose firehose event logs, guestbook state logs, and app circle management logs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update POV indicator: change "point of view:" to "point of view of" and make handle a clickable Bluesky profile link
- Fix guestbook button avatar display: always show page owner's avatar, not authenticated user's avatar
- Add dynamic guestbook sign text: shows "you already signed" when page owner has signed, "sign the guest list" otherwise
- Update authentication success toast to mention both sign and unsign actions
- Add CSS styling for clickable POV handle with hover effects
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- populate global signatures cache from existing visit records on page load
- make guestbook truly global - shows all visitors regardless of page
- check page owner signature status without authentication
- refine particle animations: slower speed, gentler easing, fade in/out
- make PDS pulse more subtle with proper centering transform
- fix pdsls.dev links to include at:// prefix and /app.at-me.visit path
- update justfile to watch static directory for template changes
- remove tangled.org logo from info modal
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- add drop shadow to guestbook avatar button
- show authenticated user's avatar in sign button (not page owner's)
- add confirmation modal for unauthenticated users: "are you @handle?"
- implement global guestbook signatures cache (all users, not per-DID)
- invalidate cache on sign/delete operations
- return handle and avatar in /api/auth/status
- add "point of view: @handle" indicator on left side with neon styling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
adds full-screen modal to view all guestbook signatures with avatars and timestamps. fixes critical firehose bug where guestbook collection wasn't being watched if it didn't exist yet.
backend changes:
- always include app.at-me.visit in watched collections (routes.rs:1184-1187)
- add /api/guestbook/signatures endpoint with caching (routes.rs:981-1096)
- cache invalidation on sign/unsign for real-time updates
frontend changes:
- add view guestbook button (👥 icon) positioned left of sign button
- full-screen modal with loading, empty, and error states
- fetch and display signatures sorted by most recent first
- modal shows handle, avatar, and formatted timestamp for each signer
fixes:
- guestbook collection now tracked even before first signature
- real-time particle animations work on first sign/unsign
- dynamic circle appearance/removal works correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
firehose now registers ALL collections from user's PDS instead of hardcoded list:
- firehose endpoint fetches collections from PDS via describeRepo
- passes collections to get_or_create_broadcaster()
- registers ingesters for every collection the user has
- removes hardcoded BSKY_COLLECTIONS constant (dead code)
before: only listened to hardcoded bluesky collections
after: listens to all collections (bluesky, whitewind, tangled, guestbook, etc.)
this fixes the original bug where non-bluesky apps wouldn't show real-time updates. now any app writing to your PDS will be visualized when watch live is enabled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
app circle lifecycle:
- automatically remove app circle when delete particle animation completes
- added removeAppCircle() function to clean up DOM and globalApps state
- repositions remaining circles smoothly after removal
fixed dynamically added circles:
- app circles created via firehose now properly fetch and display records
- clicking circle loads record count from PDS
- expanding records shows full data with copy functionality
- fixes "loading..." state that never resolved
improved toast notifications:
- hide "view record" link for delete events (no record to view)
- collection names now use inline code formatting for better readability
- code style: monospace background, subtle padding, reduced font size
- changed to innerHTML to support formatted content
this completes the guestbook circle lifecycle: create → visualize → interact → delete → remove
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
the firehose toast notification now behaves consistently on both desktop and mobile:
- uses max-width: min(300px, calc(100vw - 2rem)) to respect both size limit and viewport
- width: max-content keeps toast compact, only as wide as needed
- removed mobile-specific left/right/max-width overrides that caused full-width stretch
- toast remains right-aligned and compact on all screen sizes
before: mobile toast spanned entire screen width, looked oversized
after: mobile toast is same compact size as desktop, just positioned lower to avoid button overlap
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
users can now "sign" the guestbook by writing app.at-me.visit records to their own PDS via OAuth authentication. changes are visualized in real-time through firehose integration with particle animations.
authentication & authorization:
- OAuth flow using atrium-oauth with proper scopes (repo:app.at-me.visit)
- session management via actix-session to track auth state
- redirect flow that returns users to page after authentication
- agent caching by DID to avoid re-authentication
api endpoints:
- POST /api/sign-guestbook - creates visit record in user's PDS
- DELETE /api/sign-guestbook - removes visit record (unsign)
- GET /api/auth/status - checks authentication and existing records
- /login and /callback - OAuth authentication flow
real-time visualization:
- firehose integration watches for new visit records
- particle animation flows from app circle to PDS on record creation
- dynamic app circle creation when new apps write to your PDS
- toast notifications showing firehose events
- proper namespace handling (app.at-me → display as at-me.app)
critical bug fix:
- firehose was only listening for hardcoded Bluesky collections (app.bsky.*)
- added app.at-me.visit to registered collections in src/firehose.rs
- this was preventing any non-Bluesky app events from appearing
- remaining: should register ALL collections dynamically, not hardcoded list
utilities:
- scripts/delete_visits.py - delete all visit records from your PDS
- just clean-up-my-visits - quick command to run cleanup script
- uses uv for python dependencies with inline script metadata
frontend improvements:
- guestbook button with 3 states (not authenticated, ready, signed)
- watch prompt modal encouraging users to enable live mode
- extensive debug logging for troubleshooting (to be removed)
- dynamic app circle creation in real-time
technical details:
- extracted constants to src/constants.rs (collections, buffer sizes)
- updated dependencies: actix-session, atrium-oauth
- DID-filtered jetstream connections per user
- proper error handling for OAuth and API calls
known issues to address:
- app.js is 1800+ lines, needs refactoring into modules
- remove debug console.logs before production
- better loading states during OAuth flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>