Which part is affected?
Web app (classroom50.org)
What happened?
Three web writers rebuild <classroom>/assignments.json as "everything except this slug, then the updated entry", so the touched row is moved to the end of the array:
assignments: [
...currentAssignments.assignments.filter((a) => a.slug !== slug),
updatedEntry,
],
The CLI is the other side of this contract and does the opposite, deliberately:
// UpsertAssignment replaces by Slug (case-sensitive: ...).
// Position preserved on replace; new slugs append.
— cli/gh-teacher/internal/assignment/assignments_json.go:766, and gh teacher assignment lock writes file.Assignments[idx] = entry (lock.go:108).
The web app is inconsistent with itself too: renameAssignment already maps in place (rename.ts:292, rename.ts:396) — and says so in a comment that names the lock flip as the counter-example — while deleteAssignment is a plain filter and createAssignment/copyAssignment append genuinely new slugs, which is correct. Only these three move an existing row.
What this causes
-
gh teacher assignment list reorders. The list command prints entries in file order — no sort (assignment.go:442) — and --json emits the array as stored. Locking hw1 in the web app moves it below hw9 in the teacher's terminal, and nothing the teacher did asked for a reorder. The web UI hides this because both its lists sort explicitly (DEFAULT_SORT = "name-asc", students due-asc), so the divergence is invisible from the side that causes it.
-
Every flag flip is a whole-row diff. Toggling locked should be a one-line change in the config repo. Instead the commit deletes the entry from its position and re-adds it at the end — for an entry with a template block, tests, and a due date, that is a ~15-line diff for a one-field change. The config repo is the teacher's audit trail, and git log -p cs50/assignments.json no longer shows what actually changed.
-
Same action, two files. Web app and CLI, given the same starting file and the same lock, commit different content. Comparing two classrooms, or diffing a file across the two tools, has to normalize order first.
Nothing is functionally broken: every reader looks entries up by slug, so no student, accept, or grading path misbehaves. The cost is the teacher-visible ordering in the CLI and the audit trail in the config repo.
(closed has no CLI writer at all — the CLI preserves it as "owned out of band by the web", assignment.go:878 — so for setAssignmentClosed it is points 1 and 2 that apply, not CLI parity.)
What did you expect to happen?
The web app replaces an existing entry where it sits, matching UpsertAssignment's documented "Position preserved on replace". Only genuinely new slugs append.
Steps to reproduce
- In a classroom with several assignments, note the order of
gh teacher assignment list --org <org> --classroom <c>, or open <classroom>/assignments.json in the config repo.
- In the web app, lock (or close, or edit) an assignment that is not the last one in that file.
- Re-run
gh teacher assignment list — the assignment is now printed last.
git show the commit the web app made: the whole entry was removed from its position and re-added at the end, not flipped in place.
Doing the same lock with gh teacher assignment lock leaves the position untouched.
Version and environment
Web app on main (acff6b1c), any browser; teacher CLI from the same tree. Not environment-specific — it is the write shape in the three functions above.
Which part is affected?
Web app (classroom50.org)
What happened?
Three web writers rebuild
<classroom>/assignments.jsonas "everything except this slug, then the updated entry", so the touched row is moved to the end of the array:editAssignment—web/src/domain/assignments/createEdit.ts:249setAssignmentLock—createEdit.ts:1315setAssignmentClosed—createEdit.ts:1456The CLI is the other side of this contract and does the opposite, deliberately:
—
cli/gh-teacher/internal/assignment/assignments_json.go:766, andgh teacher assignment lockwritesfile.Assignments[idx] = entry(lock.go:108).The web app is inconsistent with itself too:
renameAssignmentalready maps in place (rename.ts:292,rename.ts:396) — and says so in a comment that names the lock flip as the counter-example — whiledeleteAssignmentis a plain filter andcreateAssignment/copyAssignmentappend genuinely new slugs, which is correct. Only these three move an existing row.What this causes
gh teacher assignment listreorders. The list command prints entries in file order — no sort (assignment.go:442) — and--jsonemits the array as stored. Lockinghw1in the web app moves it belowhw9in the teacher's terminal, and nothing the teacher did asked for a reorder. The web UI hides this because both its lists sort explicitly (DEFAULT_SORT = "name-asc", studentsdue-asc), so the divergence is invisible from the side that causes it.Every flag flip is a whole-row diff. Toggling
lockedshould be a one-line change in the config repo. Instead the commit deletes the entry from its position and re-adds it at the end — for an entry with a template block, tests, and a due date, that is a ~15-line diff for a one-field change. The config repo is the teacher's audit trail, andgit log -p cs50/assignments.jsonno longer shows what actually changed.Same action, two files. Web app and CLI, given the same starting file and the same lock, commit different content. Comparing two classrooms, or diffing a file across the two tools, has to normalize order first.
Nothing is functionally broken: every reader looks entries up by slug, so no student, accept, or grading path misbehaves. The cost is the teacher-visible ordering in the CLI and the audit trail in the config repo.
(
closedhas no CLI writer at all — the CLI preserves it as "owned out of band by the web",assignment.go:878— so forsetAssignmentClosedit is points 1 and 2 that apply, not CLI parity.)What did you expect to happen?
The web app replaces an existing entry where it sits, matching
UpsertAssignment's documented "Position preserved on replace". Only genuinely new slugs append.Steps to reproduce
gh teacher assignment list --org <org> --classroom <c>, or open<classroom>/assignments.jsonin the config repo.gh teacher assignment list— the assignment is now printed last.git showthe commit the web app made: the whole entry was removed from its position and re-added at the end, not flipped in place.Doing the same lock with
gh teacher assignment lockleaves the position untouched.Version and environment
Web app on
main(acff6b1c), any browser; teacher CLI from the same tree. Not environment-specific — it is the write shape in the three functions above.