···11-Welcome to your new TanStack Start app!
11+# Dudesky
2233-# Getting Started
33+An experimental [Bluesky](https://bsky.app) client built to explore [ATProto](https://atproto.com), [TanStack](https://tanstack.com), and modern frontend web development.
4455-To run this application:
55+## What it does
6677-```bash
88-npm install
99-npm run dev
1010-```
77+- Sign in with your Bluesky handle via ATProto OAuth (DPoP + PKCE)
88+- Browse your home feed with reply-parent context
99+- Navigate full conversation threads (ancestors + replies)
1010+- Image grids and link card previews for embedded content
11111212-# Building For Production
1212+## Stack
13131414-To build this application for production:
1414+| Layer | Tech |
1515+|---|---|
1616+| Framework | [TanStack Start](https://tanstack.com/start) (SSR, file-based routing, server functions) |
1717+| Routing | [TanStack Router](https://tanstack.com/router) |
1818+| UI | React 19, Tailwind CSS v4 |
1919+| ATProto | `@atproto/api`, `@atproto/oauth-client-node` |
2020+| Persistence | `better-sqlite3` (OAuth state + session store) |
2121+| Linting/Formatting | [Biome](https://biomejs.dev) |
2222+| Testing | Vitest + Playwright |
15231616-```bash
1717-npm run build
1818-```
1919-2020-## Testing
2121-2222-This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
2424+## Getting started
23252426```bash
2525-npm run test
2727+npm install
2628```
27292828-## Styling
2929-3030-This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
3131-3232-### Removing Tailwind CSS
3333-3434-If you prefer not to use Tailwind CSS:
3030+Create a `.env` file with the required environment variables:
35313636-1. Remove the demo pages in `src/routes/demo/`
3737-2. Replace the Tailwind import in `src/styles.css` with your own styles
3838-3. Remove `tailwindcss()` from the plugins array in `vite.config.ts`
3939-4. Uninstall the packages: `npm install @tailwindcss/vite tailwindcss -D`
4040-3232+```env
3333+VITE_APP_URL=http://localhost:3000
41343535+# DPoP key pair (generate with the ATProto key tool or openssl)
3636+PRIVATE_KEY_0=...
3737+PRIVATE_KEY_1=...
3838+PRIVATE_KEY_2=...
42394343-## Routing
4444-4545-This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`.
4646-4747-### Adding A Route
4848-4949-To add a new route to your application just add a new file in the `./src/routes` directory.
5050-5151-TanStack will automatically generate the content of the route file for you.
5252-5353-Now that you have two routes you can use a `Link` component to navigate between them.
5454-5555-### Adding Links
5656-5757-To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
5858-5959-```tsx
6060-import { Link } from "@tanstack/react-router";
4040+# Optional: override the SQLite DB path (defaults to dudesky.db in the project root)
4141+# DB_PATH=/path/to/dudesky.db
6142```
62436363-Then anywhere in your JSX you can use it like so:
4444+Then start the dev server:
64456565-```tsx
6666-<Link to="/about">About</Link>
4646+```bash
4747+npm run dev
6748```
68496969-This will create a link that will navigate to the `/about` route.
7070-7171-More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
7272-7373-### Using A Layout
7474-7575-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`.
5050+The app runs on port 3000 by default. Open it, enter your Bluesky handle, and sign in via the ATProto OAuth flow.
76517777-Here is an example layout that includes a header:
7878-7979-```tsx
8080-import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
5252+## Project structure
81538282-export const Route = createRootRoute({
8383- head: () => ({
8484- meta: [
8585- { charSet: 'utf-8' },
8686- { name: 'viewport', content: 'width=device-width, initial-scale=1' },
8787- { title: 'My App' },
8888- ],
8989- }),
9090- shellComponent: ({ children }) => (
9191- <html lang="en">
9292- <head>
9393- <HeadContent />
9494- </head>
9595- <body>
9696- <header>
9797- <nav>
9898- <Link to="/">Home</Link>
9999- <Link to="/about">About</Link>
100100- </nav>
101101- </header>
102102- {children}
103103- <Scripts />
104104- </body>
105105- </html>
106106- ),
107107-})
10854```
109109-110110-More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
111111-112112-## Server Functions
113113-114114-TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components.
115115-116116-```tsx
117117-import { createServerFn } from '@tanstack/react-start'
118118-119119-const getServerTime = createServerFn({
120120- method: 'GET',
121121-}).handler(async () => {
122122- return new Date().toISOString()
123123-})
124124-125125-// Use in a component
126126-function MyComponent() {
127127- const [time, setTime] = useState('')
128128-129129- useEffect(() => {
130130- getServerTime().then(setTime)
131131- }, [])
132132-133133- return <div>Server time: {time}</div>
134134-}
135135-```
136136-137137-## API Routes
138138-139139-You can create API routes by using the `server` property in your route definitions:
140140-141141-```tsx
142142-import { createFileRoute } from '@tanstack/react-router'
143143-import { json } from '@tanstack/react-start'
144144-145145-export const Route = createFileRoute('/api/hello')({
146146- server: {
147147- handlers: {
148148- GET: () => json({ message: 'Hello, World!' }),
149149- },
150150- },
151151-})
5555+src/
5656+ components/ # Shared UI components (Header, Footer, EmbedBlock)
5757+ lib/ # Server-only utilities (DB setup, OAuth client, types)
5858+ routes/ # File-based routes (TanStack Router)
5959+ __root.tsx # App shell, root loader for auth state
6060+ index.tsx # Landing page
6161+ login.tsx # Handle entry + OAuth redirect
6262+ callback.tsx # OAuth callback, sets DID cookie
6363+ feed.tsx # Home timeline
6464+ post.$uri.tsx # Thread view (ancestors + focal post + replies)
6565+ logout.tsx # POST handler, clears session
6666+ client-metadata.tsx # ATProto OAuth client metadata endpoint
6767+ jwks.tsx # JWK set endpoint
6868+ styles.css # Tailwind v4 + custom design system
15269```
15370154154-## Data Fetching
7171+## Design system
15572156156-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.
7373+The app has a custom beach/ocean-themed design system defined in `src/styles.css`:
15774158158-For example:
7575+- **Fonts:** Manrope (body), Fraunces (display headings)
7676+- **Color tokens:** `--sea-ink`, `--lagoon`, `--palm`, `--sand`, `--foam`, and friends — all with light/dark variants
7777+- **Utility classes:** `.island-shell` (frosted-glass cards), `.island-kicker` (small-caps labels), `.display-title`, `.rise-in` (entrance animation), `.nav-link`
15978160160-```tsx
161161-import { createFileRoute } from '@tanstack/react-router'
7979+Tokens can be used as Tailwind values directly: `text-[--sea-ink-soft]`, `bg-[--surface]`, `border-[--line]`.
16280163163-export const Route = createFileRoute('/people')({
164164- loader: async () => {
165165- const response = await fetch('https://swapi.dev/api/people')
166166- return response.json()
167167- },
168168- component: PeopleComponent,
169169-})
8181+## Scripts
17082171171-function PeopleComponent() {
172172- const data = Route.useLoaderData()
173173- return (
174174- <ul>
175175- {data.results.map((person) => (
176176- <li key={person.name}>{person.name}</li>
177177- ))}
178178- </ul>
179179- )
180180-}
8383+```bash
8484+npm run dev # Start dev server (port 3000)
8585+npm run build # Production build
8686+npm run preview # Preview production build
8787+npm run test # Run Vitest unit tests
18188```
18289183183-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).
184184-185185-# Demo files
9090+## ATProto notes
18691187187-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.
188188-189189-# Learn More
9292+- OAuth state and session tokens are persisted in SQLite (`oauth_state` and `oauth_session` tables)
9393+- 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
9494+- Server route handlers use `new Response(null, { status: 302, headers: {...} })` for redirects — not `Response.redirect()` (immutable headers) and not TanStack Router's `redirect()` helper
19095191191-You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
9696+## Purpose
19297193193-For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start).
9898+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.