diff --git a/.claude/rules/coding-style.md b/.claude/rules/coding-style.md index 454db270b..591e9be71 100644 --- a/.claude/rules/coding-style.md +++ b/.claude/rules/coding-style.md @@ -34,6 +34,15 @@ When the same constraint is enforced in two layers (e.g. Zod validation + SQL `C .refine(...) ``` +## Optimistic Updates + +Derive computed fields (flags, labels, etc.) from the canonical data source — don't +re-implement the derivation inline. Divergence causes a "works after reload" bug where +the server state is correct but the client-side update is wrong. + +**Diagnostic**: "Not reflected live, but fixed after reload" → suspect the optimistic +update payload, not the reactivity system. + ## Async Rollback: Capture State Before `await` Capture `$state` values before the first `await` for safe rollback. A concurrent update can overwrite the variable while awaiting: diff --git a/src/features/tasks/components/contest-table/TaskTable.svelte b/src/features/tasks/components/contest-table/TaskTable.svelte index 3c26f5bb6..f88932772 100644 --- a/src/features/tasks/components/contest-table/TaskTable.svelte +++ b/src/features/tasks/components/contest-table/TaskTable.svelte @@ -26,6 +26,7 @@ } from '$features/tasks/utils/contest-table/contest_table_provider'; import { getBackgroundColorFrom } from '$lib/services/submission_status'; + import { areAllTasksAccepted } from '$lib/utils/task'; import { createContestTaskPairKey } from '$lib/utils/contest_task_pair'; interface Props { @@ -105,6 +106,24 @@ return totalColumns > 8 ? 'flex flex-wrap' : 'flex flex-wrap xl:table-row'; } + function getRoundLabelClasses(contestTable: ProviderData, contestId: string): string { + const tasks = Object.values(contestTable.innerTaskTable[contestId]); + const bgColor = getRoundLabelBgColor(tasks); + + return `w-full ${contestTable.displayConfig.roundLabelWidth} truncate px-2 py-2 text-center dark:text-gray-300 ${bgColor}`; + } + + // Note: If any task is ac_with_editorial, that color takes priority over AC (Accepted). + function getRoundLabelBgColor(tasks: TaskResults): string { + if (!isLoggedIn || !areAllTasksAccepted(tasks, tasks)) { + return 'bg-gray-50 dark:bg-gray-800'; + } + + const hasEditorial = tasks.some((task) => task.status_name === 'ac_with_editorial'); + + return getBackgroundColorFrom(hasEditorial ? 'ac_with_editorial' : 'ac'); + } + function getBodyCellClasses(taskResult: TaskResult, tableBodyCellWidth: string): string { const backgroundColor = getBackgroundColor(taskResult); @@ -240,10 +259,7 @@ {#each contestTable.contestIds as contestId (contestId)} {#if contestTable.displayConfig.isShownRoundLabel} - + {getContestRoundLabel(provider, contestId)} {/if} diff --git a/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte b/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte index 00e1cf4e0..6993d6e92 100644 --- a/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte +++ b/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte @@ -158,7 +158,9 @@ status_name: submissionStatus.innerName, status_id: submissionStatus.innerId, submission_status_label_name: submissionStatus.labelName, - is_ac: submissionStatus.innerName === 'ac', + is_ac: + submission_statuses.find((status) => status.status_name === submissionStatus.innerName) + ?.is_AC ?? false, updated_at: new Date(), }; }