commits
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- remove localStorage persistence for discover callout — it shows
every time for users with only bluesky, dismissable per session
- ghost app circles are now links that navigate to the app
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- stack callout vertically on mobile instead of side-by-side
- shorten callout text for tighter mobile fit
- reduce padding and font sizes for mobile
- sync view/index.html
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
view/index.html was stale with old onboarding element IDs and old copy.
this is the file wisp serves for /view, which is why the deployed site
was broken — new JS looked for onboardingBackdrop/Ring/Card but the HTML
still had onboardingSpotlight/Content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
also forces fresh HTML deploy to bust CDN cache — the previous
deploy served new JS but stale HTML with old onboarding element IDs,
which broke the tour silently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix step 2 spotlight: compute circular cutout around full app orbit
instead of dimming everything with no focus
- defer discover callout until onboarding completes
- wire up "replay tour" link in info modal
- replace suggested apps with grain.social, leaflet.pub, pckt.blog, plyr.fm
- fetch real logos for suggested apps via avatar API
- add descriptions (photos, blogging, music) under each suggestion
- add discover callout: "your account works with more than just bluesky"
- guard against null elements in onboarding dismiss
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
replace PDS/repository jargon with accessible language throughout:
"your account", "your data", "your apps" instead of technical terms.
PDS explained once as "personal data server" where needed.
- landing page: new "what is this?" copy with atmosphere framing
- info modal: reframed as "this is your account"
- identity sidebar: "your account" instead of "your personal data server"
- onboarding: rewritten with clip-path spotlight, smooth transitions
- merge chat.bsky into app.bsky (not a standalone app)
- ghost app circles for users with few apps (tangled, whitewind, etc.)
- wire up "replay tour" link in info modal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same approach as zat — add glibc and patchelf deps, then patch the
ELF interpreter to the nix store path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add X-Client header to identify the app in traffic stats.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save a 'guestbook' intent in sessionStorage before the OAuth redirect,
then consume it after init to automatically open the sign modal instead
of making the user click again.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues:
- client metadata used wisp.place domain but users access via custom
domain, causing OAuth state stored on one origin to be inaccessible
on the redirect origin
- after OAuth callback, the view page had no handle/did param, showing
"no identity specified" — now saves and restores the original URL
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bluesky PDS: gentler framing for users who may not know their PDS is
hosted by Bluesky
- Independent PDS: suggest moving to a new home, since users may still
want to switch providers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The onboarding tour's "explore your records" step targeted `.app-view`,
which could be hidden by the valid-apps filter, causing the spotlight
to appear at the top-left corner instead of on an app.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
view/index.html was stale with old onboarding element IDs and old copy.
this is the file wisp serves for /view, which is why the deployed site
was broken — new JS looked for onboardingBackdrop/Ring/Card but the HTML
still had onboardingSpotlight/Content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix step 2 spotlight: compute circular cutout around full app orbit
instead of dimming everything with no focus
- defer discover callout until onboarding completes
- wire up "replay tour" link in info modal
- replace suggested apps with grain.social, leaflet.pub, pckt.blog, plyr.fm
- fetch real logos for suggested apps via avatar API
- add descriptions (photos, blogging, music) under each suggestion
- add discover callout: "your account works with more than just bluesky"
- guard against null elements in onboarding dismiss
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
replace PDS/repository jargon with accessible language throughout:
"your account", "your data", "your apps" instead of technical terms.
PDS explained once as "personal data server" where needed.
- landing page: new "what is this?" copy with atmosphere framing
- info modal: reframed as "this is your account"
- identity sidebar: "your account" instead of "your personal data server"
- onboarding: rewritten with clip-path spotlight, smooth transitions
- merge chat.bsky into app.bsky (not a standalone app)
- ghost app circles for users with few apps (tangled, whitewind, etc.)
- wire up "replay tour" link in info modal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues:
- client metadata used wisp.place domain but users access via custom
domain, causing OAuth state stored on one origin to be inaccessible
on the redirect origin
- after OAuth callback, the view page had no handle/did param, showing
"no identity specified" — now saves and restores the original URL
Co-Authored-By: Claude Opus 4.6 <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>