Skip to content

Conversation

@pan93412
Copy link
Member

@pan93412 pan93412 commented Jan 4, 2026

No description provided.

@vercel
Copy link
Contributor

vercel bot commented Jan 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
dbplay-app Ready Ready Preview, Comment Jan 4, 2026 6:35pm

Copilot AI review requested due to automatic review settings January 4, 2026 18:21
@pan93412 pan93412 self-assigned this Jan 4, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a cheat detection and overlay system for the application. The feature detects potential cheating behaviors (opening developer tools and leaving the application window) and blocks users with a warning overlay when cheating is detected.

Key changes include:

  • Added GraphQL schema for CheatRecord entity with queries and mutations to track and manage cheat records
  • Implemented cheat detection using the devtools-detect library and window blur events
  • Created a blocking overlay UI that displays when a user has unresolved cheat records

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
schema.graphql Defines CheatRecord type, connection types, mutations, and queries for cheat tracking
pnpm-lock.yaml Adds devtools-detect@4.0.2 dependency lock
package.json Adds devtools-detect library for detecting developer tools
lib/features.ts Adds LOCK_USER_ON_LEAVING feature flag with helper function
gql/graphql.ts Auto-generated TypeScript types for CheatRecord GraphQL schema
gql/gql.ts Auto-generated GraphQL document definitions for cheat-related queries and mutations
components/cheat/index.tsx Main cheat detection logic with event listeners and GraphQL integration
components/cheat/cheat-overlay.tsx Blocking UI overlay displayed when user is flagged for cheating
app/(app)/layout.tsx Integrates CheatWrapper component into application layout
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<div
className={`
flex min-h-svh flex-col items-center justify-center gap-6
bg-linear-to-br from-red-50 via-white to-red-100 p-6
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The CSS class 'bg-linear-to-br' appears to be invalid. Tailwind CSS uses 'bg-gradient-to-br' for bottom-right gradients. This will result in the gradient not being applied to the background.

Suggested change
bg-linear-to-br from-red-50 via-white to-red-100 p-6
bg-gradient-to-br from-red-50 via-white to-red-100 p-6

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +93
const devtoolsChangeHandler = async () => {
await createRecord("開啟開發者工具");
};

const screenLeaveHandler = async () => {
await createRecord("離開系統");
};

window.addEventListener("devtoolschange", devtoolsChangeHandler);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The 'devtoolschange' event from the devtools-detect library only fires when devtools are opened or closed, not on every state change. This means if devtools are already open when the page loads, it won't trigger the event. Consider checking the initial devtools state when the component mounts.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +103
useEffect(() => {
if (!enable) {
return;
}

let throttle = false;
let throttleTimeout: ReturnType<typeof setTimeout> | null = null;

const createRecord = async (reason: string) => {
if (throttle) {
return;
}
throttle = true;

await createMyCheatRecord({ variables: { reason } });

throttleTimeout = setTimeout(() => {
throttle = false;
throttleTimeout = null;
}, THROTTLE_TIME_MS);
};

const devtoolsChangeHandler = async () => {
await createRecord("開啟開發者工具");
};

const screenLeaveHandler = async () => {
await createRecord("離開系統");
};

window.addEventListener("devtoolschange", devtoolsChangeHandler);
window.addEventListener("blur", screenLeaveHandler);

return () => {
window.removeEventListener("devtoolschange", devtoolsChangeHandler);
window.removeEventListener("blur", screenLeaveHandler);
if (throttleTimeout) {
clearTimeout(throttleTimeout);
}
};
}, [enable, createMyCheatRecord]);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The useEffect dependency array includes 'createMyCheatRecord' which is a function from useMutation. This function has a stable identity and doesn't need to be in the dependency array. However, including it is not harmful. More importantly, the effect creates closures over the 'throttle' and 'throttleTimeout' variables which are reset on re-runs, potentially causing issues if the effect re-runs while throttling is active.

Copilot uses AI. Check for mistakes.
Comment on lines +500 to +503
If userID is not provided, the current user will be used.
For this case, you should have "me:write" scope.
If userID is provided, you should have "cheat_record:write" scope.
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The mutation documentation states that if userID is not provided, you need 'me:write' scope, and if userID is provided, you need 'cheat_record:write' scope. However, there's a potential security concern: allowing users to create cheat records for themselves with just 'me:write' scope could be exploited. Consider whether self-reporting should be allowed or if cheat records should only be created by authorized personnel.

Suggested change
If userID is not provided, the current user will be used.
For this case, you should have "me:write" scope.
If userID is provided, you should have "cheat_record:write" scope.
This mutation is intended to be used by authorized staff or systems.
Regardless of whether userID is provided, you must have "cheat_record:write" scope.
If userID is not provided, the implementation may infer the target user from context.

Copilot uses AI. Check for mistakes.
Comment on lines +537 to +538
"""Resolve a cheat record."""
resolveCheatRecord(cheatRecordID: ID!, reason: String!): Boolean!
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The 'resolveCheatRecord' mutation allows resolving cheat records but only requires a reason parameter. Consider adding validation to ensure only authorized personnel (e.g., administrators or proctors) can resolve cheat records, and potentially include additional fields like who resolved it and when.

Suggested change
"""Resolve a cheat record."""
resolveCheatRecord(cheatRecordID: ID!, reason: String!): Boolean!
"""Resolve a cheat record. Only authorized staff (e.g., administrators or proctors) may perform this action."""
resolveCheatRecord(cheatRecordID: ID!, reason: String!): Boolean! @scope(scope: "admin")

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +49
<Link
href="/"
className={`flex items-center gap-2 self-center font-medium`}
>
<div
className={`
flex size-6 items-center justify-center rounded-md
text-primary-foreground
`}
>
<Logo />
</div>
資料庫練功坊
</Link>
<Card className="min-w-md">
<CardHeader className="flex w-full flex-col items-center text-center">
<AlertTriangle className="mb-2 size-7 text-red-500" aria-hidden />
<CardTitle className="text-xl">您已經被系統判定為作弊</CardTitle>
<CardDescription className="space-y-2">
<p>
您的帳號已被系統判定為作弊,禁止繼續作答。作弊將會導致考試成績作廢,同時我們也會將您的記錄交給校方懲處。
</p>
<p>
如果您沒有作弊行為,請當場與監考官告知,我們判斷後可以解除這個狀態。
</p>
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center gap-4">
<Button asChild variant="outline">
<Link href="/login">重新登入</Link>
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The cheat overlay allows users to navigate back to the home page and even log in again, but this doesn't prevent them from bypassing the cheat detection. Since CheatWrapper is rendered inside the app layout after authentication, a user could potentially clear their session and log back in to reset their cheat status. Consider whether the overlay should prevent all navigation or if server-side enforcement is needed.

Copilot uses AI. Check for mistakes.
@pan93412 pan93412 merged commit 9d42e37 into main Jan 5, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants