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 ee59501e91bae6d2c23a8370
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.
src/App.tsx
// The app's pages: your lists, a list (opening its share link joins it), and the admin link.
import { useEffect, useState } from "react";
import { get } from "@bounded-sh/client";
import { me, explain } from "./lib/bounded";
import { navigate } from "./lib/router";
import { useMine, joinList, claimAdmin, remember } from "./lib/data";
import { ListPage } from "./ListPage";
import { NewList } from "./Start";
import { toast } from "./ui";
import type { ListDoc } from "./lib/data";
export default function App({ path }: { path: string }) {
const m = path.match(/^\/l\/([A-Za-z0-9]{20})(\/admin)?$/);
if (!m) return <ListsPage />;
if (m[2]) return <AdminLink key={m[1]} id={m[1]} />;
return <ListGate key={m[1]} id={m[1]} />;
}
const go = (to: string) => (e: React.MouseEvent) => { e.preventDefault(); navigate(to); };
function ListsPage() {
const mine = Object.values(useMine()).sort((a, b) => (b.opened || 0) - (a.opened || 0));
useEffect(() => { document.title = "Your lists | Lists"; }, []);
return (
<div className="page narrow">
<h1 className="page-title">Your lists</h1>
{mine.length ? (
<ul className="lists">
{mine.map((l) => (
<li key={l.id}>
<a href={`/l/${l.id}`} onClick={go(`/l/${l.id}`)}>
<span className="lists-name">{l.name}</span>
<span className="lists-meta">{l.toGet ? `${l.toGet} ${l.kind === "todo" ? "left" : "to get"}` : "Nothing left"}{l.ownerKey ? " · yours" : ""}</span>
</a>
</li>
))}
</ul>
) : (
<div className="empty">
<img className="art" src="/art/card-lists.webp" alt="" width="200" height="150" />
<p className="muted">Lists you make or open on this device show up here. To open a list someone shared, use the link they sent.</p>
</div>
)}
<section className="card new-card">
<h2 className="h3">New list</h2>
<NewList compact />
</section>
</div>
);
}
/**
* Opens a list. With a share link (#secret) it joins first; a list already on this device opens right away,
* even offline. A share link opened online still joins on a device that has the list, so someone who was
* removed and sent the new link gets back in.
*/
function ListGate({ id }: { id: string }) {
const known = Boolean(useMine()[id]);
const [secret] = useState(() => location.hash.slice(1));
const [opensNow] = useState(() => known && !(/^[A-Za-z0-9]{24}$/.test(secret) && navigator.onLine));
const [state, setState] = useState<"ready" | "joining" | "failed" | "missing">(opensNow ? "ready" : "joining");
const [error, setError] = useState("");
useEffect(() => {
// The link's secret is only needed once: keep it out of the address bar and history.
const done = () => { if (location.hash) history.replaceState(null, "", `/l/${id}`); };
if (opensNow) return done();
let on = true;
(async () => {
try {
if (/^[A-Za-z0-9]{24}$/.test(secret)) {
await joinList(id, secret);
} else {
// No link secret: this device may have joined before and lost its list of lists.
const uid = await me();
if (!(await get(`lists/${id}/members/${uid}`))) { if (on) setState("missing"); return; }
const list = (await get(`lists/${id}`)) as ListDoc | null;
remember(id, { name: list?.name || "Shared list", kind: list?.kind || "grocery", opened: Math.floor(Date.now() / 1000) });
}
done();
if (on) setState("ready");
} catch (err) {
if (!on) return;
// The device has this list: open its copy, and the list page says so if you are not on it any more.
if (known) { done(); setState("ready"); return; }
setError(err instanceof Error && !/offline/.test(explain(err)) ? err.message : explain(err));
setState("failed");
}
})();
return () => { on = false; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
if (state === "ready") return <ListPage id={id} />;
if (state === "joining") return <div className="page narrow"><div className="loading"><span className="spinner" /> Opening the list...</div></div>;
return (
<div className="page narrow">
<h1 className="page-title">{state === "missing" ? "This list is not on this device" : "Could not open the list"}</h1>
<p className="lede">{state === "missing" ? "Open the share link someone on the list sent you. It ends with a long code after a #." : error}</p>
<a className="btn" href="/lists" onClick={go("/lists")}>Your lists</a>
</div>
);
}
function AdminLink({ id }: { id: string }) {
const [error, setError] = useState("");
useEffect(() => {
const key = location.hash.slice(1);
if (!/^[A-Za-z0-9]{24}$/.test(key)) { setError("This admin link is not complete. Copy all of it, including the part after the #."); return; }
claimAdmin(id, key)
.then(() => { navigate(`/l/${id}`, true); toast("You can manage this list on this device now."); })
.catch((err) => setError(err instanceof Error && !/offline/.test(explain(err)) ? err.message : explain(err)));
}, [id]);
return (
<div className="page narrow">
{error ? (
<>
<h1 className="page-title">Could not open the admin link</h1>
<p className="lede">{error}</p>
<a className="btn" href="/lists" onClick={go("/lists")}>Your lists</a>
</>
) : <div className="loading"><span className="spinner" /> Opening the admin link...</div>}
</div>
);
}
Change history
Point OpenApps links to openapps.free
Bind agent address in policy constants
Use OpenApps service URLs and add contribution strip
Bounded source sync
Bounded source sync
Bounded source sync