From f328c7bb513ca6487b34d0543e822d5c4107e938 Mon Sep 17 00:00:00 2001 From: ColtenOuO Date: Mon, 14 Sep 2026 08:38:26 +0000 Subject: [PATCH] Keep grounding choices when the other file loads Every upload rebuilt all three snippet lists from scratch, so loading a resume unchecked JD requirements the candidate had already picked, and nothing on the page said so. Only the groups a file replaces lose their selection now, since those indexes point into a list that no longer exists. --- tests/browser/document-grounding.test.js | 10 +++++++++- tests/browser/lobby.test.js | 16 ++++++++++++++++ web/app.js | 16 +++++++++++----- web/document-grounding.js | 9 +++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/tests/browser/document-grounding.test.js b/tests/browser/document-grounding.test.js index 93e6e8b3..85b7ac6f 100644 --- a/tests/browser/document-grounding.test.js +++ b/tests/browser/document-grounding.test.js @@ -2,7 +2,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import { consumeGroundingPacket, groundingStorageKey, maxGroundingFileBytes, maxGroundingPacketBytes, - groundingConsentVersion, parseGroundingFile, selectedGroundingPacket, storeGroundingPacket, + groundingConsentVersion, parseGroundingFile, retainedSelection, selectedGroundingPacket, storeGroundingPacket, } from "../../web/document-grounding.js"; import { memoryStorage } from "./source.js"; @@ -149,3 +149,11 @@ test("selection indexes outside the extracted list are dropped, not clamped", as // Order follows the indexes as given, not the document. assert.deepEqual(pickWith([2, 0]), ["Must have Go", "Must have Rust"]); }); + +test("re-reading one document keeps the selection made in the other", () => { + const selected = { requirements: [0, 2], skills: [1], anchors: [0] }; + assert.deepEqual(retainedSelection(selected, "resume"), { requirements: [0, 2], skills: [], anchors: [] }); + assert.deepEqual(retainedSelection(selected, "jd"), { requirements: [], skills: [1], anchors: [0] }); + retainedSelection(selected, "jd").skills.push(5); + assert.deepEqual(selected.skills, [1]); +}); diff --git a/tests/browser/lobby.test.js b/tests/browser/lobby.test.js index 8d729cce..42f47379 100644 --- a/tests/browser/lobby.test.js +++ b/tests/browser/lobby.test.js @@ -378,6 +378,22 @@ lobbyTest("a candidate explicitly chooses whether to share the practice focus", ); }); +lobbyTest("loading a resume keeps the JD requirements already checked", async (page) => { + await lobby(page); + await page.click("details.interview-context summary"); + const txt = (name, text) => ({ name, mimeType: "text/plain", buffer: Buffer.from(text) }); + + await page.setInputFiles("#grounding-jd", txt("jd.txt", "Must know Rust\nMust know SQL")); + const jd = page.locator('#grounding-choices input[data-group="requirements"]'); + await jd.first().waitFor(); + await jd.nth(1).check(); + + await page.setInputFiles("#grounding-resume", txt("resume.txt", "Skills: Rust, Go\nBuilt a parser")); + await page.locator('#grounding-choices input[data-group="skills"]').first().waitFor(); + + assert.deepEqual(await jd.evaluateAll((boxes) => boxes.map((box) => box.checked)), [false, true]); +}); + lobbyTest("a manually selected problem still receives the arriving practice focus", async (page) => { reports = [focusedAttempt(EASY[0])]; const release = await heldLobby(page); diff --git a/web/app.js b/web/app.js index d33f87c8..2361c0f0 100644 --- a/web/app.js +++ b/web/app.js @@ -3,7 +3,7 @@ import { clearReportHistory, readLocalHistory, renameLocalHistory } from "./hist import { pickProblem, practiceFocus, storeSharedFocus, suggestDifficulty } from "./problem-picker.js"; import { buildProgressModel, pickerEntry } from "./progress.js"; import { loadPageMap } from "./problem-data.js"; -import { parseGroundingFile, selectedGroundingPacket, storeGroundingPacket } from "./document-grounding.js"; +import { parseGroundingFile, retainedSelection, selectedGroundingPacket, storeGroundingPacket } from "./document-grounding.js"; let problem; let duration; @@ -220,8 +220,7 @@ start.addEventListener("click", async () => { if (profile.seniority) destination.searchParams.set("seniority", profile.seniority); if (profile.targetCompany) destination.searchParams.set("company", profile.targetCompany); const focus = nodes.practiceFocusShareInput.checked ? practiceFocus(reports) : null; - const selected = { requirements: [], skills: [], anchors: [] }; - for (const input of nodes.groundingChoices.querySelectorAll("input:checked")) selected[input.dataset.group].push(Number(input.value)); + const selected = checkedGrounding(); const consented = nodes.groundingConsent.checked; starting = true; @@ -271,10 +270,16 @@ async function loadGroundingFile(kind) { else { grounding.skills = []; grounding.anchors = []; } status.textContent = error.message; } - renderGroundingChoices(); + renderGroundingChoices(retainedSelection(checkedGrounding(), kind)); +} + +function checkedGrounding() { + const selected = { requirements: [], skills: [], anchors: [] }; + for (const input of nodes.groundingChoices.querySelectorAll("input:checked")) selected[input.dataset.group].push(Number(input.value)); + return selected; } -function renderGroundingChoices() { +function renderGroundingChoices(selected) { nodes.groundingChoices.replaceChildren(); for (const [group, label] of [["requirements", "JD requirements"], ["skills", "Resume skills"], ["anchors", "Resume experience/project anchors"]]) { if (!grounding[group].length) continue; @@ -288,6 +293,7 @@ function renderGroundingChoices() { checkbox.type = "checkbox"; checkbox.dataset.group = group; checkbox.value = String(index); + checkbox.checked = selected[group].includes(index); row.append(checkbox, document.createTextNode(` ${snippet}`)); fieldset.append(row); }); diff --git a/web/document-grounding.js b/web/document-grounding.js index 46428d41..372ea2a9 100644 --- a/web/document-grounding.js +++ b/web/document-grounding.js @@ -27,6 +27,15 @@ export async function parseGroundingFile(file, kind) { return kind === "jd" ? parseJd(lines) : parseResume(lines); } +export function retainedSelection(selected, kind) { + const replaced = kind === "jd" ? ["requirements"] : ["skills", "anchors"]; + const retained = { requirements: [], skills: [], anchors: [] }; + for (const group of Object.keys(retained)) { + if (!replaced.includes(group)) retained[group] = [...(selected[group] || [])]; + } + return retained; +} + export function selectedGroundingPacket(extracted, selected, consent) { const packet = { consentVersion: groundingConsentVersion,