diff --git a/modules/rtasks/checklist-routes.ts b/modules/rtasks/checklist-routes.ts index 4b9b4d6..93503da 100644 --- a/modules/rtasks/checklist-routes.ts +++ b/modules/rtasks/checklist-routes.ts @@ -7,7 +7,7 @@ import { Hono } from "hono"; import { checklistConfig } from "./lib/checklist-config"; import { createToken, verifyToken } from "./lib/token"; -import { readTask, checkAC } from "./lib/backlog"; +import { readTask, toggleAC } from "./lib/backlog"; import { renderEmailHTML, renderWebPage, renderError } from "./lib/render"; export const checklistCheckRoutes = new Hono(); @@ -30,17 +30,14 @@ checklistCheckRoutes.get("/:token", async (c) => { } try { - await checkAC(verified.repo, verified.taskId, verified.acIndex); - const task = await readTask(verified.repo, verified.taskId); + const result = await toggleAC(verified.repo, verified.taskId, verified.acIndex); const tokens = new Map(); - for (const ac of task.criteria) { - if (!ac.checked) { - tokens.set(ac.index, await createToken(verified.repo, verified.taskId, ac.index)); - } + for (const ac of result.criteria) { + tokens.set(ac.index, await createToken(verified.repo, verified.taskId, ac.index)); } - return c.html(renderWebPage(task.title, verified.taskId, task.criteria, tokens, verified.acIndex)); + return c.html(renderWebPage(result.title, verified.taskId, result.criteria, tokens, verified.acIndex, result.toggled)); } catch (err) { console.error("[Tasks/checklist] Check error:", err); return c.html(renderError("Error", "Could not update task. Please try again."), 500); diff --git a/modules/rtasks/lib/backlog.ts b/modules/rtasks/lib/backlog.ts index defcf69..58b0eca 100644 --- a/modules/rtasks/lib/backlog.ts +++ b/modules/rtasks/lib/backlog.ts @@ -87,16 +87,23 @@ export async function readTask(repo: string, taskId: string): Promise } /** - * Toggle an AC item to checked. Idempotent: if already checked, no-op. + * Toggle an AC item: unchecked → checked, checked → unchecked. + * Returns the new checked state. */ -export async function checkAC(repo: string, taskId: string, acIndex: number): Promise { +export async function toggleAC(repo: string, taskId: string, acIndex: number): Promise { const filePath = await findTaskFile(repo, taskId); let content = await Bun.file(filePath).text(); const unchecked = `- [ ] #${acIndex} `; const checked = `- [x] #${acIndex} `; + let nowChecked = false; if (content.includes(unchecked)) { content = content.replace(unchecked, checked); + nowChecked = true; + await Bun.write(filePath, content); + } else if (content.includes(checked)) { + content = content.replace(checked, unchecked); + nowChecked = false; await Bun.write(filePath, content); } @@ -105,5 +112,6 @@ export async function checkAC(repo: string, taskId: string, acIndex: number): Pr status: parseStatus(content), criteria: parseAC(content), filePath, + toggled: nowChecked, }; } diff --git a/modules/rtasks/lib/render.ts b/modules/rtasks/lib/render.ts index 87286c7..7048d73 100644 --- a/modules/rtasks/lib/render.ts +++ b/modules/rtasks/lib/render.ts @@ -36,15 +36,15 @@ function progress(criteria: AcceptanceCriterion[]): string { return `
${done} of ${total} complete (${pct}%)
`; } -function checklist(criteria: AcceptanceCriterion[], tokens: Map, justChecked?: number): string { +function checklist(criteria: AcceptanceCriterion[], tokens: Map, justToggled?: number): string { return criteria .map((ac) => { - const hi = ac.index === justChecked ? " hi" : ""; - if (ac.checked) { - return `
#${ac.index} ${esc(ac.text)}
`; - } + const hi = ac.index === justToggled ? " hi" : ""; const token = tokens.get(ac.index); const url = token ? `${checklistConfig.baseUrl}/rtasks/check/${token}` : "#"; + if (ac.checked) { + return `
#${ac.index} ${esc(ac.text)}
`; + } return `
 #${ac.index} ${esc(ac.text)}
`; }) .join("\n"); @@ -71,23 +71,33 @@ export function renderWebPage( taskId: string, criteria: AcceptanceCriterion[], tokens: Map, - justChecked?: number, + justToggled?: number, + wasChecked?: boolean, ): string { - const justAC = justChecked !== undefined ? criteria.find((c) => c.index === justChecked) : undefined; - const banner = justAC - ? `
✓ Checked off: #${justAC.index} ${esc(justAC.text)}
` - : ""; + const justAC = justToggled !== undefined ? criteria.find((c) => c.index === justToggled) : undefined; + let banner = ""; + if (justAC && wasChecked) { + banner = `
✓ Checked off: #${justAC.index} ${esc(justAC.text)}
`; + } else if (justAC && !wasChecked) { + banner = `
↻ Unchecked: #${justAC.index} ${esc(justAC.text)}
`; + } const allDone = criteria.every((c) => c.checked); const doneMsg = allDone ? `
🎉 All items complete!
` : ""; - return `${esc(title)} - rTasks + const rtasksUrl = `${checklistConfig.baseUrl}/demo/rtasks`; + + return `${esc(title)} - rTasks
${banner}

${esc(title)}

${esc(taskId)}
${progress(criteria)} -${checklist(criteria, tokens, justChecked)} +${checklist(criteria, tokens, justToggled)} ${doneMsg} +
rTasks · rspace.online
`; }