const QUIET = ["idle", "exited", "stopped", "orphanTlc"];const going = (v) => v.pc.filter((p) => !QUIET.includes(p)).length;const changed = (before, after) => after.pc.findIndex( (p, i) => p !== before.pc[i] || after.aside[i].exists !== before.aside[i].exists, ) + 1;export const readerActions = ["Start", "Edit", "Note", "Stop", "KillNode"];function context(v) { if (v.pc.every((p) => p === "idle")) return "Before any run"; if (going(v) === 2) return "Two runs going"; return going(v) === 1 ? "One run going" : "Done";}const SPECS = { 1: "the spec", 2: "the edited spec", "1,2": "both specs" };function contents(v) { const kept = v.aside.filter((a) => a.exists); const aside = kept.some((a) => a.notes) ? ", your notes moved aside" : kept.some((a) => a.by) ? ", the old chart moved aside" : ""; const parts = [ v.out.by && `chart of ${SPECS[v.out.read.join()]}`, v.out.notes && "your notes", ].filter(Boolean); if (!v.out.exists) return `no folder${aside}`; return `${parts.join(" and ") || "empty folder"}${aside}`;}export function state(v) { return `${context(v)}: ${contents(v)}`;}export function group(v) { if (v.pc.every((p) => p === "idle")) return "Not started"; return going(v) ? "Running" : "Finished";}export function step(action, before, after) { const r = changed(before, after); switch (action) { case "Start": { const where = going(before) ? " in a second shell" : ""; const how = after.inputOk[r - 1] ? "" : " with a typo"; return `you start run ${r}${where}${how}`; } case "Refuse": return before.inputOk[r - 1] ? `run ${r} says the folder has a file that it did not write` : `run ${r} says the input is wrong`; case "Tlc": return `run ${r}'s TLC reads the spec`; case "Draw": return `run ${r} draws the chart in its own folder`; case "Stale": return `run ${r} says the spec changed while it read it`; case "Foreign": return `run ${r} says the folder has a file that it did not write`; case "Swap": case "Checked": return ""; case "MoveIn": return `run ${r} moves its folder into place`; case "MoveAside": return `run ${r} moves the old folder aside`; case "Restore": return `run ${r} puts the folder with your notes back and stops`; case "Supersede": return `run ${r} finds another run's folder in place and deletes the old folder`; case "KeepAside": return `run ${r} keeps the folder with your notes aside and says where`; case "RenameFails": if (after.aside[r - 1].exists) return `run ${r} cannot rename a folder and says where the old folder is`; return after.out.exists && !before.out.exists ? `run ${r} cannot rename a folder, puts the old folder back, and stops` : `run ${r} cannot rename a folder and stops`; case "Clean": return `run ${r} deletes the old folder and finishes`; case "Edit": return "you edit the spec"; case "Note": return "you save notes in the folder"; case "Stop": return `you stop run ${r} with Ctrl-C`; case "KillNode": return `you kill run ${r}'s Node, and its TLC keeps running`; case "OrphanEnd": return `run ${r}'s orphaned TLC finishes`; default: return action; }}export function cause(v, facts, invariant) { return { ChartsOneSpec: "the run read the spec before and after an edit, and still wrote its chart", KeepsYourFiles: "a run deleted a folder that had your notes in it", NeverLosesChart: "a run deleted the last chart before another chart was in place", ExitTwoLeavesFolder: "a run wrote the folder and then said it could not finish", OverlapNeverRefuses: "a run stopped only because another run overlapped it", }[invariant];}
const QUIET = ["idle", "exited", "stopped", "orphanTlc"];
const going = (v) => v.pc.filter((p) => !QUIET.includes(p)).length;
const changed = (before, after) =>
after.pc.findIndex(
(p, i) => p !== before.pc[i] || after.aside[i].exists !== before.aside[i].exists,
) + 1;
export const readerActions = ["Start", "Edit", "Note", "Stop", "KillNode"];
function context(v) {
if (v.pc.every((p) => p === "idle")) return "Before any run";
if (going(v) === 2) return "Two runs going";
return going(v) === 1 ? "One run going" : "Done";
}
const SPECS = { 1: "the spec", 2: "the edited spec", "1,2": "both specs" };
function contents(v) {
const kept = v.aside.filter((a) => a.exists);
const aside = kept.some((a) => a.notes)
? ", your notes moved aside"
: kept.some((a) => a.by)
? ", the old chart moved aside"
: "";
const parts = [
v.out.by && `chart of ${SPECS[v.out.read.join()]}`,
v.out.notes && "your notes",
].filter(Boolean);
if (!v.out.exists) return `no folder${aside}`;
return `${parts.join(" and ") || "empty folder"}${aside}`;
}
export function state(v) {
return `${context(v)}: ${contents(v)}`;
}
export function group(v) {
if (v.pc.every((p) => p === "idle")) return "Not started";
return going(v) ? "Running" : "Finished";
}
export function step(action, before, after) {
const r = changed(before, after);
switch (action) {
case "Start": {
const where = going(before) ? " in a second shell" : "";
const how = after.inputOk[r - 1] ? "" : " with a typo";
return `you start run ${r}${where}${how}`;
}
case "Refuse":
return before.inputOk[r - 1]
? `run ${r} says the folder has a file that it did not write`
: `run ${r} says the input is wrong`;
case "Tlc":
return `run ${r}'s TLC reads the spec`;
case "Draw":
return `run ${r} draws the chart in its own folder`;
case "Stale":
return `run ${r} says the spec changed while it read it`;
case "Foreign":
return `run ${r} says the folder has a file that it did not write`;
case "Swap":
case "Checked":
return "";
case "MoveIn":
return `run ${r} moves its folder into place`;
case "MoveAside":
return `run ${r} moves the old folder aside`;
case "Restore":
return `run ${r} puts the folder with your notes back and stops`;
case "Supersede":
return `run ${r} finds another run's folder in place and deletes the old folder`;
case "KeepAside":
return `run ${r} keeps the folder with your notes aside and says where`;
case "RenameFails":
if (after.aside[r - 1].exists)
return `run ${r} cannot rename a folder and says where the old folder is`;
return after.out.exists && !before.out.exists
? `run ${r} cannot rename a folder, puts the old folder back, and stops`
: `run ${r} cannot rename a folder and stops`;
case "Clean":
return `run ${r} deletes the old folder and finishes`;
case "Edit":
return "you edit the spec";
case "Note":
return "you save notes in the folder";
case "Stop":
return `you stop run ${r} with Ctrl-C`;
case "KillNode":
return `you kill run ${r}'s Node, and its TLC keeps running`;
case "OrphanEnd":
return `run ${r}'s orphaned TLC finishes`;
default:
return action;
}
}
export function cause(v, facts, invariant) {
return {
ChartsOneSpec: "the run read the spec before and after an edit, and still wrote its chart",
KeepsYourFiles: "a run deleted a folder that had your notes in it",
NeverLosesChart: "a run deleted the last chart before another chart was in place",
ExitTwoLeavesFolder: "a run wrote the folder and then said it could not finish",
OverlapNeverRefuses: "a run stopped only because another run overlapped it",
}[invariant];
}