Download src/render.js from abhid1234/branchjam: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/spaces/abhid1234/branchjam/resolve/main/src/render.js
- Command line
-
hf download hf://spaces/abhid1234/branchjam/src/render.js
-
curl -L -o render.js https://huggingface.co/spaces/abhid1234/branchjam/resolve/main/src/render.js
11.2 kB
| import { constraintsFor, getArtifact, getConstraint } from "./catalog.js"; | |
| import { historyPreorder, MAX_VOTES_PER_OUTCOME } from "./domain.js"; | |
| const button = (label, className, action, dataset = {}) => { | |
| const element = document.createElement("button"); | |
| element.type = "button"; | |
| element.className = className; | |
| element.textContent = label; | |
| Object.assign(element.dataset, dataset); | |
| element.addEventListener("click", action); | |
| return element; | |
| }; | |
| const setFocusKey = (element, key) => { | |
| element.dataset.focusKey = key; | |
| return element; | |
| }; | |
| export function voteCountLabel(count) { | |
| return `${count} ${count === 1 ? "vote" : "votes"}`; | |
| } | |
| export function historyNodeMetadata(node, artifactTitle, parentTitle, isLineage, selected) { | |
| const status = node.status === "not-selected" | |
| ? "Non-selected sibling" | |
| : node.status[0].toUpperCase() + node.status.slice(1); | |
| const labels = [ | |
| node.depth === 0 ? "Seed origin" : `Round ${node.depth}`, | |
| node.depth === 0 ? null : status, | |
| isLineage ? "Active lineage" : null, | |
| selected ? "Currently inspected" : null | |
| ].filter(Boolean); | |
| const relationship = node.depth === 0 ? "no parent" : `parent ${parentTitle}`; | |
| return { | |
| accessibleName: `${artifactTitle}, ${labels[0]}, ${relationship}${labels.slice(1).map((label) => `, ${label.toLowerCase()}`).join("")}`, | |
| visibleText: labels.join(" · ") | |
| }; | |
| } | |
| export function renderRound(container, state, source, actions) { | |
| container.replaceChildren(); | |
| const phase = document.createElement("p"); | |
| phase.className = "phase-status"; | |
| phase.textContent = state.phase === "choose" | |
| ? "Current phase: choose a constraint. Next: reveal two fixed outcomes." | |
| : state.phase === "compare" | |
| ? "Current phase: compare both outcomes. Next: record at least one vote." | |
| : state.phase === "vote" | |
| ? "Current phase: operator records participant preferences. Next: resolve the vote." | |
| : state.phase === "resolved" | |
| ? "Current phase: decision recorded. Next: continue from the winner." | |
| : "Current phase: branch complete. Next: explore any runnable history node."; | |
| container.append(phase); | |
| const kicker = document.createElement("p"); | |
| kicker.className = "section-kicker"; | |
| kicker.textContent = state.phase === "choose" ? "Shape the next fork" : `Round ${Object.keys(state.roundsById).length}`; | |
| container.append(kicker); | |
| if (state.phase === "choose") { | |
| const heading = document.createElement("h2"); | |
| heading.id = "round-heading"; | |
| heading.tabIndex = -1; | |
| heading.textContent = state.activeNodeId === state.seedNodeId ? "Choose a constraint" : "Continue the winning branch"; | |
| container.append(heading); | |
| const intro = document.createElement("p"); | |
| intro.textContent = "Select one curated rule, then reveal its two fixed interpretations."; | |
| container.append(intro); | |
| const group = document.createElement("fieldset"); | |
| const legend = document.createElement("legend"); | |
| legend.textContent = "Available constraints"; | |
| group.append(legend); | |
| for (const constraint of constraintsFor(source, state.nodesById[state.activeNodeId].artifactId)) { | |
| const label = document.createElement("label"); | |
| label.className = "constraint-card"; | |
| const radio = document.createElement("input"); | |
| radio.type = "radio"; | |
| radio.name = "constraint"; | |
| radio.value = constraint.id; | |
| setFocusKey(radio, `constraint-${constraint.id}`); | |
| radio.checked = state.selectedConstraintId === constraint.id; | |
| radio.addEventListener("change", () => actions.selectConstraint(constraint.id)); | |
| const copy = document.createElement("span"); | |
| const name = document.createElement("strong"); | |
| name.textContent = constraint.title; | |
| const detail = document.createElement("small"); | |
| detail.textContent = constraint.description; | |
| copy.append(name, detail); | |
| label.append(radio, copy); | |
| group.append(label); | |
| } | |
| container.append(group); | |
| const reveal = button("Reveal outcomes", "primary-button", actions.reveal); | |
| setFocusKey(reveal, "reveal-outcomes"); | |
| reveal.disabled = !state.selectedConstraintId; | |
| container.append(reveal); | |
| return; | |
| } | |
| if (state.phase === "complete") { | |
| const heading = document.createElement("h2"); | |
| heading.id = "round-heading"; | |
| heading.tabIndex = -1; | |
| heading.textContent = "Branch complete"; | |
| container.append(heading); | |
| const end = document.createElement("p"); | |
| end.className = "end-card"; | |
| end.textContent = "This pre-authored branch ends here in the local MVP."; | |
| container.append(end); | |
| return; | |
| } | |
| const round = state.roundsById[state.currentRoundId]; | |
| const recipe = source.recipes.find((item) => item.id === round.recipeId); | |
| const constraint = getConstraint(source, recipe.constraintId); | |
| const heading = document.createElement("h2"); | |
| heading.id = state.phase === "resolved" ? "result-heading" : "comparison-heading"; | |
| heading.tabIndex = -1; | |
| heading.textContent = state.phase === "resolved" ? "The branch is decided" : `Compare: ${constraint.title}`; | |
| container.append(heading); | |
| const local = document.createElement("p"); | |
| local.className = "context-disclosure"; | |
| local.textContent = "These two outcomes were selected from a pre-authored local catalog for this artifact and constraint. The same choices always produce the same results."; | |
| container.append(local); | |
| const cards = document.createElement("div"); | |
| cards.className = "outcome-grid"; | |
| recipe.outcomes.forEach((outcome) => { | |
| const artifact = getArtifact(source, outcome.artifactId); | |
| const card = document.createElement("article"); | |
| const selected = state.compareSelection === outcome.slot; | |
| card.className = `outcome-card branch-${outcome.slot}${selected ? " is-selected" : ""}`; | |
| const title = document.createElement("h3"); | |
| title.textContent = `${outcome.slot.toUpperCase()} — ${artifact.title}`; | |
| const summary = document.createElement("p"); | |
| summary.textContent = artifact.summary; | |
| const divergence = document.createElement("p"); | |
| divergence.className = "divergence-note"; | |
| const divergenceLabel = document.createElement("strong"); | |
| divergenceLabel.textContent = "Meaningful split: "; | |
| divergence.append(divergenceLabel); | |
| divergence.append(outcome.divergenceNote); | |
| const play = button(selected ? "Playing this outcome" : "Play this outcome", "compare-button", () => actions.compare(outcome.slot)); | |
| setFocusKey(play, `compare-${outcome.slot}`); | |
| play.setAttribute("aria-pressed", String(selected)); | |
| if (selected) { | |
| play.disabled = true; | |
| } | |
| card.append(title, summary, divergence, play); | |
| cards.append(card); | |
| }); | |
| container.append(cards); | |
| if (state.phase === "resolved") { | |
| const winnerArtifact = getArtifact(source, state.nodesById[state.activeNodeId].artifactId); | |
| const result = document.createElement("p"); | |
| result.className = "result-card"; | |
| result.textContent = `${round.winnerSlot.toUpperCase()} wins: ${winnerArtifact.title}. ${round.resolution === "authored-tie-rule" ? "The displayed authored tie rule decided this round." : "The higher recorded vote total decided this round."}`; | |
| const continueButton = button("Continue from winner", "primary-button", actions.continue); | |
| setFocusKey(continueButton, "continue-winner"); | |
| container.append(result, continueButton); | |
| return; | |
| } | |
| const voting = document.createElement("section"); | |
| voting.className = "voting"; | |
| voting.setAttribute("aria-labelledby", "voting-heading"); | |
| const voteHeading = document.createElement("h3"); | |
| voteHeading.id = "voting-heading"; | |
| voteHeading.textContent = "Operator-recorded voting"; | |
| const disclosure = document.createElement("p"); | |
| disclosure.className = "vote-disclosure"; | |
| disclosure.textContent = "Votes are entered by the local operator in this browser on this device. Participant devices are not connected."; | |
| const tie = document.createElement("p"); | |
| tie.textContent = `Tie rule, shown before resolution: outcome ${recipe.tieWinnerSlot.toUpperCase()} wins an equal total.`; | |
| voting.append(voteHeading, disclosure, tie); | |
| for (const slot of ["a", "b"]) { | |
| const row = document.createElement("div"); | |
| row.className = "vote-row"; | |
| const name = document.createElement("strong"); | |
| name.textContent = `Outcome ${slot.toUpperCase()}: ${voteCountLabel(round.votes[slot])}`; | |
| const subtract = button("−", "vote-button", () => { | |
| if (round.votes[slot] > 0) actions.vote(slot, -1); | |
| }); | |
| setFocusKey(subtract, `vote-${slot}-subtract`); | |
| subtract.setAttribute("aria-label", `Remove one vote from outcome ${slot.toUpperCase()}`); | |
| subtract.disabled = round.votes[slot] === 0; | |
| const add = button("+", "vote-button", () => actions.vote(slot, 1)); | |
| setFocusKey(add, `vote-${slot}-add`); | |
| add.setAttribute("aria-label", `Add one vote to outcome ${slot.toUpperCase()}`); | |
| add.disabled = round.votes[slot] >= MAX_VOTES_PER_OUTCOME; | |
| row.append(name, subtract, add); | |
| voting.append(row); | |
| } | |
| const resolve = button("Resolve vote", "primary-button", actions.resolve); | |
| setFocusKey(resolve, "resolve-vote"); | |
| resolve.disabled = state.phase !== "vote"; | |
| voting.append(resolve); | |
| container.append(voting); | |
| } | |
| export function renderHistory(container, state, source, inspect) { | |
| const nodes = historyPreorder(state); | |
| const activeLineage = new Set(); | |
| for (let id = state.activeNodeId; id; id = state.nodesById[id].parentNodeId) activeLineage.add(id); | |
| const children = new Map(); | |
| for (const node of nodes) { | |
| const key = node.parentNodeId ?? "root"; | |
| children.set(key, [...(children.get(key) ?? []), node]); | |
| } | |
| const build = (parent) => { | |
| const list = document.createElement("ul"); | |
| for (const node of children.get(parent) ?? []) { | |
| const item = document.createElement("li"); | |
| const selected = state.inspectedNodeId === node.nodeId; | |
| item.className = `history-node status-${node.status}${activeLineage.has(node.nodeId) ? " is-lineage" : ""}${selected ? " is-inspected" : ""}`; | |
| const artifact = getArtifact(source, node.artifactId); | |
| const parentTitle = node.parentNodeId ? getArtifact(source, state.nodesById[node.parentNodeId].artifactId).title : "none"; | |
| const control = button(artifact.title, "history-button", () => inspect(node.nodeId)); | |
| setFocusKey(control, `history-${node.nodeId}`); | |
| const metadata = historyNodeMetadata(node, artifact.title, parentTitle, activeLineage.has(node.nodeId), selected); | |
| control.setAttribute("aria-label", metadata.accessibleName); | |
| if (selected) control.setAttribute("aria-current", "true"); | |
| control.setAttribute("aria-pressed", String(selected)); | |
| const row = document.createElement("div"); | |
| row.className = "history-node-row"; | |
| const meta = document.createElement("span"); | |
| meta.className = "node-meta"; | |
| meta.textContent = metadata.visibleText; | |
| row.append(control, meta); | |
| item.append(row); | |
| if (children.has(node.nodeId)) item.append(build(node.nodeId)); | |
| list.append(item); | |
| } | |
| return list; | |
| }; | |
| container.replaceChildren(build("root")); | |
| } | |