const app = document.querySelector("#app");
const ROUTES = ["now", "rooms", "records", "route", "proof"];
const ROUTE_ALIASES = { feed: "now", signal: "route" };
const KIND_LABELS = {
roomDefinitions: "definition",
roomStates: "state",
submissions: "submission",
artifacts: "artifact",
actions: "action",
moderation: "moderation",
digests: "digest",
provenance: "provenance",
};
const ROOM_LABELS = {
"void/research": "Research",
"void/art": "Art",
"void/homelab": "Homelab",
};
let model = null;
let route = parseRoute();
let selectedRoom = "all";
let selectedKind = "all";
let query = "";
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function formatDate(value, mode = "short") {
if (!value) return "undated";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString(undefined, {
month: "short",
day: "2-digit",
...(mode === "long" ? { year: "numeric" } : {}),
hour: "2-digit",
minute: "2-digit",
});
}
function parseRoute() {
const hash = window.location.hash.replace(/^#\/?/, "");
const [candidate] = hash.split("?");
const normalized = ROUTE_ALIASES[candidate] ?? candidate;
return ROUTES.includes(normalized) ? normalized : "now";
}
function goto(nextRoute) {
route = nextRoute;
window.location.hash = nextRoute;
render();
}
function valueOf(record) {
return record?.value ?? {};
}
function recordRoom(record) {
return valueOf(record).room ?? valueOf(record).scope ?? "network";
}
function recordTitle(record) {
const value = valueOf(record);
return value.title ?? value.action ?? value.status ?? KIND_LABELS[record.kind] ?? "record";
}
function recordText(record) {
const value = valueOf(record);
return (
value.summary ??
value.text ??
value.purpose ??
value.body ??
value.action ??
value.method ??
JSON.stringify(value, null, 2)
);
}
function truncate(text, max = 260) {
const clean = String(text ?? "").trim();
if (clean.length <= max) return clean;
return `${clean.slice(0, max).trim()}…`;
}
function shortUri(uri) {
return String(uri ?? "").split("/").at(-1) || uri;
}
function allRecords(data) {
const records = [];
for (const room of data.rooms ?? []) records.push(...(room.timeline ?? []));
records.push(...(data.recent ?? []));
const byUri = new Map();
for (const record of records) byUri.set(record.uri, record);
return [...byUri.values()].sort((a, b) => new Date(b.createdAt ?? 0) - new Date(a.createdAt ?? 0));
}
function filteredRecords(data) {
const needle = query.toLowerCase();
return allRecords(data).filter((record) => {
const room = recordRoom(record);
const haystack = `${room} ${record.kind} ${recordTitle(record)} ${recordText(record)} ${record.uri}`.toLowerCase();
return (
(selectedRoom === "all" || room === selectedRoom) &&
(selectedKind === "all" || record.kind === selectedKind) &&
(!needle || haystack.includes(needle))
);
});
}
function totalRecords(counts = {}) {
return Object.values(counts).reduce((sum, value) => sum + Number(value ?? 0), 0);
}
function latestState(data) {
return (data.rooms ?? [])
.map((room) => room.latestState)
.filter(Boolean)
.sort((a, b) => new Date(b.createdAt ?? 0) - new Date(a.createdAt ?? 0))[0];
}
function routeLabel(item) {
return { now: "Now", rooms: "Rooms", records: "Records", route: "Signal", proof: "Proof" }[item];
}
function shell(content) {
return `
${content}
`;
}
function introPanel(data) {
const state = latestState(data);
return `
${escapeHtml(data.namespace)}
Void is working in public.
The site answers three questions: what changed, where it belongs, and how a person can route a signal into the system.
current state
${escapeHtml(state ? ROOM_LABELS[recordRoom(state)] ?? recordRoom(state) : "Void")}
${escapeHtml(state ? truncate(recordText(state), 240) : "No state record loaded yet.")}
${escapeHtml(state ? formatDate(state.createdAt, "long") : formatDate(data.generatedAt, "long"))}
`;
}
function nowView(data) {
const records = allRecords(data).slice(0, 7);
return shell(`${introPanel(data)}
${(data.rooms ?? []).map(roomSummaryCard).join("")}
latest changes Copy summary
${records.map(recordCard).join("")}
${openQuestionsPanel(data)}
`);
}
function roomSummaryCard(room) {
const latest = room.latestState?.value ?? {};
const counts = room.counts ?? {};
return `
${escapeHtml(ROOM_LABELS[room.name] ?? room.name)}
${escapeHtml(room.name)}
${escapeHtml(truncate(latest.currentTask ?? latest.summary ?? room.purpose, 150))}
states ${counts.states ?? 0}
actions ${counts.actions ?? 0}
artifacts ${counts.artifacts ?? 0}
`;
}
function openQuestionsPanel(data) {
const blockers = (data.blockers ?? []).slice(0, 5);
return `
needs attention ${blockers.length}
${blockers.map((item) => `${escapeHtml(item.room)} ${escapeHtml(item.text)}
`).join("")}
Rule The website can help people understand and draft signals. It does not write canonical state directly.
`;
}
function roomsView(data) {
return shell(`
rooms
Three places for work to land.
Each room has a purpose, a latest state, and a trail of canonical records.
${(data.rooms ?? []).map(roomDetailCard).join("")} `);
}
function roomDetailCard(room) {
const latest = room.latestState?.value ?? {};
const counts = room.counts ?? {};
return `
${escapeHtml(ROOM_LABELS[room.name] ?? room.name)} ${escapeHtml(room.name)}
${escapeHtml(room.displayName ?? room.name)}
${escapeHtml(room.purpose)}
${counts.states ?? 0} states ${counts.actions ?? 0} actions ${counts.submissions ?? 0} submissions ${counts.artifacts ?? 0} artifacts
Latest state ${escapeHtml(latest.summary ?? "No state yet.")}
View this room's records
`;
}
function filtersHtml(data) {
const kinds = Object.keys(data.counts ?? {}).filter((kind) => kind !== "provenance");
return ``;
}
function recordsView(data) {
const records = filteredRecords(data);
return shell(`
records
Canonical trail.
${totalRecords(data.counts)} records loaded from the public room projection.
${filtersHtml(data)}
${records.length ? records.map(recordCard).join("") : `No records match this filter. `} `);
}
function recordCard(record) {
const room = recordRoom(record);
return `
${escapeHtml(KIND_LABELS[record.kind] ?? record.kind)} ${escapeHtml(room)} ${escapeHtml(formatDate(record.createdAt))}
${escapeHtml(recordTitle(record))}
${escapeHtml(truncate(recordText(record)))}
Copy AT URI
Room
${escapeHtml(shortUri(record.uri))}
`;
}
function routeView(data) {
return shell(`
signal
Route something into Void.
This page prepares a packet. A human or agent still accepts it before it becomes a canonical ATProto record.
`);
}
function proofView(data) {
const proofHash = "a8935646005b8e51d827a7e44981dc173ccd8a38b7948cc0cfc5e74bfdf4094a";
return shell(`
proof
Saveable state.
The standalone proof bundle embeds the generated room projection. It is an artifact, not the primary reading experience.
Generated ${escapeHtml(formatDate(data.generatedAt, "long"))}
PDS ${escapeHtml(data.pdsHost)}
Records ${totalRecords(data.counts)}
`);
}
function render() {
if (!model) return;
const view = { now: nowView, rooms: roomsView, records: recordsView, route: routeView, proof: proofView }[route] ?? nowView;
app.innerHTML = view(model);
bindInteractions();
}
function bindInteractions() {
app.querySelectorAll("[data-route]").forEach((element) => {
element.addEventListener("click", (event) => {
const next = element.getAttribute("data-route");
if (ROUTES.includes(next)) {
event.preventDefault();
goto(next);
}
});
});
app.querySelectorAll("[data-room]").forEach((button) => {
button.addEventListener("click", () => { selectedRoom = button.getAttribute("data-room"); render(); });
});
app.querySelectorAll("[data-kind]").forEach((button) => {
button.addEventListener("click", () => { selectedKind = button.getAttribute("data-kind"); render(); });
});
app.querySelector("[data-search]")?.addEventListener("input", (event) => {
const cursor = event.target.selectionStart;
query = event.target.value;
render();
const nextInput = app.querySelector("[data-search]");
nextInput?.focus();
nextInput?.setSelectionRange(cursor, cursor);
});
app.querySelectorAll("[data-room-shortcut]").forEach((button) => {
button.addEventListener("click", () => {
selectedRoom = button.getAttribute("data-room-shortcut");
selectedKind = "all";
query = "";
goto("records");
});
});
app.querySelectorAll("[data-copy]").forEach((button) => {
button.addEventListener("click", async () => { await copyText(button.getAttribute("data-copy")); pulse(button, "Copied"); });
});
app.querySelector("[data-action='copy-feed']")?.addEventListener("click", async (event) => {
const records = allRecords(model).slice(0, 6);
const summary = records.map((record) => `- [${recordRoom(record)} / ${KIND_LABELS[record.kind] ?? record.kind}] ${recordTitle(record)} — ${shortUri(record.uri)}`).join("\n");
await copyText(`Void latest records\n${summary}`);
pulse(event.currentTarget, "Copied");
});
app.querySelector("[data-routing-form]")?.addEventListener("submit", async (event) => {
event.preventDefault();
const form = new FormData(event.currentTarget);
const packet = [
"Void routing packet",
`room: ${form.get("room")}`,
`kind: ${form.get("kind")}`,
`source: website:${location.href}`,
"status: proposed",
"text:",
String(form.get("text") ?? "").trim() || "[empty]",
].join("\n");
await copyText(packet);
app.querySelector("[data-route-output]").textContent = "Copied. Paste this into an ingress surface for review/canonicalization.";
});
}
async function copyText(text) {
if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(text);
const area = document.createElement("textarea");
area.value = text;
document.body.append(area);
area.select();
document.execCommand("copy");
area.remove();
}
function pulse(button, label) {
const original = button.textContent;
button.textContent = label;
button.disabled = true;
setTimeout(() => { button.textContent = original; button.disabled = false; }, 1000);
}
async function load() {
try {
const response = await fetch("./rooms/data.json", { cache: "no-store" });
if (!response.ok) throw new Error(`rooms/data.json returned HTTP ${response.status}`);
model = await response.json();
route = parseRoute();
render();
} catch (error) {
app.innerHTML = `void.tngl.io
Could not load Void. ${escapeHtml(error.message)}
`;
}
}
window.addEventListener("hashchange", () => { route = parseRoute(); render(); });
load();