Skip to content

[#549] Add configurable challenge groups for project judging - #550

Merged
DVidal1205 merged 18 commits into
mainfrom
fix/first-time-hacker-room-tagging
Sep 9, 2026
Merged

[#549] Add configurable challenge groups for project judging#550
DVidal1205 merged 18 commits into
mainfrom
fix/first-time-hacker-room-tagging

Conversation

@farisAwheel

@farisAwheel farisAwheel commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Why

First Time Hacker projects are judged with General, while their eligibility must remain obvious to General judges. MLH tracks use the same room model. Both need organizer-controlled challenge groups.

What

Closes #549.

  • Adds editable Challenge groups above Devpost-imported prize challenges. General and MLH Challenges are starter defaults, and organizers can rename, remove, or create groups.
  • Collapses child challenges into their parent appointment and evaluation. Child eligibility remains visible in project details and the feedback modal; project directory shows parent groups while child challenges remain available in filters.
  • Routes imported MLH labels into the selected MLH group by default while preserving organizer assignments across re-imports.
  • Keeps configured groups when projects are dropped and provides General and MLH starter groups for new or uninitialized hackathons.
  • Preserves add-only imports after schedules are saved and keeps room and rubric setup locked once saved.
  • Uses one final challenge-group migration (0053_challenge_groups) with its Drizzle snapshot.

Prize challenges continue to come from Devpost. No shared or production database was migrated by this PR.

Test Plan

  • pnpm verify:precommit passed formatting, lint, React analysis, and workspace typechecks.
  • pnpm --filter @forge/db test: 30 files and 157 tests passed.
  • API project-room/access tests: 8 tests passed.
  • Blade projects tests: 63 tests passed.
  • Validators tests: 9 tests passed.
  • pnpm db:generate: no pending schema changes.
  • Browser verification used synthetic local fixtures and submitted no scores.

Screenshots

Fresh captures from the current Blade build:

Group controls

image

Imported challenge assignments

image

Project tags

image image image

Room editor

image

Add-only import after scheduling (old-screenshot)

image

Room setup lock (old-screenshot)

image

Projects search filter

image

Judging modal

image image

for the judging modal, it filters based on what your search filter is, this works because if you're judging for general, you don't need to worry about mlh, and if mlh does use our system, they only see their challenges under their filter too!

@farisAwheel farisAwheel added Feature New Feature or Request Major Big change - 2+ reviewers required Blade Change modifies code in Blade app Database Change modifies code in the DB package API Change modifies code in the global API/tRPC package labels Sep 8, 2026
@farisAwheel farisAwheel self-assigned this Sep 8, 2026
@farisAwheel
farisAwheel force-pushed the fix/first-time-hacker-room-tagging branch from af7c8fa to 53d5730 Compare September 8, 2026 22:27
@farisAwheel
farisAwheel marked this pull request as draft September 8, 2026 22:34
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: QUIET

Plan: Advanced

Run ID: 8797bafc-73bd-48a7-9009-2a5c36a676f2

📥 Commits

Reviewing files that changed from the base of the PR and between b146c0b and 3598354.

📒 Files selected for processing (3)
  • apps/blade/src/app/_components/judging/evaluation-dialog.tsx
  • apps/blade/src/tests/member/member-dues-webhook.test.ts
  • apps/blade/src/tests/projects/project-judge-privacy.test.tsx

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Adds organizer-managed judging groups with hierarchical challenge membership, import matching, schedule locks, and parent-aware judging behavior. Updates project imports, room and rubric editing, project filters, evaluation badges, and guest access. Adds database migration, API mutations, validation schemas, UI controls, tests, and feature documentation.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 35983

Challenge groups now support hierarchical judging, preserved imports, schedule locks, and visible child tags. The remaining risk is bounded to regression coverage and filter metadata handling, which could cause incorrect challenge filtering or less-clear locked-state behavior in edge cases but does not establish a broad production failure.

🚥 Pre-merge checks | ✅ 5 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Most linked objectives are implemented, but the issue requires organizer-configurable tag colors. The status summary says custom tag-color support was removed, and validators strip tagColor. Restore persisted organizer-configurable tag-color support, including validation, API handling, migration/schema support, and UI coverage, or update issue #549 to remove this acceptance criterion.
Out of Scope Changes check ⚠️ Warning The member dues webhook test change is unrelated to challenge groups and judging configuration. Remove the change to apps/blade/src/tests/member/member-dues-webhook.test.ts or link it to a separate issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 49 functions across 48 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title starts with [#549], describes configurable challenge groups, and is 60 characters long.
Description check ✅ Passed The description directly explains the challenge-group implementation, locking rules, imports, migrations, and validation.
No Hardcoded Secrets ✅ Passed No hardcoded API key, password, token, or secret was introduced by the pull request. The added-line scans found no provider token prefixes, private-key markers, or credential assignments. The generate…
Validated Env Access ✅ Passed The pull-request diff adds no process.env usage. None of the changed files contains a direct process.env access, so the PR does not introduce a violation outside env.ts or config files.
No Typescript Escape Hatches ✅ Passed No changed TypeScript code introduces an explicit any type, @ts-ignore, @ts-expect-error, or non-null member assertion (!.). The only added any match is ordinary prose in a test title, not a…
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/first-time-hacker-room-tagging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/api/src/projects-import.server.ts (1)

205-210: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Handle group-label collisions during import.

When an imported label matches a group label, retainedLabels excludes the group and challengeLabelsToCreate inserts a non-group ProjectChallenge with the same label. The database constraint allows this because it includes isGroup, and challengeIds then maps the imported label to the duplicate non-group row. Exclude group labels before insertion or reuse the existing group row.

🟡 Other comments (5)
apps/blade/src/app/_components/judging/evaluation-dialog.tsx-327-335 (1)

327-335: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not hardcode white text over an organizer-chosen color.

tagColor comes from an unconstrained <input type="color"> in the challenge configuration panel. If an organizer picks a light color, color: "white" makes the challenge label unreadable. The badge is the only place the child challenge label appears in this dialog.

Pick the foreground from the color's relative luminance.

Proposed fix
+function readableForeground(hex: string) {
+  const value = hex.replace("#", "");
+  if (value.length !== 6) return "white";
+  const [r, g, b] = [0, 2, 4].map((offset) => {
+    const channel = Number.parseInt(value.slice(offset, offset + 2), 16) / 255;
+    return channel <= 0.03928
+      ? channel / 12.92
+      : ((channel + 0.055) / 1.055) ** 2.4;
+  }) as [number, number, number];
+  return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.4 ? "black" : "white";
+}
                     style={
                       challenge.tagColor
                         ? {
                             backgroundColor: challenge.tagColor,
                             borderColor: challenge.tagColor,
-                            color: "white",
+                            color: readableForeground(challenge.tagColor),
                           }
                         : undefined
                     }

As per path instructions for apps/blade/**: "Accessibility (alt text, ARIA, semantic HTML)".

Source: Path instructions

packages/api/src/routers/projects.ts-837-841 (1)

837-841: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Duplicate ids in input.challengeIds trigger the challenge-setup lock spuriously.

projectUpdateInputSchema.challengeIds does not enforce uniqueness, and line 804 compares against new Set(input.challengeIds).size, so duplicates reach this point. explicitIds holds distinct membership rows, so the length comparison fails and assertChallengeSetupEditable throws "Challenge setup is locked" for a save that changes nothing. Compare sets instead.

🐛 Proposed fix
-        if (
-          explicitIds.length !== input.challengeIds.length ||
-          explicitIds.some((id) => !input.challengeIds.includes(id))
-        )
+        const selectedIds = new Set(input.challengeIds);
+        if (
+          explicitIds.length !== selectedIds.size ||
+          explicitIds.some((id) => !selectedIds.has(id))
+        )
           await assertChallengeSetupEditable(tx, existing.hackathonId);
apps/blade/src/app/_components/judging/judging-configuration-panel.tsx-61-61 (1)

61-61: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the lock reason text for the saved schedule.

When data.setupLocked is true and data.configuration.state is "draft", an existing JudgingSchedule locks the rubric. Tell the officer to drop the schedule. Keep the current message for non-draft states.

Proposed fix
-  const rubricLocked = data.setupLocked || data.configuration.state !== "draft";
+  const scheduleLocked = data.setupLocked;
+  const rubricLocked = scheduleLocked || data.configuration.state !== "draft";
       {rubricLocked ? (
         <Alert>
           <AlertTitle>Rubric locked</AlertTitle>
           <AlertDescription>
-            The rubric cannot change after judging opens. Close and reopen
-            judging without changing the questions.
+            {scheduleLocked
+              ? "A saved schedule locks the rubric. Drop the schedule to change the questions."
+              : "The rubric cannot change after judging opens. Close and reopen judging without changing the questions."}
           </AlertDescription>
         </Alert>
       ) : null}
apps/blade/src/app/_components/judging/judging-control-panel.tsx-727-727 (1)

727-727: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Explain the room lock when data.setupLocked is true.

A saved schedule sets data.setupLocked and disables room creation, saving, reordering, editing, and archiving. The schedule controls are on a separate tab, so officers can see disabled room controls without a cause or remediation. Render a lock alert in this panel and state that officers must drop the eligible schedule before changing rooms.

Proposed fix
+      {data.setupLocked ? (
+        <Alert>
+          <QrCode className="size-4" />
+          <AlertTitle>Room setup locked</AlertTitle>
+          <AlertDescription>
+            A saved schedule locks room changes. Open the Schedule tab and drop
+            the schedule while it is eligible before changing rooms.
+          </AlertDescription>
+        </Alert>
+      ) : null}
       {!data.challenges.length ? (
packages/api/src/routers/project-challenges.ts-159-159 (1)

159-159: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add specific messages to all three NOT_FOUND errors.

Stale group requests, or a challenge deleted between validation and update, can reach these branches. ChallengeConfigurationPanel.change displays error.message directly, so organizers can see only tRPC's generic NOT_FOUND message. Use entity-specific messages for the two groups and the challenge.

🧹 Nitpick comments (3)
packages/validators/src/audit.ts (1)

535-542: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Declare change fields for the judging update actions.

validateActionPayload rejects changes fields that are not declared in each policy. Add change fields for the group properties and challenge parentId and isScheduled. Update both emitters to provide the corresponding before and after values.

apps/blade/src/tests/projects/challenge-configuration-panel.test.tsx (1)

165-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the locked state of existing group names.

When data.challengeSetupLocked is true, the group-name Input receives disabled={locked || pending}. Add expect(screen.getByRole("textbox", { name: "Group name: General" })).toBeDisabled() so this behavior cannot regress unnoticed.

packages/validators/src/projects.ts (1)

25-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit tagColor regex message to both group schemas.

When malformed tagColor reaches either mutation, ChallengeConfigurationPanel displays Zod's generic regex error in a toast. Use the same message in both .regex(...) calls.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: QUIET

Plan: Advanced

Run ID: a41b4834-ebba-45a8-bd6f-a417868af412

📥 Commits

Reviewing files that changed from the base of the PR and between d22c913 and af7c8fa.

📒 Files selected for processing (56)
  • .forge/features/collapsed-challenges/spec.md
  • .forge/features/collapsed-challenges/srd.md
  • .forge/features/collapsed-challenges/status.md
  • .forge/features/collapsed-challenges/test-cases.md
  • apps/blade/src/app/_components/judging/challenge-configuration-panel.tsx
  • apps/blade/src/app/_components/judging/evaluation-dialog.tsx
  • apps/blade/src/app/_components/judging/judge-submissions.tsx
  • apps/blade/src/app/_components/judging/judging-configuration-panel.tsx
  • apps/blade/src/app/_components/judging/judging-control-panel.tsx
  • apps/blade/src/app/_components/judging/judging-schedule-panel.tsx
  • apps/blade/src/app/_components/projects/admin-project-workspace.tsx
  • apps/blade/src/app/_components/projects/drop-all-projects-dialog.tsx
  • apps/blade/src/app/_components/projects/judge-project-workspace.tsx
  • apps/blade/src/app/_components/projects/project-detail-dialog.tsx
  • apps/blade/src/app/_components/projects/project-directory.tsx
  • apps/blade/src/app/_components/projects/project-import-dialog.tsx
  • apps/blade/src/app/judge/projects/page.tsx
  • apps/blade/src/tests/projects/challenge-configuration-panel.test.tsx
  • apps/blade/src/tests/projects/judge-deliberation.test.tsx
  • apps/blade/src/tests/projects/judging-announcement-editor.test.tsx
  • apps/blade/src/tests/projects/project-judge-privacy.test.tsx
  • docs/DATABASE-USAGE.md
  • packages/api/src/projects-import.server.ts
  • packages/api/src/routers/hackathon.ts
  • packages/api/src/routers/judging-schedule-view.ts
  • packages/api/src/routers/judging-scores.ts
  • packages/api/src/routers/judging.ts
  • packages/api/src/routers/project-challenges.ts
  • packages/api/src/routers/projects.ts
  • packages/api/src/tests/integration/judging-access.test.ts
  • packages/api/src/tests/integration/judging-schedule.test.ts
  • packages/api/src/tests/integration/project-room-filter.test.ts
  • packages/api/src/tests/projects/devpost-import.test.ts
  • packages/api/src/tests/projects/drop-all.test.ts
  • packages/api/src/utils/audit/coverage.ts
  • packages/api/src/utils/judging-schedule/appointments.ts
  • packages/api/src/utils/judging-schedule/evaluation-access.ts
  • packages/api/src/utils/judging-schedule/source.ts
  • packages/api/src/utils/judging/scope.ts
  • packages/api/src/utils/projects/challenge-configuration.ts
  • packages/api/src/utils/projects/challenge-labels.ts
  • packages/api/src/utils/projects/devpost-import.ts
  • packages/api/src/utils/projects/initialize-judging-groups.ts
  • packages/db/drizzle/0053_naive_wasp.sql
  • packages/db/drizzle/0054_giant_black_cat.sql
  • packages/db/drizzle/0055_initialize_judging_groups.sql
  • packages/db/drizzle/0056_premium_mesmero.sql
  • packages/db/drizzle/meta/0053_snapshot.json
  • packages/db/drizzle/meta/0054_snapshot.json
  • packages/db/drizzle/meta/0055_snapshot.json
  • packages/db/drizzle/meta/0056_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schemas/knight-hacks.ts
  • packages/db/src/tests/projects-schema.test.ts
  • packages/validators/src/audit.ts
  • packages/validators/src/projects.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread apps/blade/src/app/_components/projects/project-import-dialog.tsx
Comment thread packages/api/src/projects-import.server.ts Outdated
Comment thread packages/api/src/routers/project-challenges.ts
DGoel1602 and others added 2 commits September 8, 2026 18:50
Co-authored-by: Codex <codex@openai.com>
@farisAwheel
farisAwheel marked this pull request as ready for review September 8, 2026 23:03
Co-authored-by: Codex <codex@openai.com>
@DGoel1602

Copy link
Copy Markdown
Contributor

Addressed the review findings:

  • Saved schedules allow add-only imports again, per the maintainer's decision. Replacement remains blocked, including an already-open replacement dialog. Tests cover both disabled entry points, late imports, unchanged existing projects/memberships, and replacement rejection.
  • All three challenge-tag surfaces choose the higher-contrast black/white foreground. Added light, dark, and middle-gray regression cases and refreshed screenshots.
  • Duplicate copies of the same opt-in IDs no longer trigger the feedback lock. Room and rubric locks explain the cause and where to drop the eligible schedule.
  • Group/challenge updates now record permitted before/after change fields. Missing entities and invalid colors have explicit messages; locked group names have a regression assertion.

Two findings retain the existing behavior with added regression coverage:

  • Groups and imported prizes may intentionally share a display label. The spec/SRD and (hackathon_id, label, is_group) constraint distinguish their identities; reusing the group would lose the explicit imported-prize opt-in. The import regression checks distinct IDs and separate memberships after re-import.
  • Group deletion already calls assertChallengeSetupEditable under the hackathon lock. That rejects saved schedules, evaluations, and drafts before membership deletion; appointments require a saved schedule through the scoped foreign key. Tests now verify controlled PRECONDITION_FAILED errors for a saved schedule and existing feedback, rather than a database constraint failure.

Also reproduced and fixed the failed CI run's API contract snapshot: only the four new group/challenge procedures were missing. Full-suite validation exposed a separate test timeout caused by importing the shared API barrel inside the webhook test's timer; that import now runs during collection with all assertions preserved.

Validation: CI=true pnpm exec turbo run test --filter='!@forge/db' passed all 28 tasks (938 API tests, 849 Blade tests). pnpm verify:precommit passed React analysis, formatting, lint, and 33 workspace typechecks. Screenshots use synthetic local fixtures.

Co-authored-by: Codex <codex@openai.com>
@DGoel1602

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (3)
apps/blade/src/app/_components/judging/judging-control-panel.tsx-303-306 (1)

303-306: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Guard RoomEditor.submit before createBuilding.

If data.setupLocked becomes true while the dialog is open, submit can create a global JudgingBuilding before createRoom rejects the locked setup. Return immediately when data.setupLocked is true.

packages/api/src/tests/integration/project-room-filter.test.ts-506-514 (1)

506-514: 🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟡 Minor | ⚡ Quick win

Assert FORBIDDEN for both guest authorization failures.

rejects.toThrow() accepts any error. Match the authorization code so unrelated failures cannot satisfy these checks.

💚 Proposed fix
     await expect(
       guest.projects.listJudge({
         ...guestListInput,
         challengeIds: [mlhOptInId],
       }),
-    ).rejects.toThrow();
+    ).rejects.toMatchObject({ code: "FORBIDDEN" });
     await expect(
       guest.projects.listJudge({ ...guestListInput, challengeIds: [mlhId] }),
-    ).rejects.toThrow();
+    ).rejects.toMatchObject({ code: "FORBIDDEN" });
apps/blade/src/app/_components/judging/evaluation-dialog.tsx-316-319 (1)

316-319: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Give the labelled container an ARIA role.

A plain div cannot use aria-label as its accessible name. Add role="group" so assistive technology can expose “Challenge opt-ins” before its child labels.

♿ Proposed fix
             <div
               className="mt-2 flex max-h-28 flex-wrap gap-2 overflow-y-auto"
+              role="group"
               aria-label="Challenge opt-ins"
             >

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: QUIET

Plan: Advanced

Run ID: 4adeb0fd-926f-4b8c-a708-63c674ed87cf

📥 Commits

Reviewing files that changed from the base of the PR and between af7c8fa and 138ddce.

⛔ Files ignored due to path filters (7)
  • .forge/features/collapsed-challenges/screenshots/add-only-import.jpg is excluded by !**/*.jpg
  • .forge/features/collapsed-challenges/screenshots/challenge-groups.jpg is excluded by !**/*.jpg
  • .forge/features/collapsed-challenges/screenshots/imported-challenges.jpg is excluded by !**/*.jpg
  • .forge/features/collapsed-challenges/screenshots/project-detail.jpg is excluded by !**/*.jpg
  • .forge/features/collapsed-challenges/screenshots/project-tags.jpg is excluded by !**/*.jpg
  • .forge/features/collapsed-challenges/screenshots/room-setup-lock.jpg is excluded by !**/*.jpg
  • packages/api/src/tests/root/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (29)
  • .forge/features/collapsed-challenges/spec.md
  • .forge/features/collapsed-challenges/srd.md
  • .forge/features/collapsed-challenges/status.md
  • .forge/features/collapsed-challenges/test-cases.md
  • apps/blade/src/app/_components/judging/evaluation-dialog.tsx
  • apps/blade/src/app/_components/judging/judging-configuration-panel.tsx
  • apps/blade/src/app/_components/judging/judging-control-panel.tsx
  • apps/blade/src/app/_components/projects/admin-project-workspace.tsx
  • apps/blade/src/app/_components/projects/challenge-tag-style.ts
  • apps/blade/src/app/_components/projects/project-detail-dialog.tsx
  • apps/blade/src/app/_components/projects/project-directory.tsx
  • apps/blade/src/app/_components/projects/project-import-dialog.tsx
  • apps/blade/src/tests/member/member-dues-webhook.test.ts
  • apps/blade/src/tests/projects/challenge-configuration-panel.test.tsx
  • apps/blade/src/tests/projects/judging-announcement-editor.test.tsx
  • apps/blade/src/tests/projects/project-import-dialog.test.tsx
  • apps/blade/src/tests/projects/project-judge-privacy.test.tsx
  • docs/DATABASE-USAGE.md
  • packages/api/src/projects-import.server.ts
  • packages/api/src/routers/project-challenges.ts
  • packages/api/src/routers/projects.ts
  • packages/api/src/tests/integration/project-room-filter.test.ts
  • packages/db/drizzle/0053_challenge_groups.sql
  • packages/db/drizzle/meta/0053_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/tests/migration-lineage.test.ts
  • packages/validators/src/audit.ts
  • packages/validators/src/projects.ts
  • packages/validators/src/tests/projects.test.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread apps/blade/src/app/_components/judging/judging-control-panel.tsx
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/blade/src/app/_components/projects/project-directory.tsx (1)

48-49: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make isGeneral and parentId required on this contract.

Both fields are optional. Line 129 excludes general groups from the filter list only when isGeneral is true, so any caller that omits the field silently reintroduces all-project groups into the challenge filter. The API challenge shape supplies both fields; drop the ? so a caller cannot skip them.

♻️ Proposed change
   challenges: {
     id: string;
     label: string;
-    isGeneral?: boolean;
-    parentId?: string | null;
+    isGeneral: boolean;
+    parentId: string | null;
   }[];
🟡 Other comments (1)
.forge/features/collapsed-challenges/test-cases.md-20-20 (1)

20-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align behavioral case 6 with this regression requirement.

Case 6 says search excludes child challenges. Line 20 requires child challenges to remain selectable in filters. Change case 6 to include child challenges and exclude only every-project groups.

🧹 Nitpick comments (1)
packages/api/src/tests/integration/project-room-filter.test.ts (1)

735-744: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the filtered audit events are not empty.

If the filter matches zero events, the loop body never runs and the test passes without checking anything. A regression that stops recording judging.group.created or judging.group.updated would not fail this test. Add a length assertion before the loop.

🧪 Proposed fix
-    for (const event of auditEvents.filter(
-      (event) =>
-        event.actionKey === "judging.group.created" ||
-        event.actionKey === "judging.group.updated",
-    )) {
+    const groupAuditEvents = auditEvents.filter(
+      (event) =>
+        event.actionKey === "judging.group.created" ||
+        event.actionKey === "judging.group.updated",
+    );
+    expect(groupAuditEvents.length).toBeGreaterThan(0);
+    for (const event of groupAuditEvents) {
       expect(event.metadata).not.toHaveProperty("tagColor");
       expect(event.changes.some((change) => change.field === "tagColor")).toBe(
         false,
       );
     }

As per path instructions for **/*.test.*: "Check for meaningful descriptions, proper assertions, and no skipped tests without explanation."

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: QUIET

Plan: Advanced

Run ID: ee86e1d7-2f9e-43ef-854e-12e65a60147c

📥 Commits

Reviewing files that changed from the base of the PR and between 138ddce and aab75c6.

⛔ Files ignored due to path filters (1)
  • .forge/features/collapsed-challenges/screenshots/room-editor.jpg is excluded by !**/*.jpg
📒 Files selected for processing (27)
  • .forge/features/collapsed-challenges/spec.md
  • .forge/features/collapsed-challenges/srd.md
  • .forge/features/collapsed-challenges/status.md
  • .forge/features/collapsed-challenges/test-cases.md
  • apps/blade/src/app/_components/judging/challenge-configuration-panel.tsx
  • apps/blade/src/app/_components/judging/evaluation-dialog.tsx
  • apps/blade/src/app/_components/judging/judging-control-panel.tsx
  • apps/blade/src/app/_components/projects/project-detail-dialog.tsx
  • apps/blade/src/app/_components/projects/project-directory.tsx
  • apps/blade/src/tests/projects/challenge-configuration-panel.test.tsx
  • apps/blade/src/tests/projects/judging-announcement-editor.test.tsx
  • apps/blade/src/tests/projects/project-judge-privacy.test.tsx
  • packages/api/src/routers/judging-schedule-view.ts
  • packages/api/src/routers/judging-scores.ts
  • packages/api/src/routers/project-challenges.ts
  • packages/api/src/routers/projects.ts
  • packages/api/src/tests/integration/project-room-filter.test.ts
  • packages/api/src/utils/judging-schedule/evaluation-access.ts
  • packages/api/src/utils/projects/challenge-configuration.ts
  • packages/api/src/utils/projects/challenge-labels.ts
  • packages/db/drizzle/0053_challenge_groups.sql
  • packages/db/drizzle/meta/0053_snapshot.json
  • packages/db/src/schemas/knight-hacks.ts
  • packages/db/src/tests/event-management-migration.test.ts
  • packages/validators/src/audit.ts
  • packages/validators/src/projects.ts
  • packages/validators/src/tests/projects.test.ts
💤 Files with no reviewable changes (6)
  • packages/validators/src/projects.ts
  • packages/api/src/utils/projects/challenge-labels.ts
  • packages/api/src/utils/projects/challenge-configuration.ts
  • packages/db/src/schemas/knight-hacks.ts
  • packages/db/drizzle/0053_challenge_groups.sql
  • packages/api/src/routers/projects.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread packages/api/src/routers/project-challenges.ts
@DVidal1205
DVidal1205 merged commit 9bf477d into main Sep 9, 2026
13 checks passed
@DVidal1205
DVidal1205 deleted the fix/first-time-hacker-room-tagging branch September 9, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Change modifies code in the global API/tRPC package Blade Change modifies code in Blade app Database Change modifies code in the DB package Feature New Feature or Request Major Big change - 2+ reviewers required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add configurable challenge groups for project judging

3 participants