diff --git a/CHANGELOG.md b/CHANGELOG.md
index 066e6266..7d353b9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,9 +19,9 @@ written while it was being built. See [RELEASING.md](RELEASING.md).
### Added
- Manage Teams fixes the teams once a team has a submission or teams are
- published: a notice says why, and uploading, adding and deleting teams are
- disabled. People can still be moved between teams or to Unassigned by hand,
- and teams renamed.
+ published: a notice says why, and uploading and deleting teams are disabled.
+ People can still be moved between teams or to Unassigned by hand, teams
+ renamed, and new teams added.
- Manage Teams can filter the Unassigned column by registration answers: click
an answer (or type into a free-text question's box) to bring the people who
gave it to the top; everyone else stays below, greyed out. Each answer shows
diff --git a/components/frontend/src/lib/server/hackathon/teamAssignmentLock.ts b/components/frontend/src/lib/server/hackathon/teamAssignmentLock.ts
index 595cfb08..7154f8e4 100644
--- a/components/frontend/src/lib/server/hackathon/teamAssignmentLock.ts
+++ b/components/frontend/src/lib/server/hackathon/teamAssignmentLock.ts
@@ -11,9 +11,9 @@ import { enabledCapabilities } from "./phaseForm"
/**
* Why the teams themselves are fixed — empty while they are not.
*
- * Fixed means no team may be added or deleted, and so no upload, which
- * replaces every team. People may still be moved by hand and teams renamed:
- * that is how an organizer handles someone dropping out late. Two things fix
+ * Fixed means no team may be deleted, and so no upload, which replaces every
+ * team. People may still be moved by hand, teams renamed and new teams added:
+ * that is how an organizer handles a late dropout or a late joiner. Two things fix
* the teams, each because rebuilding them would pull the ground from under
* somebody:
*
@@ -25,8 +25,8 @@ import { enabledCapabilities } from "./phaseForm"
* Either one is enough, and the reasons are listed so the page can say which.
*
* TODO(backend: team-assignment-lock): this is a frontend rule only. The page
- * stops offering the controls and its save action refuses a plan that adds or
- * deletes a team, but `TeamService` still accepts that from any other caller.
+ * stops offering the controls and its save action refuses a plan that deletes
+ * a team, but `TeamService` still accepts that from any other caller.
* Planned as a backend check in a follow-up pull request; the frontend can
* then keep this for the explanation and rely on the error for the rule.
*/
diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.server.ts b/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.server.ts
index e0b86ab8..18d1ecac 100644
--- a/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.server.ts
+++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.server.ts
@@ -249,19 +249,18 @@ export const actions: Actions = {
const before = all.filter((t) => approved.has(t.projectId))
if (locked.length > 0) {
- // Locked, the teams themselves stand: people may move between them or
- // to Unassigned and a team may be renamed, but none may be added or
- // deleted — deleting one takes its submissions with it, and published
- // teams are what participants have been told. An emptied team is kept.
+ // Locked, no team may be deleted: deleting one takes its submissions
+ // with it, and published teams are what participants have been told.
+ // Everything else stays open — people may move between teams or to
+ // Unassigned, teams may be renamed, and a new team may be added. An
+ // emptied team is kept; a new one nobody was put on is not created.
const planned = new Set(plan.map((t) => t.id))
- if (
- plan.some((t) => t.id === null) ||
- before.some((t) => !planned.has(t.id))
- ) {
+ if (before.some((t) => !planned.has(t.id))) {
return fail(409, {
- message: `Teams cannot be added or deleted now: ${locked.join(" and ")}.`,
+ message: `Teams cannot be deleted now: ${locked.join(" and ")}.`,
})
}
+ plan = plan.filter((t) => t.id !== null || t.memberIds.length > 0)
} else {
// A team with nobody in it is not part of the assignment: dropped, so
// a new one is never created and a saved one is deleted below like any
diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.svelte b/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.svelte
index a48e1635..82cd9981 100644
--- a/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.svelte
+++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/teams/manage/+page.svelte
@@ -366,9 +366,12 @@
const base = fromServer(data.projectRows);
const nameBefore = new Map(base.map((t) => [t.key, t.name]));
// Save drops a team with nobody in it, so an empty one counts as
- // deleted, or as not added — the summary says what Save will do. Not
- // while the teams are fixed: then an emptied team is kept.
- const kept = locked ? teams : teams.filter((t) => t.memberIds.length > 0);
+ // deleted, or as not added — the summary says what Save will do. While
+ // the teams are fixed, an emptied saved team is kept; an empty new one
+ // is still not created.
+ const kept = teams.filter(
+ (t) => t.memberIds.length > 0 || (locked && t.id !== null)
+ );
const alive = new Set(kept.map((t) => t.key));
const added = kept.filter((t) => t.id === null).length;
@@ -438,10 +441,10 @@
dropTarget = null;
}
- // Once a submission exists or teams are published, the teams themselves
- // stand: no upload (it replaces every team), no adding, deleting or Clear
- // all. People can still be moved by hand — between teams or to Unassigned —
- // and teams renamed, and the save action enforces the same line. The
+ // Once a submission exists or teams are published, no team may go: no
+ // upload (it replaces every team), no deleting, no Clear all. People can
+ // still be moved by hand — between teams or to Unassigned — teams renamed
+ // and new teams added, and the save action enforces the same line. The
// reason is said once at the top and again on hover over what is off. See
// `assignmentLockReasons`.
const lockReasons = $derived(data.lockReasons);
@@ -692,8 +695,8 @@
Teams are fixed: - {lockReasons.join(' and ')}. Upload and adding or deleting teams are off; you can - still move people between teams or to Unassigned, and rename teams. + {lockReasons.join(' and ')}. Upload and deleting teams are off; you can still + move people between teams or to Unassigned, rename teams and add new ones.
{/if} @@ -819,7 +822,7 @@Once a team has a submission or teams are published, teams are fixed: no - upload, no adding or deleting teams. Moving people and renaming still work. + upload, no deleting teams. Moving people, renaming and adding teams still work.
@@ -1190,7 +1193,9 @@ >