Use when creating one or more missing assignments in an Aeries gradebook after gb-compare identified gaps, especially when Stage 2 of gb-pipeline needs a confirmation-gated, idempotent assignment-creation pass.
Create missing Aeries assignments from validated comparison results without duplicating records. This skill reads the Stage 1 gb-compare temp file (or a manual fallback list), previews exactly what will be created vs. skipped, waits for explicit user approval, then fills the Aeries Add Assignment dialog with the correct MOM-derived values and writes a Stage 2 status file for the pipeline.
Prerequisites
Playwriter connected to a Chrome tab already on the correct Aeries gradebook page
Teacher write access in Aeries
Stage 1 comparison data available from gb-compare or a manually curated assignment list
User available to approve the final create/skip preview before any writes
When to Use
User says "add these to Aeries", "create the missing assignments", or similar
gb-compare already identified a missing assignment list for the same Aeries gradebook
関連 Skill
Stage 2 of gb-pipeline needs to create missing assignments before score sync
When NOT to Use
Read-only comparison work — use gb-compare
Score entry or score reconciliation — use gb-sync
Editing, deleting, or bulk-maintaining assignments that already exist in Aeries
Any run where the assignment list has not been validated yet
Guardrails
⚠️ Must NOT:
Never create assignments until you show the proposed create list and skip list and get explicit user approval.
Never create duplicates. Always scan existing Aeries assignments first and skip anything already present.
Never edit or delete an existing Aeries assignment in this workflow.
Never hardcode category indexes. Aeries category options are course-specific and must be matched by displayed name each run.
Never change MOM-derived category or points values during entry. Preserve the category exactly and map MOM pts into Aeries #Assignment_MaxScore.
Never navigate away from the Aeries gradebook page except for in-place Aeries dialogs already opened from that page.
Never trust a save without verification. Confirm the modal state after each save and run a post-batch verification pass before writing the completion temp file.
Never overwrite the Stage 2 temp file with false success. Record created, skipped, and failed assignments separately.
Quick Start
Load the missing assignments from the Stage 1 gb_compare_{gradebookNum}.json file.
Scan current Aeries assignments, build the create/skip preview, and get explicit user approval.
Create only the remaining assignments, verify them, then write gb_new_assignment_{gradebookNum}.json.
Workflow
Phase 1: Attach to the Correct Aeries Gradebook
INPUT: Open browser tabs managed by Playwriter
ACTION:
Find the Aeries page whose URL contains both aeries and gradebook.
Confirm the page responds before doing any extraction.
Extract gradebookNum from the URL for temp-file naming.
OUTPUT: Live state.aeries page plus gradebookNum for Stage 2 input/output.
const aeriesPage = context.pages().find(p =>
p.url().includes('aeries') && p.url().toLowerCase().includes('gradebook')
);
if (!aeriesPage) throw new Error('No Aeries Gradebook tab found — open it first.');
await aeriesPage.evaluate(() => document.title);
state.aeries = aeriesPage;
const gradebookNum = state.aeries.url().match(/gradebook\/(\d+)/)?.[1] ?? 'unknown';
Phase 2: Load the Stage 1 Input File or Manual Fallback
INPUT:gradebookNum and optional user-provided assignments
Show the user a preview containing both lists and wait for explicit approval.
If toCreate.length === 0, stop safely and still write a completion file showing only skips.
OUTPUT: Approved toCreate list and a recorded skippedExisting list.
function normalizeName(s) {
return String(s ?? '').toLowerCase().replace(/\s+/g, ' ').trim();
}
const existingNames = await state.aeries.evaluate(() => {
return [...document.querySelectorAll('th[data-an]')].map(th => {
const full = th.querySelector('a.assignment-edit')?.getAttribute('data-assignment-name');
return (full || th.textContent || '').trim();
});
});
const existingSet = new Set(existingNames.map(normalizeName));
const skippedExisting = [];
const toCreate = [];
for (const assignment of assignments) {
if (existingSet.has(normalizeName(assignment.name))) {
skippedExisting.push(assignment);
} else {
toCreate.push(assignment);
}
}
console.log(JSON.stringify({
toCreate: toCreate.map(a => ({ name: a.name, category: a.category, pts: a.pts })),
skippedExisting: skippedExisting.map(a => ({ name: a.name, category: a.category, pts: a.pts })),
}, null, 2));
// STOP here and get explicit user approval before opening the add form.
Phase 4: Open the Add Assignment Dialog
INPUT: Approved toCreate list
ACTION:
On the first item only, click #subHeaderAddAssignment.
Do not wait for navigation; this opens a Kendo modal in place.
Wait briefly, then verify #Assignment_Description exists before continuing.
On later items, reuse the blank form opened by Save and Add New.
OUTPUT: Ready Aeries assignment form.
if (i === 0) {
await state.aeries.locator('#subHeaderAddAssignment').click();
await state.aeries.waitForTimeout(1500);
const isOpen = await state.aeries.evaluate(() => !!document.querySelector('#Assignment_Description'));
if (!isOpen) throw new Error('Add Assignment modal did not open.');
}
Phase 6: Save Each Assignment in the Correct Sequence
INPUT: Populated form and loop position
ACTION:
If more assignments remain, click #assignmentSaveNAdd.
If this is the last assignment (or the only one), click #assignmentSaveNClose.
Remember: these save controls are <span> elements, not accessible buttons.
After each save, verify the expected modal state:
intermediate save: blank form reopened
final save: modal closed
OUTPUT: Saved assignment plus either the next blank form or a closed modal.
if (isLast) {
await state.aeries.locator('#assignmentSaveNClose').click();
await state.aeries.waitForTimeout(3000);
const stillOpen = await state.aeries.evaluate(() => !!document.querySelector('#assignmentSave'));
if (stillOpen) throw new Error('Modal still open after Save — check for validation errors.');
console.log('Saved and closed: "' + a.name + '"');
} else {
await state.aeries.locator('#assignmentSaveNAdd').click();
await state.aeries.waitForTimeout(2000);
const newFormOpen = await state.aeries.evaluate(() => {
const f = document.querySelector('#Assignment_Description');
return f && f.value === '';
});
if (!newFormOpen) throw new Error('New blank form did not open after Save and Add New.');
console.log('Saved, opening next form: "' + a.name + '"');
}
Phase 7: Verify the Batch Before Writing Stage 2 Output
INPUT:toCreate and skippedExisting
ACTION:
Reload the gradebook page after the batch.
Re-scrape assignment names from the page.
Confirm each newly created assignment exists by normalized full-name matching.
Record three lists:
created
skippedExisting
failed
OUTPUT: Verified Stage 2 result set.
await state.aeries.reload({ waitUntil: 'domcontentloaded' });
await state.aeries.waitForTimeout(3000);
const finalNames = await state.aeries.evaluate(() => {
return [...document.querySelectorAll('th[data-an]')].map(th => {
const full = th.querySelector('a.assignment-edit')?.getAttribute('data-assignment-name');
return (full || th.textContent || '').trim();
});
});
const finalSet = new Set(finalNames.map(normalizeName));
const created = [];
const failed = [];
for (const assignment of toCreate) {
if (finalSet.has(normalizeName(assignment.name))) created.push(assignment.name);
else failed.push(assignment.name);
}
Phase 8: Write the Stage 2 Temp File
INPUT: Verified created, skippedExisting, and failed lists
ACTION:
Ensure the temp directory exists.
Write the Stage 2 completion file at:
C:\Users\shuff\grade-cloning\temp\gb_new_assignment_${gradebookNum}.json
Preserve enough data for gb-pipeline to decide whether Stage 2 succeeded and whether a rerun should skip existing items.