diff --git a/.claude/rules/prisma-db.md b/.claude/rules/prisma-db.md index d9a9ac420..0189c0125 100644 --- a/.claude/rules/prisma-db.md +++ b/.claude/rules/prisma-db.md @@ -141,6 +141,12 @@ When a migration leaves `finished_at = NULL` in `_prisma_migrations`: A `--rolled-back` migration is permanently skipped by `migrate deploy`; fixing the original file has no effect. +## Merged Data: Document Uniqueness Loss + +Service functions that merge records from multiple sources (e.g., `getMergedTasksMap()` combining base tasks + `ContestTaskPair` entries) lose individual field uniqueness. Document that results contain duplicates in "unique" DB fields. + +Example: `getMergedTasksMap()` returns multiple entries with same `task_id` but different `contest_id` — callers must not assume `task_id` is unique. UI layer must use composite keys (see svelte-components.md `{#each}` patterns). + ## Validate Constraints Prisma does not support `@@check`. To add one: diff --git a/.claude/rules/svelte-components.md b/.claude/rules/svelte-components.md index 4569ac508..bcb24da19 100644 --- a/.claude/rules/svelte-components.md +++ b/.claude/rules/svelte-components.md @@ -37,6 +37,7 @@ Use `$props()`, `$state()`, `$derived()`, `$effect()` in all components: ## `{#each}` Patterns - Always key: `(item.id)` or `(i)` +- **Key MUST be unique per iteration** — if domain allows duplicates, use composite key (e.g. `contest_id + '-' + task_id`) - Filter **before**, not inside with `{#if}` - Use `{:else}` for empty lists @@ -48,6 +49,8 @@ Use `$props()`, `$state()`, `$derived()`, `$effect()` in all components: {/each} ``` +**Common Trap:** `task_id` alone is NOT unique when same task appears in multiple contests. Use `contest_id + '-' + task_id` as composite key (see issue #3460 & PR #3442). + ## Snippets vs Components - **Snippet**: needs `$state` access, pure display, same-file; params require explicit type `{#snippet label(item: Item)}` diff --git a/src/lib/components/TaskList.svelte b/src/lib/components/TaskList.svelte index bbfa66303..3cf080f56 100644 --- a/src/lib/components/TaskList.svelte +++ b/src/lib/components/TaskList.svelte @@ -97,7 +97,7 @@ - {#each taskResults as taskResult (taskResult.task_id)} + {#each taskResults as taskResult (taskResult.contest_id + '-' + taskResult.task_id)}