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 703ed60f2cc5ad3d98a5bf4a
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/ui.tsx
// Small shared pieces: modal sheet, toast (with an optional action like Undo), copy, share, a
// popover, and the icons.
import { useEffect, useRef, useState, type ReactNode } from "react";
export function Modal({ title, onClose, children, footer, wide = false }: { title: string; onClose: () => void; children: ReactNode; footer?: ReactNode; wide?: boolean }) {
const ref = useRef<HTMLDivElement>(null);
const close = useRef(onClose);
close.current = onClose;
useEffect(() => {
const prev = document.activeElement as HTMLElement | null;
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape" && !document.querySelector(".pop")) close.current(); };
document.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
ref.current?.querySelector<HTMLElement>("[autofocus], input, select, textarea")?.focus();
return () => {
document.removeEventListener("keydown", onKey);
document.body.style.overflow = "";
prev?.focus?.();
};
}, []);
return (
<div className="modal-backdrop" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
<div className={`modal${wide ? " modal-wide" : ""}`} role="dialog" aria-modal="true" aria-label={title} ref={ref}>
<div className="modal-head">
<h2 className="modal-title">{title}</h2>
<button className="icon-btn" type="button" onClick={onClose} aria-label="Close"><X /></button>
</div>
<div className="modal-body">{children}</div>
{footer && <div className="modal-foot">{footer}</div>}
</div>
</div>
);
}
/** A small floating panel under a button. Closes on outside click or Escape. */
export function Popover({ open, onClose, children, align = "left", label }: { open: boolean; onClose: () => void; children: ReactNode; align?: "left" | "right"; label: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const onDown = (e: MouseEvent | TouchEvent) => {
const el = ref.current;
if (el && !el.contains(e.target as Node) && !(el.parentElement?.contains(e.target as Node))) onClose();
};
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") { e.stopPropagation(); onClose(); } };
document.addEventListener("mousedown", onDown);
document.addEventListener("touchstart", onDown);
document.addEventListener("keydown", onKey, true);
return () => {
document.removeEventListener("mousedown", onDown);
document.removeEventListener("touchstart", onDown);
document.removeEventListener("keydown", onKey, true);
};
}, [open, onClose]);
if (!open) return null;
return <div className={`pop pop-${align}`} ref={ref} role="dialog" aria-label={label}>{children}</div>;
}
let pushToast: ((text: string, action?: { label: string; run: () => void }) => void) | null = null;
export function toast(text: string, action?: { label: string; run: () => void }) { pushToast?.(text, action); }
export function Toaster() {
const [t, setT] = useState<{ text: string; action?: { label: string; run: () => void } } | null>(null);
useEffect(() => {
let timer: number | undefined;
pushToast = (text, action) => {
setT({ text, action });
window.clearTimeout(timer);
timer = window.setTimeout(() => setT(null), action ? 6000 : 3200);
};
return () => { pushToast = null; window.clearTimeout(timer); };
}, []);
if (!t) return null;
return (
<div className="toast" role="status">
<span>{t.text}</span>
{t.action && <button type="button" className="toast-action" onClick={() => { t.action!.run(); setT(null); }}>{t.action.label}</button>}
</div>
);
}
export async function copy(text: string, done = "Copied") {
try {
await navigator.clipboard.writeText(text);
toast(done);
} catch {
window.prompt("Copy this:", text);
}
}
export async function share(url: string, title: string, done = "Link copied") {
if (navigator.share && matchMedia("(pointer: coarse)").matches) {
try { await navigator.share({ title, url }); return; } catch { /* cancelled */ }
}
await copy(url, done);
}
export function download(name: string, text: string, type = "text/csv;charset=utf-8") {
const url = URL.createObjectURL(new Blob(["" + text], { type }));
const a = document.createElement("a");
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
a.remove();
window.setTimeout(() => URL.revokeObjectURL(url), 2000);
}
// ---- Icons (stroke icons, 20px grid) ----
type P = { size?: number };
const svg = (size: number, children: ReactNode, fill = false) => (
<svg width={size} height={size} viewBox="0 0 20 20" aria-hidden="true" fill={fill ? "currentColor" : "none"} stroke={fill ? "none" : "currentColor"} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">{children}</svg>
);
export const X = ({ size = 18 }: P) => svg(size, <path d="M5 5l10 10M15 5 5 15" />);
export const Play = ({ size = 18 }: P) => svg(size, <path d="M6.5 4.5v11l9-5.5z" />, true);
export const Stop = ({ size = 18 }: P) => svg(size, <rect x="5" y="5" width="10" height="10" rx="2" />, true);
export const Plus = ({ size = 18 }: P) => svg(size, <path d="M10 4v12M4 10h12" />);
export const Clock = ({ size = 20 }: P) => svg(size, <><circle cx="10" cy="10" r="7" /><path d="M10 6v4l2.5 2" /></>);
export const Grid = ({ size = 20 }: P) => svg(size, <><rect x="3" y="4" width="14" height="12" rx="2" /><path d="M3 8h14M8 8v8M12.5 8v8" /></>);
export const Chart = ({ size = 20 }: P) => svg(size, <path d="M4 16V9M8.5 16V4M13 16v-5M17 16H3" />);
export const Folder = ({ size = 20 }: P) => svg(size, <path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h3l1.5 2h6.5A1.5 1.5 0 0 1 17 8.5v6a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 3 14.5z" />);
export const Gear = ({ size = 20 }: P) => svg(size, <><circle cx="10" cy="10" r="2.6" /><path d="M10 2.8v2M10 15.2v2M17.2 10h-2M4.8 10h-2M15.1 4.9l-1.4 1.4M6.3 13.7l-1.4 1.4M15.1 15.1l-1.4-1.4M6.3 6.3 4.9 4.9" /></>);
export const Tag = ({ size = 18 }: P) => svg(size, <><path d="M3.5 10.5V4.5a1 1 0 0 1 1-1h6l6 6-7 7z" /><circle cx="7.5" cy="7.5" r="1.2" /></>);
export const Left = ({ size = 18 }: P) => svg(size, <path d="M12 4.5 6.5 10l5.5 5.5" />);
export const Right = ({ size = 18 }: P) => svg(size, <path d="M8 4.5 13.5 10 8 15.5" />);
export const Users = ({ size = 18 }: P) => svg(size, <><circle cx="7.5" cy="7" r="2.6" /><path d="M2.8 16c.5-2.6 2.4-4 4.7-4s4.2 1.4 4.7 4" /><path d="M13 4.6a2.5 2.5 0 0 1 0 4.8M14.5 12.2c1.5.5 2.5 1.8 2.8 3.8" /></>);
export const Check = ({ size = 16 }: P) => svg(size, <path d="M4.5 10.5 8 14l7.5-8" />);
export const Pencil = ({ size = 16 }: P) => svg(size, <path d="M12.5 4.5l3 3L7 16H4v-3z" />);
Change history
Point OpenApps links to openapps.free
Bind agent address constants in policy
Use functions.openapps.xyz alias and add OpenApp contribution strip
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync