Add diff viewer line marking and submitting fixes by line

This commit is contained in:
Oracle 2026-04-17 13:09:06 +02:00
parent b4b7d989cb
commit 6596dc6d9b
Signed by: Oracle
SSH key fingerprint: SHA256:x4/RtnjUyuHkdvmwNDsWSfcfF1V5PNr3OpriZqOvCX8
15 changed files with 1610 additions and 82 deletions

154
.astro/content.d.ts vendored Normal file
View file

@ -0,0 +1,154 @@
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof DataEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof DataEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof DataEntryMap>(
entry: DataEntryMap[C][string],
): Promise<RenderResult>;
export function reference<
C extends
| keyof DataEntryMap
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
| (string & {}),
>(
collection: C,
): import('astro/zod').ZodPipe<
import('astro/zod').ZodString,
import('astro/zod').ZodTransform<
C extends keyof DataEntryMap
? {
collection: C;
id: string;
}
: never,
string
>
>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
type InferLoaderSchema<
C extends keyof DataEntryMap,
L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
> = L extends { schema: import('astro/zod').ZodSchema }
? import('astro/zod').infer<L['schema']>
: any;
type DataEntryMap = {
};
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = never;
export type LiveContentConfig = never;
}

1
.astro/types.d.ts vendored
View file

@ -1 +1,2 @@
/// <reference types="astro/client" /> /// <reference types="astro/client" />
/// <reference path="content.d.ts" />

47
AGENTS.md Normal file
View file

@ -0,0 +1,47 @@
# PR Dojo — Agent Guide
## Project
**Code Review Hunter** ("PR Dojo") — an Astro site where users practice finding bugs in rejected PR code, earn XP, and submit fixes. Currently a static Astro frontend; backend (Quarkus or Node.js+SQLite) is planned per `full_plan.md`.
## Developer Commands
```
bun install # install deps
bun dev # start dev server at localhost:4321
bun build # static build → dist/
bun preview # preview the build locally
bun astro ... # run Astro CLI (add, check, etc.)
```
**Use `bun`, not npm or npx.**
## Tech Stack
- **Astro 6** with **Tailwind CSS v4** via `@tailwindcss/vite` plugin (not the PostCSS pipeline)
- **TypeScript** strict mode via `astro/tsconfigs/strict`
- **Node >= 22.12.0** (enforced in `package.json` engines)
- No test framework, linter, or formatter configured yet
## Architecture
```
src/
data/challenges.json # static challenge data (id, code, hints, expectedLines, expectedPatch)
pages/
index.astro # challenge listing + hero
profile.astro # user profile / XP dashboard
challenges/[slug].astro # single challenge view with DiffViewer
components/
DiffViewer.astro # side-by-side code diff UI
Welcome.astro # legacy starter component
layouts/Layout.astro # root layout wrapping header/nav/footer
styles/global.css # global styles
```
## Challenge Data Schema
Each entry in `src/data/challenges.json`:
- `id`, `title`, `repository`, `baseSha`, `difficulty` (1-5), `xpValue`, `bugType`
- `file`, `code` (buggy source), `hints` (array of "Line N: description")
- `expectedLines` (array of line numbers), `expectedPatch` (unified diff string)
## Key Constraints
- Tailwind v4 uses the Vite plugin, not PostCSS. Do not add `tailwind.config.js` or `@layer` directives — they won't work.
- Astro SSR renders everything client-side by default. Add `client:*` directives only when interactivity is needed.
- `full_plan.md` documents the planned backend architecture and MVP roadmap. It is a reference, not a spec to implement blindly.
- No CI, no pre-commit hooks, no test runner. If you add tests, document the command in this file.

1
dist/_astro/Layout.D3ovbguN.css vendored Normal file

File diff suppressed because one or more lines are too long

212
dist/challenges/1/index.html vendored Normal file

File diff suppressed because one or more lines are too long

211
dist/challenges/2/index.html vendored Normal file
View file

