branchjam / src /app.js
abhid1234's picture
Upload folder using huggingface_hub
2fe29a7 verified
Raw
History Blame Contribute Delete
5.13 kB
import { catalog, getArtifact, validateCatalog } from "./catalog.js";
import {
createSession, selectConstraint, revealOutcomes, selectComparison,
incrementVote, decrementVote, resolveVote, continueFromWinner, inspectNode
} from "./domain.js";
import { createPersistence } from "./persistence.js";
import { mountSignalGarden } from "./artifact.js";
import { renderRound, renderHistory } from "./render.js";
import { focusControl } from "./focus.js";
const validation = validateCatalog(catalog);
if (!validation.valid) throw new Error(`Catalog validation failed: ${validation.errors.join(" ")}`);
const artifactView = document.querySelector("#artifact-view");
const roundView = document.querySelector("#round-view");
const historyView = document.querySelector("#history-view");
const polite = document.querySelector("#polite-status");
const assertive = document.querySelector("#assertive-status");
const storageSummary = document.querySelector("#storage-summary");
const resetSession = document.querySelector("#reset-session");
const resetConfirmation = document.querySelector("#reset-confirmation");
const cancelReset = document.querySelector("#cancel-reset");
const confirmReset = document.querySelector("#confirm-reset");
let browserStorage;
try {
browserStorage = window.localStorage;
} catch {
browserStorage = {
getItem() { throw new Error("Storage unavailable"); },
setItem() { throw new Error("Storage unavailable"); },
removeItem() { throw new Error("Storage unavailable"); }
};
}
const persistence = createPersistence(browserStorage, catalog);
const loaded = persistence.load();
let state = loaded.state ?? createSession(catalog);
let runtime = null;
if (loaded.notice) storageSummary.textContent = loaded.notice;
if (loaded.recovered) announce(loaded.notice, true);
function announce(message, urgent = false) {
const region = urgent ? assertive : polite;
region.textContent = "";
requestAnimationFrame(() => { region.textContent = message; });
}
function focusAfterRender(selector) {
requestAnimationFrame(() => document.querySelector(selector)?.focus());
}
function focusedControlKey() {
const key = document.activeElement?.dataset.focusKey;
return typeof key === "string" ? key : null;
}
function restoreControlFocus(key, fallbackKey = null) {
if (!key) return;
requestAnimationFrame(() => focusControl(document, key, fallbackKey));
}
function commit(next, message = "", preserveFocus = true, fallbackFocusKey = null) {
const focusKey = preserveFocus ? focusedControlKey() : null;
state = next;
const result = persistence.save(state);
storageSummary.textContent = result.notice;
render();
restoreControlFocus(focusKey, fallbackFocusKey);
if (message) announce(message);
}
function render() {
runtime?.destroy();
runtime = mountSignalGarden(artifactView, getArtifact(catalog, state.nodesById[state.inspectedNodeId].artifactId));
renderRound(roundView, state, catalog, {
selectConstraint: (id) => commit(selectConstraint(state, catalog, id)),
reveal: () => {
commit(revealOutcomes(state, catalog), "Two pre-authored outcomes are ready to compare.", false);
focusAfterRender("#comparison-heading");
},
compare: (slot) => commit(selectComparison(state, slot), "", true, "artifact-heading"),
vote: (slot, change) => {
const next = change > 0 ? incrementVote(state, slot) : decrementVote(state, slot);
const total = next.roundsById[next.currentRoundId].votes[slot];
const fallback = change < 0 && total === 0 ? `vote-${slot}-add` : null;
commit(next, `Outcome ${slot.toUpperCase()} now has ${total} votes.`, true, fallback);
},
resolve: () => {
const next = resolveVote(state, catalog);
commit(next, `Outcome ${next.roundsById[next.currentRoundId].winnerSlot.toUpperCase()} won the round.`, false);
focusAfterRender("#result-heading");
},
continue: () => {
const next = continueFromWinner(state, catalog);
commit(next, next.phase === "complete" ? "This pre-authored branch is complete." : "", false);
focusAfterRender("#round-heading");
}
});
renderHistory(historyView, state, catalog, (nodeId) => commit(inspectNode(state, nodeId)));
}
function closeResetConfirmation({ restoreFocus = true } = {}) {
resetConfirmation.hidden = true;
resetSession.setAttribute("aria-expanded", "false");
if (restoreFocus) resetSession.focus();
}
resetSession.addEventListener("click", () => {
resetConfirmation.hidden = false;
resetSession.setAttribute("aria-expanded", "true");
cancelReset.focus();
});
cancelReset.addEventListener("click", () => closeResetConfirmation());
resetConfirmation.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
event.preventDefault();
closeResetConfirmation();
});
confirmReset.addEventListener("click", () => {
closeResetConfirmation({ restoreFocus: false });
runtime?.destroy();
const result = persistence.reset();
state = result.state;
storageSummary.textContent = result.notice;
render();
focusAfterRender("#play-heading");
announce(result.notice);
});
render();