9} from "https://esm.sh/react-router-dom@7.5.0?deps=react@19,react-dom@19";
10import { routes } from "./routes.tsx";
11import { app as apiRoute } from "./backend/app.ts";
12
13const CLIENT_MODULE = import.meta.resolve("./client.tsx");
14
15const app = new Hono()
16 .route("/api", apiRoute)
17 .get("*", async (c) => {
18 try {
1import { email } from "https://esm.town/v/std/email?v=13";
2import { sqlite } from "https://esm.town/v/std/sqlite2?v=1";
3import { AtpAgent, AtpSessionData, AtpSessionEvent } from "npm:@atproto/api";
4import diff from "npm:fast-diff@1.3.0";
5
26
27 // Supports up to 5,000 (100*50) follows, stopping there to try
28 // and avoid rate limits. These APIs have pretty low limits, sound off here
29 // https://github.com/bluesky-social/atproto/discussions/3356
30 outerLoop: for (let i = 0; i < 20; i++) {
29
30// Mount routes
31app.route("/api/jobs", jobRoutes);
32app.route("/api/chat", chatRoutes);
33app.route("/", staticRoutes);
34
31
32 try {
33 const response = await fetch('/api/chat');
34 if (!response.ok) throw new Error('Failed to fetch chat messages');
35
97
98 try {
99 const response = await fetch('/api/chat', {
100 method: 'POST',
101 headers: {
36
37 try {
38 const response = await fetch('/api/jobs');
39 if (!response.ok) throw new Error('Failed to fetch jobs');
40
77 };
78
79 const response = await fetch('/api/jobs', {
80 method: 'POST',
81 headers: {
15## Technical Stack
16
17- Backend: Hono.js API framework
18- Database: SQLite for data persistence
19- Frontend: HTML, JavaScript with Tailwind CSS for styling
43```
44
45## API Endpoints
46
47- `GET /api/jobs` - Get all job postings
48- `POST /api/jobs` - Create a new job posting
49- `GET /api/chat` - Get chat messages
50- `POST /api/chat` - Post a new chat message
1import { email } from "https://esm.town/v/std/email?v=13";
2import { sqlite } from "https://esm.town/v/std/sqlite2?v=1";
3import { AtpAgent, AtpSessionData, AtpSessionEvent } from "npm:@atproto/api";
4import diff from "npm:fast-diff@1.3.0";
5
26
27 // Supports up to 5,000 (100*50) follows, stopping there to try
28 // and avoid rate limits. These APIs have pretty low limits, sound off here
29 // https://github.com/bluesky-social/atproto/discussions/3356
30 outerLoop: for (let i = 0; i < 20; i++) {
6export const client = hc<AppType>(
7 typeof window !== "undefined"
8 ? window.location.origin + "/api"
9 : import.meta.url + "/api",
10 { init: { credentials: "include" } },
11);
17I was an avid user of [ThinkUp](https://www.thinkupapp.com/), a tool that connected to Twitter and sent me a daily email with profile updates from all the people I followed. I found it useful in work and for fun. Maybe one of my friends switched jobs or changed their username to something goofy or political. I want to know! In the distant past Facebook would include profile updates in the newsfeed: why not that for Twitter? ThinkUp did some other cool stuff, like providing full archives of Tweets in a more convenient format than Twitter did themselves.
18
19But Twitter [is bad now](https://macwright.com/2025/03/04/twitter-eol) and ThinkUp [shut down in 2016](https://www.thinkupapp.com/) because [the APIs that they were relying on from Twitter, Facebook, and Instagram were all locked down and limited](https://medium.com/@anildash/the-end-of-thinkup-e600bc46cc56). How disappointing.
20
21But there's a new social network in town, [Bluesky](https://bsky.app/), and it's ~~impossible~~ somewhat more difficult to corrupt and enshittify than those networks were, and it comes with a pretty good, if sometimes weird API that gives you access to everything you need.
22
23Could you build some of ThinkUp on Bluesky? Yes. This is it.
14const app = new Hono();
15
16// api server
17app.get("/api", async (c) => {
18 return c.json([
19 { name: "Leia", breed: "DSH", coat: "Calico" },