@ -0,0 +1,211 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #2</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Null Pointer Dereference in User Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>expressjs/express</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>def5678</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Null Pointer</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+75 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-f5n3kgt" class="diff-viewer-container" data-dv-id="dv-f5n3kgt" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function getUserProfile(userId) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> const user = database.findUser(userId); </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> return { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• No null check before accessing user properties </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> name: user.name, </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> email: user.email, </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> profile: user.profile.displayName </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Potential null pointer on user.profile </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> }; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 3: No null check before accessing user properties</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 6: Potential null pointer on user.profile</span> </li> </ul> </div> </div> </div> <script client:load>
(() => {
const el = document.querySelector('[data-dv-id]');
if (!el) return;
const buggedLines = new Set();
function updateBuggedState() {
const rows = el.querySelectorAll('tr[data-line]');
rows.forEach(row => {
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
row.classList.toggle('bugged', buggedLines.has(lineNum));
});
}
el.addEventListener('click', function(e) {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const row = target.closest('tr[data-line]');
if (!row) return;
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
if (buggedLines.has(lineNum)) {
buggedLines.delete(lineNum);
} else {
buggedLines.add(lineNum);
}
updateBuggedState();
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
bubbles: true,
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
}));
});
})();
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Line-by-line fix:
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Or provide as unified diff:
</label> <textarea id="diff-textarea" placeholder="- old line
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
(() => {
const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container');
const markedLinesTags = document.getElementById('marked-lines-tags');
const markedLinesSummary = document.getElementById('marked-lines-summary');
const buggedCountNum = document.getElementById('bugged-count-num');
const submitBtn = document.getElementById('submit-fix-btn');
const clearBtn = document.getElementById('clear-all-btn');
const diffTextarea = document.getElementById('diff-textarea');
const submissionMessage = document.getElementById('submission-message');
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
function updateUI() {
const arr = [...buggedLines].sort((a, b) => a - b);
buggedCountNum.textContent = arr.length.toString();
if (markedLinesTags) {
markedLinesTags.innerHTML = '';
arr.forEach(lineNum => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
tag.textContent = `Line ${lineNum}`;
tag.addEventListener('click', () => {
showLineFixEditor(lineNum);
});
markedLinesTags.appendChild(tag);
});
}
if (markedLinesSummary) {
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
}
if (fixEditorPlaceholder) {
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
}
if (submitBtn) {
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
}
}
function showLineFixEditor(lineNum) {
if (!fixEditorContainer) return;
const existing = document.getElementById(`fix-editor-${lineNum}`);
if (existing) {
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
existing.querySelector('textarea')?.focus();
return;
}
const editorDiv = document.createElement('div');
editorDiv.id = `fix-editor-${lineNum}`;
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
const header = document.createElement('div');
header.className = 'flex items-center justify-between mb-2';
const label = document.createElement('span');
label.className = 'text-sm font-medium text-[#f85149]';
label.textContent = `Line ${lineNum} — Your fix:`;
const removeBtn = document.createElement('button');
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
removeBtn.textContent = '✕';
removeBtn.addEventListener('click', () => {
buggedLines.delete(lineNum);
editorDiv.remove();
updateUI();
});
header.appendChild(label);
header.appendChild(removeBtn);
const textarea = document.createElement('textarea');
textarea.rows = 3;
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
textarea.placeholder = 'Enter the corrected line...';
textarea.addEventListener('input', updateUI);
editorDiv.appendChild(header);
editorDiv.appendChild(textarea);
fixEditorContainer.appendChild(editorDiv);
}
// Listen for bugged-line-toggle events from DiffViewer
document.addEventListener('bugged-line-toggle', (e) => {
const detail = e.detail;
if (detail.bugged) {
buggedLines.add(detail.line);
showLineFixEditor(detail.line);
} else {
buggedLines.delete(detail.line);
const editor = document.getElementById(`fix-editor-${detail.line}`);
if (editor) editor.remove();
}
updateUI();
});
// Diff textarea input
if (diffTextarea) {
diffTextarea.addEventListener('input', updateUI);
}
// Submit
if (submitBtn) {
submitBtn.addEventListener('click', () => {
const lineFixes = {};
buggedLines.forEach(lineNum => {
const editor = document.getElementById(`fix-editor-${lineNum}`);
if (editor) {
const ta = editor.querySelector('textarea');
if (ta?.value.trim()) {
lineFixes[lineNum] = ta.value.trim();
}
}
});
const unifiedDiff = diffTextarea?.value.trim() || '';
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
if (submissionMessage) {
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
submissionMessage.classList.remove('hidden');
}
return;
}
// Show success message
if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
submissionMessage.classList.remove('hidden');
}
});
}
// Clear all
if (clearBtn) {
clearBtn.addEventListener('click', () => {
buggedLines.clear();
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
editors?.forEach(e => e.remove());
if (diffTextarea) diffTextarea.value = '';
if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI();
// Reset bugged state in DiffViewer
document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged');
});
});
}
updateUI();
})();
</script>

212
dist/challenges/3/index.html vendored Normal file
View file

