Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 4ce232ee856d7de1ba5f2b6d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
AGENTS.md
# Bio
Bio makes free link in bio pages, and is a free alternative to Linktree, Beacons and Carrd's link pages. A page lives at `/<handle>` and holds a photo, a name, a one-line bio, links and headers, and social icons. It is an OpenApp in the OpenApps free apps store (https://free-apps.openapps.xyz/), owned by the OpenApps team.
## What it promises
- Free, with no limit on pages or links, no ads and no paid tier. Every theme and the view and click counts are free for everyone. Never add a cap, an upsell, tracking or a badge the owner cannot turn off.
- No sign-up. Every visitor is a guest account created in the browser.
- Pages are public by design, like any link in bio page: anyone can read one, and the `pages` collection can be listed. Owner keys (readable only by the API, to check them) and click counts (readable only by owners and the API) live in separate collections.
- Bio counts page views and link clicks and keeps nothing about visitors.
- The public page opens fast on phones: it loads no React and no Bounded client.
- AI assistants can do everything a person can through the public API (`functions/api.ts`), documented in `public/llms.txt` and `public/openapi.json`, with the landing as plain markdown in `public/index.md`. Keep all three true whenever the API or the app changes.
## How it is built
- `index.html` is the static landing page that search engines and AI crawlers read. Keep its copy, FAQ and JSON-LD in sync with what the app does. The inline script at the very top of `<head>` spots a public page (`/<handle>`), marks `<html class="bio">` and starts `GET /view/<handle>` (the page plus one counted view, written while the page is read) before anything else loads. Its reserved list must match `RESERVED` in `functions/lib/bio.ts`, and its API base must carry the real app id.
- `src/main.tsx` loads `src/public.ts` for a public page (plain DOM, no React) and `src/app.tsx` for everything else. `/` mounts "Claim your page", "Your pages" and the example phone into the landing; `/e/<handle>#k=<key>` hides the landing and shows the editor (`src/Editor.tsx`).
- `src/page.ts` draws a page as HTML. The public page and the editor's phone preview both use it, so the preview is exactly what visitors see. It escapes everything people type and only lets http(s), mailto and image data addresses into attributes; keep it that way. It leaves hidden links out and draws headers as small titles. The social icons are inline SVG drawn there.
- The editor (`src/Editor.tsx`) keeps every change in a draft until Save. Links reorder by dragging the handle (pointer events, so touch works) or with the arrow keys on it, and slide into place with a FLIP animation. Share opens a dialog with the page address and its QR code (`functions/lib/qr.ts`, copied from the Links app).
- `functions/lib/bio.ts` holds the rules for handles, links, headers, socials, photos and themes for both the app and the API, and reads stored pages defensively (`fromDoc`). `links` is a JSON list where a header is `{id, title, url: "", header: true}` and any item may carry `hidden: true`; both live inside the same string field, so the policy does not change for them.
- `functions/lib/config.ts` holds the app id, the site address (`SITE_URL`) and the API base.
- `policy.json` holds the data rules. `pages/<handle>` is readable by anyone; owners change everything but `views`, and the public `api` function may only add one to `views`. `pages/<handle>/clicks/<linkId>` is counted only by the API, one at a time, and read only by owners. Owners are the creator, or anyone who presents the owner key (`pagekeys/<handle>`, readable only by the API) through `pageclaims`. The edit link carries the key in its `#k=` fragment, which never reaches a server.
- `ogRoutes` in `policy.json` gives each shared `/<handle>` link its own card: the page's name as the title and its bio as the description. Photos are stored as small data URLs inside the page, so cards use the default Bio image.
- Photos are cropped square and shrunk to 256 pixels in the browser (`src/lib/photo.ts`), then stored as a JPEG data URL of at most 60,000 characters.
## Working on it
`npm install && npm run build` typechecks and builds `dist/`. Ship changes through previews and releases as your runtime instructions describe; the app is an OpenApp, so nobody deploys it directly. The function bundles `functions/lib` when it is deployed, so a change there needs the function deployed as well as the site.
`SITE_URL` in `functions/lib/config.ts` is the app's address, `https://bio-free.openapps.xyz`; the API uses it for the links it hands back.
Check a change by claiming a page, adding a photo, links, a header and social icons, dragging a link and hiding one, opening the page in a fresh browser and tapping a link (the editor's view and click counts should go up), editing from the edit link on another browser, and calling the API with curl, including a wrong owner key. Check the editor at 390 pixels wide too: phones get Edit and Preview tabs and the save bar sits at the bottom.
Known limit: public functions answer at most 120 requests a minute per app and Cloudflare location, and every page view goes through `GET /view`. The page retries twice on a busy answer, then says it could not load.
## Voice
Plain words, short sentences, no jargon, no em dashes. Say what happens, not how clever it is.
Change history
Quality pass: reorder, hide links, headers, QR share, 15 social icons, API docs
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync