experimental bluesky client
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update README

+65 -160
+65 -160
README.md
··· 1 - Welcome to your new TanStack Start app! 1 + # Dudesky 2 2 3 - # Getting Started 3 + An experimental [Bluesky](https://bsky.app) client built to explore [ATProto](https://atproto.com), [TanStack](https://tanstack.com), and modern frontend web development. 4 4 5 - To run this application: 5 + ## What it does 6 6 7 - ```bash 8 - npm install 9 - npm run dev 10 - ``` 7 + - Sign in with your Bluesky handle via ATProto OAuth (DPoP + PKCE) 8 + - Browse your home feed with reply-parent context 9 + - Navigate full conversation threads (ancestors + replies) 10 + - Image grids and link card previews for embedded content 11 11 12 - # Building For Production 12 + ## Stack 13 13 14 - To build this application for production: 14 + | Layer | Tech | 15 + |---|---| 16 + | Framework | [TanStack Start](https://tanstack.com/start) (SSR, file-based routing, server functions) | 17 + | Routing | [TanStack Router](https://tanstack.com/router) | 18 + | UI | React 19, Tailwind CSS v4 | 19 + | ATProto | `@atproto/api`, `@atproto/oauth-client-node` | 20 + | Persistence | `better-sqlite3` (OAuth state + session store) | 21 + | Linting/Formatting | [Biome](https://biomejs.dev) | 22 + | Testing | Vitest + Playwright | 15 23 16 - ```bash 17 - npm run build 18 - ``` 19 - 20 - ## Testing 21 - 22 - This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: 24 + ## Getting started 23 25 24 26 ```bash 25 - npm run test 27 + npm install 26 28 ``` 27 29 28 - ## Styling 29 - 30 - This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. 31 - 32 - ### Removing Tailwind CSS 33 - 34 - If you prefer not to use Tailwind CSS: 30 + Create a `.env` file with the required environment variables: 35 31 36 - 1. Remove the demo pages in `src/routes/demo/` 37 - 2. Replace the Tailwind import in `src/styles.css` with your own styles 38 - 3. Remove `tailwindcss()` from the plugins array in `vite.config.ts` 39 - 4. Uninstall the packages: `npm install @tailwindcss/vite tailwindcss -D` 40 - 32 + ```env 33 + VITE_APP_URL=http://localhost:3000 41 34 35 + # DPoP key pair (generate with the ATProto key tool or openssl) 36 + PRIVATE_KEY_0=... 37 + PRIVATE_KEY_1=... 38 + PRIVATE_KEY_2=... 42 39 43 - ## Routing 44 - 45 - This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`. 46 - 47 - ### Adding A Route 48 - 49 - To add a new route to your application just add a new file in the `./src/routes` directory. 50 - 51 - TanStack will automatically generate the content of the route file for you. 52 - 53 - Now that you have two routes you can use a `Link` component to navigate between them. 54 - 55 - ### Adding Links 56 - 57 - To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. 58 - 59 - ```tsx 60 - import { Link } from "@tanstack/react-router"; 40 + # Optional: override the SQLite DB path (defaults to dudesky.db in the project root) 41 + # DB_PATH=/path/to/dudesky.db 61 42 ``` 62 43 63 - Then anywhere in your JSX you can use it like so: 44 + Then start the dev server: 64 45 65 - ```tsx 66 - <Link to="/about">About</Link> 46 + ```bash 47 + npm run dev 67 48 ``` 68 49 69 - This will create a link that will navigate to the `/about` route. 70 - 71 - More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). 72 - 73 - ### Using A Layout 74 - 75 - In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`. 50 + The app runs on port 3000 by default. Open it, enter your Bluesky handle, and sign in via the ATProto OAuth flow. 76 51 77 - Here is an example layout that includes a header: 78 - 79 - ```tsx 80 - import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router' 52 + ## Project structure 81 53 82 - export const Route = createRootRoute({ 83 - head: () => ({ 84 - meta: [ 85 - { charSet: 'utf-8' }, 86 - { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 87 - { title: 'My App' }, 88 - ], 89 - }), 90 - shellComponent: ({ children }) => ( 91 - <html lang="en"> 92 - <head> 93 - <HeadContent /> 94 - </head> 95 - <body> 96 - <header> 97 - <nav> 98 - <Link to="/">Home</Link> 99 - <Link to="/about">About</Link> 100 - </nav> 101 - </header> 102 - {children} 103 - <Scripts /> 104 - </body> 105 - </html> 106 - ), 107 - }) 108 54 ``` 109 - 110 - More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). 111 - 112 - ## Server Functions 113 - 114 - TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components. 115 - 116 - ```tsx 117 - import { createServerFn } from '@tanstack/react-start' 118 - 119 - const getServerTime = createServerFn({ 120 - method: 'GET', 121 - }).handler(async () => { 122 - return new Date().toISOString() 123 - }) 124 - 125 - // Use in a component 126 - function MyComponent() { 127 - const [time, setTime] = useState('') 128 - 129 - useEffect(() => { 130 - getServerTime().then(setTime) 131 - }, []) 132 - 133 - return <div>Server time: {time}</div> 134 - } 135 - ``` 136 - 137 - ## API Routes 138 - 139 - You can create API routes by using the `server` property in your route definitions: 140 - 141 - ```tsx 142 - import { createFileRoute } from '@tanstack/react-router' 143 - import { json } from '@tanstack/react-start' 144 - 145 - export const Route = createFileRoute('/api/hello')({ 146 - server: { 147 - handlers: { 148 - GET: () => json({ message: 'Hello, World!' }), 149 - }, 150 - }, 151 - }) 55 + src/ 56 + components/ # Shared UI components (Header, Footer, EmbedBlock) 57 + lib/ # Server-only utilities (DB setup, OAuth client, types) 58 + routes/ # File-based routes (TanStack Router) 59 + __root.tsx # App shell, root loader for auth state 60 + index.tsx # Landing page 61 + login.tsx # Handle entry + OAuth redirect 62 + callback.tsx # OAuth callback, sets DID cookie 63 + feed.tsx # Home timeline 64 + post.$uri.tsx # Thread view (ancestors + focal post + replies) 65 + logout.tsx # POST handler, clears session 66 + client-metadata.tsx # ATProto OAuth client metadata endpoint 67 + jwks.tsx # JWK set endpoint 68 + styles.css # Tailwind v4 + custom design system 152 69 ``` 153 70 154 - ## Data Fetching 71 + ## Design system 155 72 156 - There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. 73 + The app has a custom beach/ocean-themed design system defined in `src/styles.css`: 157 74 158 - For example: 75 + - **Fonts:** Manrope (body), Fraunces (display headings) 76 + - **Color tokens:** `--sea-ink`, `--lagoon`, `--palm`, `--sand`, `--foam`, and friends — all with light/dark variants 77 + - **Utility classes:** `.island-shell` (frosted-glass cards), `.island-kicker` (small-caps labels), `.display-title`, `.rise-in` (entrance animation), `.nav-link` 159 78 160 - ```tsx 161 - import { createFileRoute } from '@tanstack/react-router' 79 + Tokens can be used as Tailwind values directly: `text-[--sea-ink-soft]`, `bg-[--surface]`, `border-[--line]`. 162 80 163 - export const Route = createFileRoute('/people')({ 164 - loader: async () => { 165 - const response = await fetch('https://swapi.dev/api/people') 166 - return response.json() 167 - }, 168 - component: PeopleComponent, 169 - }) 81 + ## Scripts 170 82 171 - function PeopleComponent() { 172 - const data = Route.useLoaderData() 173 - return ( 174 - <ul> 175 - {data.results.map((person) => ( 176 - <li key={person.name}>{person.name}</li> 177 - ))} 178 - </ul> 179 - ) 180 - } 83 + ```bash 84 + npm run dev # Start dev server (port 3000) 85 + npm run build # Production build 86 + npm run preview # Preview production build 87 + npm run test # Run Vitest unit tests 181 88 ``` 182 89 183 - Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). 184 - 185 - # Demo files 90 + ## ATProto notes 186 91 187 - Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. 188 - 189 - # Learn More 92 + - OAuth state and session tokens are persisted in SQLite (`oauth_state` and `oauth_session` tables) 93 + - The authenticated DID is stored in a plain (non-encoded) cookie — colons in DIDs are valid in cookie values and must not be percent-encoded or `client.restore()` will fail 94 + - Server route handlers use `new Response(null, { status: 302, headers: {...} })` for redirects — not `Response.redirect()` (immutable headers) and not TanStack Router's `redirect()` helper 190 95 191 - You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). 96 + ## Purpose 192 97 193 - For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start). 98 + This is a learning project — not a production client. The goal is to understand ATProto's OAuth and data model, get hands-on with TanStack Start's SSR + server functions model, and experiment with modern frontend patterns.