@ -0,0 +1,212 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #3</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Race Condition in Counter Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>rails/rails</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>ghi9012</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Concurrency</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+150 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-mubau9j" class="diff-viewer-container" data-dv-id="dv-mubau9j" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> class Counter { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Line 12: Same issue with decrement operation </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> constructor() { </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> this.value = 0; </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> increment() { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value + 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Race condition when increment called concurrently </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="10" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 10 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 10 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="11" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 11 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 11 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> decrement() { </span> </td> </tr><tr data-line="12" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 12 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 12 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value - 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Same issue with decrement operation </span> </td> </tr><tr data-line="13" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 13 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 13 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="14" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 14 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 14 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="15" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 15 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 15 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 7: Race condition when increment called concurrently</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 12: Same issue with decrement operation</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>No synchronization mechanism for thread safety</span> </li> </ul> </div> </div> </div> <script client:load>
(() => {
const el = document.querySelector('[data-dv-id]');
if (!el) return;
const buggedLines = new Set();
function updateBuggedState() {
const rows = el.querySelectorAll('tr[data-line]');
rows.forEach(row => {
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
row.classList.toggle('bugged', buggedLines.has(lineNum));
});
}
el.addEventListener('click', function(e) {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const row = target.closest('tr[data-line]');
if (!row) return;
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
if (buggedLines.has(lineNum)) {
buggedLines.delete(lineNum);
} else {
buggedLines.add(lineNum);
}
updateBuggedState();
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
bubbles: true,
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
}));
});
})();
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Line-by-line fix:
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Or provide as unified diff:
</label> <textarea id="diff-textarea" placeholder="- old line
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
(() => {
const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container');
const markedLinesTags = document.getElementById('marked-lines-tags');
const markedLinesSummary = document.getElementById('marked-lines-summary');
const buggedCountNum = document.getElementById('bugged-count-num');
const submitBtn = document.getElementById('submit-fix-btn');
const clearBtn = document.getElementById('clear-all-btn');
const diffTextarea = document.getElementById('diff-textarea');
const submissionMessage = document.getElementById('submission-message');
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
function updateUI() {
const arr = [...buggedLines].sort((a, b) => a - b);
buggedCountNum.textContent = arr.length.toString();
if (markedLinesTags) {
markedLinesTags.innerHTML = '';
arr.forEach(lineNum => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
tag.textContent = `Line ${lineNum}`;
tag.addEventListener('click', () => {
showLineFixEditor(lineNum);
});
markedLinesTags.appendChild(tag);
});
}
if (markedLinesSummary) {
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
}
if (fixEditorPlaceholder) {
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
}
if (submitBtn) {
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
}
}
function showLineFixEditor(lineNum) {
if (!fixEditorContainer) return;
const existing = document.getElementById(`fix-editor-${lineNum}`);
if (existing) {
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
existing.querySelector('textarea')?.focus();
return;
}
const editorDiv = document.createElement('div');
editorDiv.id = `fix-editor-${lineNum}`;
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
const header = document.createElement('div');
header.className = 'flex items-center justify-between mb-2';
const label = document.createElement('span');
label.className = 'text-sm font-medium text-[#f85149]';
label.textContent = `Line ${lineNum} — Your fix:`;
const removeBtn = document.createElement('button');
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
removeBtn.textContent = '✕';
removeBtn.addEventListener('click', () => {
buggedLines.delete(lineNum);
editorDiv.remove();
updateUI();
});
header.appendChild(label);
header.appendChild(removeBtn);
const textarea = document.createElement('textarea');
textarea.rows = 3;
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
textarea.placeholder = 'Enter the corrected line...';
textarea.addEventListener('input', updateUI);
editorDiv.appendChild(header);
editorDiv.appendChild(textarea);
fixEditorContainer.appendChild(editorDiv);
}
// Listen for bugged-line-toggle events from DiffViewer
document.addEventListener('bugged-line-toggle', (e) => {
const detail = e.detail;
if (detail.bugged) {
buggedLines.add(detail.line);
showLineFixEditor(detail.line);
} else {
buggedLines.delete(detail.line);
const editor = document.getElementById(`fix-editor-${detail.line}`);
if (editor) editor.remove();
}
updateUI();
});
// Diff textarea input
if (diffTextarea) {
diffTextarea.addEventListener('input', updateUI);
}
// Submit
if (submitBtn) {
submitBtn.addEventListener('click', () => {
const lineFixes = {};
buggedLines.forEach(lineNum => {
const editor = document.getElementById(`fix-editor-${lineNum}`);
if (editor) {
const ta = editor.querySelector('textarea');
if (ta?.value.trim()) {
lineFixes[lineNum] = ta.value.trim();
}
}
});
const unifiedDiff = diffTextarea?.value.trim() || '';
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
if (submissionMessage) {
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
submissionMessage.classList.remove('hidden');
}
return;
}
// Show success message
if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
submissionMessage.classList.remove('hidden');
}
});
}
// Clear all
if (clearBtn) {
clearBtn.addEventListener('click', () => {
buggedLines.clear();
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
editors?.forEach(e => e.remove());
if (diffTextarea) diffTextarea.value = '';
if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI();
// Reset bugged state in DiffViewer
document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged');
});
});
}
updateUI();
})();
</script>

211
dist/challenges/4/index.html vendored Normal file
View file

@ -0,0 +1,211 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #4</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>SQL Injection Vulnerability</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★★</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>django/django</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>jkl3456</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Security</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★★</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+200 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-ikbmd7r" class="diff-viewer-container" data-dv-id="dv-ikbmd7r" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function findUserByUsername(username) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `SELECT * FROM users WHERE username = &#39;${username}&#39;`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Direct string interpolation allows SQL injection </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function deleteUser(userId) { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `DELETE FROM users WHERE id = ${userId}`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Second SQL injection vulnerability in delete operation </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 2: Direct string interpolation allows SQL injection</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 7: Second SQL injection vulnerability in delete operation</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Should use parameterized queries instead</span> </li> </ul> </div> </div> </div> <script client:load>
(() => {
const el = document.querySelector('[data-dv-id]');
if (!el) return;
const buggedLines = new Set();
function updateBuggedState() {
const rows = el.querySelectorAll('tr[data-line]');
rows.forEach(row => {
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
row.classList.toggle('bugged', buggedLines.has(lineNum));
});
}
el.addEventListener('click', function(e) {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const row = target.closest('tr[data-line]');
if (!row) return;
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
if (buggedLines.has(lineNum)) {
buggedLines.delete(lineNum);
} else {
buggedLines.add(lineNum);
}
updateBuggedState();
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
bubbles: true,
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
}));
});
})();
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Line-by-line fix:
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Or provide as unified diff:
</label> <textarea id="diff-textarea" placeholder="- old line
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
(() => {
const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container');
const markedLinesTags = document.getElementById('marked-lines-tags');
const markedLinesSummary = document.getElementById('marked-lines-summary');
const buggedCountNum = document.getElementById('bugged-count-num');
const submitBtn = document.getElementById('submit-fix-btn');
const clearBtn = document.getElementById('clear-all-btn');
const diffTextarea = document.getElementById('diff-textarea');
const submissionMessage = document.getElementById('submission-message');
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
function updateUI() {
const arr = [...buggedLines].sort((a, b) => a - b);
buggedCountNum.textContent = arr.length.toString();
if (markedLinesTags) {
markedLinesTags.innerHTML = '';
arr.forEach(lineNum => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
tag.textContent = `Line ${lineNum}`;
tag.addEventListener('click', () => {
showLineFixEditor(lineNum);
});
markedLinesTags.appendChild(tag);
});
}
if (markedLinesSummary) {
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
}
if (fixEditorPlaceholder) {
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
}
if (submitBtn) {
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
}
}
function showLineFixEditor(lineNum) {
if (!fixEditorContainer) return;
const existing = document.getElementById(`fix-editor-${lineNum}`);
if (existing) {
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
existing.querySelector('textarea')?.focus();
return;
}
const editorDiv = document.createElement('div');
editorDiv.id = `fix-editor-${lineNum}`;
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
const header = document.createElement('div');
header.className = 'flex items-center justify-between mb-2';
const label = document.createElement('span');
label.className = 'text-sm font-medium text-[#f85149]';
label.textContent = `Line ${lineNum} — Your fix:`;
const removeBtn = document.createElement('button');
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
removeBtn.textContent = '✕';
removeBtn.addEventListener('click', () => {
buggedLines.delete(lineNum);
editorDiv.remove();
updateUI();
});
header.appendChild(label);
header.appendChild(removeBtn);
const textarea = document.createElement('textarea');
textarea.rows = 3;
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
textarea.placeholder = 'Enter the corrected line...';
textarea.addEventListener('input', updateUI);
editorDiv.appendChild(header);
editorDiv.appendChild(textarea);
fixEditorContainer.appendChild(editorDiv);
}
// Listen for bugged-line-toggle events from DiffViewer
document.addEventListener('bugged-line-toggle', (e) => {
const detail = e.detail;
if (detail.bugged) {
buggedLines.add(detail.line);
showLineFixEditor(detail.line);
} else {
buggedLines.delete(detail.line);
const editor = document.getElementById(`fix-editor-${detail.line}`);
if (editor) editor.remove();
}
updateUI();
});
// Diff textarea input
if (diffTextarea) {
diffTextarea.addEventListener('input', updateUI);
}
// Submit
if (submitBtn) {
submitBtn.addEventListener('click', () => {
const lineFixes = {};
buggedLines.forEach(lineNum => {
const editor = document.getElementById(`fix-editor-${lineNum}`);
if (editor) {
const ta = editor.querySelector('textarea');
if (ta?.value.trim()) {
lineFixes[lineNum] = ta.value.trim();
}
}
});
const unifiedDiff = diffTextarea?.value.trim() || '';
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
if (submissionMessage) {
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
submissionMessage.classList.remove('hidden');
}
return;
}
// Show success message
if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
submissionMessage.classList.remove('hidden');
}
});
}
// Clear all
if (clearBtn) {
clearBtn.addEventListener('click', () => {
buggedLines.clear();
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
editors?.forEach(e => e.remove());
if (diffTextarea) diffTextarea.value = '';
if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI();
// Reset bugged state in DiffViewer
document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged');
});
});
}
updateUI();
})();
</script>

211
dist/challenges/5/index.html vendored Normal file
View file

@ -0,0 +1,211 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #5</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Infinite Loop in Data Processing</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>angular/angular</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>mno7890</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Logic Error</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+100 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-qgi8fcj" class="diff-viewer-container" data-dv-id="dv-qgi8fcj" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function processItems(items) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> let index = 0; </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> while (index &lt; items.length) { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Line 3-6: Infinite loop - index never increments </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> console.log(items[index]); </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> // Missing: index++ </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa>
• Missing increment statement causes endless iteration </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return &#39;done&#39;; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 3-6: Infinite loop - index never increments</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 5: Missing increment statement causes endless iteration</span> </li> </ul> </div> </div> </div> <script client:load>
(() => {
const el = document.querySelector('[data-dv-id]');
if (!el) return;
const buggedLines = new Set();
function updateBuggedState() {
const rows = el.querySelectorAll('tr[data-line]');
rows.forEach(row => {
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
row.classList.toggle('bugged', buggedLines.has(lineNum));
});
}
el.addEventListener('click', function(e) {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const row = target.closest('tr[data-line]');
if (!row) return;
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
if (buggedLines.has(lineNum)) {
buggedLines.delete(lineNum);
} else {
buggedLines.add(lineNum);
}
updateBuggedState();
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
bubbles: true,
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
}));
});
})();
</script> <!-- Fix Submission --> <div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-7fgtuneg> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between" data-astro-cid-7fgtuneg> <h2 class="text-lg font-semibold text-[#c9d1d9]" data-astro-cid-7fgtuneg>Submit Your Fix</h2> <div id="bugged-count" class="text-sm text-[#8b949e]" data-astro-cid-7fgtuneg>
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold" data-astro-cid-7fgtuneg>0</span> </div> </div> <!-- Marked lines summary --> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden" data-astro-cid-7fgtuneg> <p class="text-sm text-[#8b949e] mb-2" data-astro-cid-7fgtuneg>Marked lines:</p> <div id="marked-lines-tags" class="flex flex-wrap gap-2" data-astro-cid-7fgtuneg></div> </div> <!-- Inline fix editor --> <div class="p-4" data-astro-cid-7fgtuneg> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Line-by-line fix:
</label> <p class="text-xs text-[#8b949e] mb-3" data-astro-cid-7fgtuneg>Click a marked line number below to edit its fix.</p> <div id="fix-editor-container" class="space-y-2" data-astro-cid-7fgtuneg> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic" data-astro-cid-7fgtuneg>Click on lines in the diff above to mark them, then add your fixes here.</p> </div> </div> <!-- Unified diff textarea --> <div class="mb-4" data-astro-cid-7fgtuneg> <label class="block text-sm font-medium text-[#c9d1d9] mb-2" data-astro-cid-7fgtuneg>
Or provide as unified diff:
</label> <textarea id="diff-textarea" placeholder="- old line
+ new line" rows="6" class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" data-astro-cid-7fgtuneg></textarea> </div> <div class="flex gap-3" data-astro-cid-7fgtuneg> <button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load>
(() => {
const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container');
const markedLinesTags = document.getElementById('marked-lines-tags');
const markedLinesSummary = document.getElementById('marked-lines-summary');
const buggedCountNum = document.getElementById('bugged-count-num');
const submitBtn = document.getElementById('submit-fix-btn');
const clearBtn = document.getElementById('clear-all-btn');
const diffTextarea = document.getElementById('diff-textarea');
const submissionMessage = document.getElementById('submission-message');
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
function updateUI() {
const arr = [...buggedLines].sort((a, b) => a - b);
buggedCountNum.textContent = arr.length.toString();
if (markedLinesTags) {
markedLinesTags.innerHTML = '';
arr.forEach(lineNum => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
tag.textContent = `Line ${lineNum}`;
tag.addEventListener('click', () => {
showLineFixEditor(lineNum);
});
markedLinesTags.appendChild(tag);
});
}
if (markedLinesSummary) {
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
}
if (fixEditorPlaceholder) {
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
}
if (submitBtn) {
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
}
}
function showLineFixEditor(lineNum) {
if (!fixEditorContainer) return;
const existing = document.getElementById(`fix-editor-${lineNum}`);
if (existing) {
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
existing.querySelector('textarea')?.focus();
return;
}
const editorDiv = document.createElement('div');
editorDiv.id = `fix-editor-${lineNum}`;
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
const header = document.createElement('div');
header.className = 'flex items-center justify-between mb-2';
const label = document.createElement('span');
label.className = 'text-sm font-medium text-[#f85149]';
label.textContent = `Line ${lineNum} — Your fix:`;
const removeBtn = document.createElement('button');
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
removeBtn.textContent = '✕';
removeBtn.addEventListener('click', () => {
buggedLines.delete(lineNum);
editorDiv.remove();
updateUI();
});
header.appendChild(label);
header.appendChild(removeBtn);
const textarea = document.createElement('textarea');
textarea.rows = 3;
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
textarea.placeholder = 'Enter the corrected line...';
textarea.addEventListener('input', updateUI);
editorDiv.appendChild(header);
editorDiv.appendChild(textarea);
fixEditorContainer.appendChild(editorDiv);
}
// Listen for bugged-line-toggle events from DiffViewer
document.addEventListener('bugged-line-toggle', (e) => {
const detail = e.detail;
if (detail.bugged) {
buggedLines.add(detail.line);
showLineFixEditor(detail.line);
} else {
buggedLines.delete(detail.line);
const editor = document.getElementById(`fix-editor-${detail.line}`);
if (editor) editor.remove();
}
updateUI();
});
// Diff textarea input
if (diffTextarea) {
diffTextarea.addEventListener('input', updateUI);
}
// Submit
if (submitBtn) {
submitBtn.addEventListener('click', () => {
const lineFixes = {};
buggedLines.forEach(lineNum => {
const editor = document.getElementById(`fix-editor-${lineNum}`);
if (editor) {
const ta = editor.querySelector('textarea');
if (ta?.value.trim()) {
lineFixes[lineNum] = ta.value.trim();
}
}
});
const unifiedDiff = diffTextarea?.value.trim() || '';
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
if (submissionMessage) {
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
submissionMessage.classList.remove('hidden');
}
return;
}
// Show success message
if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
submissionMessage.classList.remove('hidden');
}
});
}
// Clear all
if (clearBtn) {
clearBtn.addEventListener('click', () => {
buggedLines.clear();
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
editors?.forEach(e => e.remove());
if (diffTextarea) diffTextarea.value = '';
if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI();
// Reset bugged state in DiffViewer
document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged');
});
});
}
updateUI();
})();
</script>

BIN
dist/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

9
dist/favicon.svg vendored Normal file
View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

21
dist/index.html vendored Normal file

File diff suppressed because one or more lines are too long

3
dist/profile/index.html vendored Normal file
View file

@ -0,0 +1,3 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen"> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]"> <div class="max-w-6xl mx-auto px-4 py-3"> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">← Back to Challenges</a> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6"> <!-- Profile Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center gap-6"> <div class="w-20 h-20 bg-[#21262d] rounded-full flex items-center justify-center"> <svg class="w-12 h-12 text-[#8b949e]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> </svg> </div> <div> <h1 class="text-2xl font-semibold text-[#c9d1d9]">Anonymous User</h1> <p class="text-[#8b949e] text-sm mt-1">Code Review Practitioner</p> </div> </div> </div> <!-- Stats Grid --> <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"></path> </svg> <span class="text-[#8b949e] text-sm">Total XP</span> </div> <div class="text-3xl font-bold text-[#58a6ff]">0</div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#a5d6ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Challenges Solved</span> </div> <div class="text-3xl font-bold text-[#c9d1d9]">0<span class="text-[#8b949e] text-xl">/5</span></div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#79c0ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Current Streak</span> </div> <div class="text-3xl font-bold text-[#79c0ff]">0<span class="text-[#8b949e] text-xl"> days</span></div> </div> </div> <!-- Rank Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center justify-between mb-3"> <span class="text-[#c9d1d9] font-semibold">Current Rank: Novice</span> <span class="text-[#8b949e] text-sm">0 / 500 XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-full bg-[#58a6ff] w-0"></div> </div> <div class="mt-4 flex items-center justify-between"> <span class="text-[#c9d1d9] font-semibold">Next: Apprentice</span> <span class="text-[#8b949e] text-sm">500 / 1500 XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-full bg-[#a5d6ff] w-0"></div> </div> </div> <!-- Recent Activity --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6"> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Recent Activity</h2> <div class="text-center text-[#8b949e] py-8"> <svg class="w-12 h-12 mx-auto mb-3 text-[#30363d]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <p>No challenges completed yet</p> <a href="/challenges/1" class="text-[#58a6ff] mt-2 inline-block no-underline">
Start your first challenge →
</a> </div> </div> </main> </div> </body></html>

View file

@ -5,68 +5,67 @@ interface Props {
} }
const { code, hints } = Astro.props; const { code, hints } = Astro.props;
const id = `dv-${Math.random().toString(36).substring(2, 9)}`;
const lines = code.split('\n'); const lines = code.split('\n');
--- ---
<div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden"> <div id={id} class="diff-viewer-container" data-dv-id={id}>
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center gap-2"> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden">
<div class="flex gap-1.5"> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]">
<div class="w-3 h-3 rounded-full bg-[#ff5f57]"></div> <span class="text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
<div class="w-3 h-3 rounded-full bg-[#febc2e]"></div>
<div class="w-3 h-3 rounded-full bg-[#28c840]"></div>
</div> </div>
<span class="ml-2 text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
</div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full font-mono text-sm"> <table class="w-full font-mono text-sm">
<tbody> <tbody>
{lines.map((line, index) => { {lines.map((line, index) => {
const lineNum = index + 1; const lineNum = index + 1;
const hasHint = hints.some(h => h.includes(`Line ${lineNum}`)); const hasHint = hints.some(h => h.includes(`Line ${lineNum}`));
const hintText = hints.find(h => h.includes(`Line ${lineNum}`)); const hintText = hints.find(h => h.includes(`Line ${lineNum}`));
return ( return (
<tr <tr
class="group hover:bg-[#161b22] cursor-pointer transition-colors" data-line={lineNum}
title={`Mark line ${lineNum} as buggy`} class="group hover:bg-[#161b22] cursor-pointer transition-colors"
> title={`Mark line ${lineNum} as buggy`}
<td class="w-16 text-right pr-4 select-none"> >
<span class={`text-[#8b949e] ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}> <td class="w-16 text-right pr-4 select-none">
{lineNum} <span class={`text-[#8b949e] line-number ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}>
</span> {lineNum}
</td>
<td class="px-4 py-0.5 whitespace-pre">
<span class={`text-[#e6edf3] ${hasHint ? 'bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
{line || ' '}
</span>
{hasHint && (
<span class="ml-2 text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity">
• {hintText?.replace(`Line ${lineNum}: `, '')}
</span> </span>
)} </td>
</td> <td class="px-4 py-0.5 whitespace-pre">
</tr> <span class={`text-[#e6edf3] ${hasHint ? 'bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
); {line || ' '}
})} </span>
</tbody> {hasHint && (
</table> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity">
</div> • {hintText?.replace(`Line ${lineNum}: `, '')}
</span>
{hints.length > 0 && ( )}
<div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]"> </td>
<h3 class="text-sm font-semibold text-[#c9d1d9] mb-2">Hints:</h3> </tr>
<ul class="space-y-1"> );
{hints.map((hint) => ( })}
<li class="text-sm text-[#8b949e] flex items-start gap-2"> </tbody>
<span class="text-[#79c0ff] mt-0.5">•</span> </table>
<span>{hint}</span>
</li>
))}
</ul>
</div> </div>
)}
{hints.length > 0 && (
<div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]">
<h3 class="text-sm font-semibold text-[#c9d1d9] mb-2">Hints:</h3>
<ul class="space-y-1">
{hints.map((hint) => (
<li class="text-sm text-[#8b949e] flex items-start gap-2">
<span class="text-[#79c0ff] mt-0.5">•</span>
<span>{hint}</span>
</li>
))}
</ul>
</div>
)}
</div>
</div> </div>
<style> <style>
@ -79,4 +78,51 @@ const lines = code.split('\n');
td:first-child { td:first-child {
border-right: 1px solid #30363d; border-right: 1px solid #30363d;
} }
tr.bugged td:first-child {
border-left: 3px solid #f85149;
}
tr.bugged .line-number {
color: #f85149;
}
tr.bugged {
background-color: #f8514918;
}
</style> </style>
<script client:load>
(() => {
const el = document.querySelector('[data-dv-id]');
if (!el) return;
const buggedLines = new Set();
function updateBuggedState() {
const rows = el.querySelectorAll('tr[data-line]');
rows.forEach(row => {
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
row.classList.toggle('bugged', buggedLines.has(lineNum));
});
}
el.addEventListener('click', function(e) {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const row = target.closest('tr[data-line]');
if (!row) return;
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
if (buggedLines.has(lineNum)) {
buggedLines.delete(lineNum);
} else {
buggedLines.add(lineNum);
}
updateBuggedState();
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
bubbles: true,
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
}));
});
})();
</script>

View file

@ -75,41 +75,230 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
</div> </div>
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <!-- Code Viewer -->
<!-- Code Viewer --> <DiffViewer code={challenge.code} hints={challenge.hints} />
<div class="lg:col-span-2">
<DiffViewer code={challenge.code} hints={challenge.hints} /> <!-- Fix Submission -->
<div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden">
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between">
<h2 class="text-lg font-semibold text-[#c9d1d9]">Submit Your Fix</h2>
<div id="bugged-count" class="text-sm text-[#8b949e]">
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold">0</span>
</div>
</div> </div>
<!-- Controls Panel --> <!-- Marked lines summary -->
<div class="lg:col-span-1"> <div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden">
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-5 sticky top-6"> <p class="text-sm text-[#8b949e] mb-2">Marked lines:</p>
<h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Analysis Tools</h2> <div id="marked-lines-tags" class="flex flex-wrap gap-2"></div>
</div>
<div class="space-y-4">
<div class="text-sm text-[#8b949e] mb-4">
<p class="mb-2">Click on the left side of lines to mark them as buggy (like breakpoints).</p>
<p>Hover over marked lines to see hints.</p>
</div>
<div> <!-- Inline fix editor -->
<label class="block text-sm font-medium text-[#c9d1d9] mb-2"> <div class="p-4">
Your Fix (unified diff): <div class="mb-4">
</label> <label class="block text-sm font-medium text-[#c9d1d9] mb-2">
<textarea Line-by-line fix:
placeholder="- old line&#10;+ new line" </label>
rows={8} <p class="text-xs text-[#8b949e] mb-3">Click a marked line number below to edit its fix.</p>
class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono" <div id="fix-editor-container" class="space-y-2">
></textarea> <p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic">Click on lines in the diff above to mark them, then add your fixes here.</p>
</div>
<button class="w-full bg-[#1f6feb] hover:bg-[#388bfd] text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0">
Submit Fix
</button>
</div> </div>
</div> </div>
<!-- Unified diff textarea -->
<div class="mb-4">
<label class="block text-sm font-medium text-[#c9d1d9] mb-2">
Or provide as unified diff:
</label>
<textarea
id="diff-textarea"
placeholder="- old line&#10;+ new line"
rows={6}
class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono"
></textarea>
</div>
<div class="flex gap-3">
<button id="submit-fix-btn" class="flex-1 bg-[#1f6feb] hover:bg-[#388bfd] disabled:bg-[#21262d] disabled:text-[#484f58] disabled:cursor-not-allowed text-white font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0">
Submit Fix
</button>
<button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0">
Clear All
</button>
</div>
<div id="submission-message" class="mt-3 text-sm hidden"></div>
</div> </div>
</div> </div>
</main> </main>
</div> </div>
</Layout> </Layout>
<script client:load>
(() => {
const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container');
const markedLinesTags = document.getElementById('marked-lines-tags');
const markedLinesSummary = document.getElementById('marked-lines-summary');
const buggedCountNum = document.getElementById('bugged-count-num');
const submitBtn = document.getElementById('submit-fix-btn');
const clearBtn = document.getElementById('clear-all-btn');
const diffTextarea = document.getElementById('diff-textarea');
const submissionMessage = document.getElementById('submission-message');
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
function updateUI() {
const arr = [...buggedLines].sort((a, b) => a - b);
buggedCountNum.textContent = arr.length.toString();
if (markedLinesTags) {
markedLinesTags.innerHTML = '';
arr.forEach(lineNum => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
tag.textContent = `Line ${lineNum}`;
tag.addEventListener('click', () => {
showLineFixEditor(lineNum);
});
markedLinesTags.appendChild(tag);
});
}
if (markedLinesSummary) {
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
}
if (fixEditorPlaceholder) {
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
}
if (submitBtn) {
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
}
}
function showLineFixEditor(lineNum) {
if (!fixEditorContainer) return;
const existing = document.getElementById(`fix-editor-${lineNum}`);
if (existing) {
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
existing.querySelector('textarea')?.focus();
return;
}
const editorDiv = document.createElement('div');
editorDiv.id = `fix-editor-${lineNum}`;
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
const header = document.createElement('div');
header.className = 'flex items-center justify-between mb-2';
const label = document.createElement('span');
label.className = 'text-sm font-medium text-[#f85149]';
label.textContent = `Line ${lineNum} — Your fix:`;
const removeBtn = document.createElement('button');
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
removeBtn.textContent = '✕';
removeBtn.addEventListener('click', () => {
buggedLines.delete(lineNum);
editorDiv.remove();
updateUI();
});
header.appendChild(label);
header.appendChild(removeBtn);
const textarea = document.createElement('textarea');
textarea.rows = 3;
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
textarea.placeholder = 'Enter the corrected line...';
textarea.addEventListener('input', updateUI);
editorDiv.appendChild(header);
editorDiv.appendChild(textarea);
fixEditorContainer.appendChild(editorDiv);
}
// Listen for bugged-line-toggle events from DiffViewer
document.addEventListener('bugged-line-toggle', (e) => {
const detail = e.detail;
if (detail.bugged) {
buggedLines.add(detail.line);
showLineFixEditor(detail.line);
} else {
buggedLines.delete(detail.line);
const editor = document.getElementById(`fix-editor-${detail.line}`);
if (editor) editor.remove();
}
updateUI();
});
// Diff textarea input
if (diffTextarea) {
diffTextarea.addEventListener('input', updateUI);
}
// Submit
if (submitBtn) {
submitBtn.addEventListener('click', () => {
const lineFixes = {};
buggedLines.forEach(lineNum => {
const editor = document.getElementById(`fix-editor-${lineNum}`);
if (editor) {
const ta = editor.querySelector('textarea');
if (ta?.value.trim()) {
lineFixes[lineNum] = ta.value.trim();
}
}
});
const unifiedDiff = diffTextarea?.value.trim() || '';
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
if (submissionMessage) {
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
submissionMessage.classList.remove('hidden');
}
return;
}
// Show success message
if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]';
submissionMessage.classList.remove('hidden');
}
});
}
// Clear all
if (clearBtn) {
clearBtn.addEventListener('click', () => {
buggedLines.clear();
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
editors?.forEach(e => e.remove());
if (diffTextarea) diffTextarea.value = '';
if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI();
// Reset bugged state in DiffViewer
document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged');
});
});
}
updateUI();
})();
</script>
<style>
#fix-editor-container textarea {
resize: vertical;
min-height: 48px;
}
</style>