Add Simon as sample app #24#28
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
Note
|
Adds serialize-javascript override to resolve high and moderate CVEs via vite-plugin-pwa 1.3.0 which uses workbox-build 7.4.1 and @rollup/plugin-terser 1.0.0 with serialize-javascript ^7.0.3. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 17
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib/game/credits-config.ts (1)
22-31:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
advance_scrollexceeds the 4-parameter limit — groupstart_z/end_zinto the bounds object
advance_scrolltakes 5 parameters. Sincestart_zandend_zare always produced together bymake_credits_scroll_bounds(which already returns{ start_z, end_z }), they can be collapsed into a singleboundsargument, bringing the count to 4 and aligning call sites with the return type of the companion function.♻️ Proposed fix
export function advance_scroll( current_z: number, delta: number, - start_z: number, - end_z: number, + bounds: { start_z: number; end_z: number }, speed: number, ): number { - const next_z = current_z - speed * delta - return next_z < end_z ? start_z : next_z + const next_z = current_z - speed * delta + return next_z < bounds.end_z ? bounds.start_z : next_z }As per coding guidelines,
**/*.{ts,tsx,js,svelte,svelte.ts}: "Function parameters must not exceed 4 parameters."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/game/credits-config.ts` around lines 22 - 31, The function advance_scroll currently accepts five parameters; change its signature to accept a single bounds object instead of separate start_z and end_z (e.g., advance_scroll(current_z, delta, bounds, speed)) and update its implementation to reference bounds.start_z and bounds.end_z; update all call sites that used make_credits_scroll_bounds (which returns { start_z, end_z }) to pass that object directly as the bounds argument so the function now has four parameters and aligns with the companion make_credits_scroll_bounds return shape.
🟡 Minor comments (9)
design/tech-icons.html-27-29 (1)
27-29:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMake the card responsive on narrow viewports.
At Line 28, a fixed
width: 640pxplus horizontal padding can overflow smaller screens. Use a fluid max-width pattern to avoid horizontal scrolling.Suggested fix
.card { background: `#161b22`; border: 1px solid `#30363d`; border-radius: 16px; - padding: 40px 48px; - width: 640px; + padding: 40px 48px; + width: min(640px, calc(100vw - 32px)); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@design/tech-icons.html` around lines 27 - 29, The rule that currently sets "padding: 40px 48px" and "width: 640px" will overflow narrow viewports; update that CSS rule by replacing the fixed width with a fluid pattern — set width: 100% and max-width: 640px (and ensure box-sizing: border-box is applied so the horizontal padding is included in the computed width) so the card stays responsive on small screens while capping its maximum size on larger viewports.src/lib/components/Logo.svelte-13-13 (1)
13-13:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHardcoded
DEFAULT_ARIA_LABELviolates the i18n guideline.
'Geometric JF Fusion Logo'is a user-visible accessibility string (read by screen readers viaaria-label). The coding guidelines require all user-visible strings to use i18n message keys — never hardcode them.🌐 Suggested fix
Replace the hardcoded default with a message key lookup (adjust the key name to match your locale files):
+import { m } from '$lib/paraglide/messages.js' + const DEFAULT_SIZE = 96 -const DEFAULT_ARIA_LABEL = 'Geometric JF Fusion Logo'-let { size = DEFAULT_SIZE, aria_label = DEFAULT_ARIA_LABEL }: Props = $props() +let { size = DEFAULT_SIZE, aria_label = m.logo_aria_label() }: Props = $props()Then add the key to every locale message file:
// en.json (and all other locales) "logo_aria_label": "Geometric JF Fusion Logo"As per coding guidelines: "All user-visible strings (labels, buttons, toasts, validation errors, page titles) must use message keys for i18n; never hardcode user-facing text. Add entries to all locale message files when implementing i18n."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/components/Logo.svelte` at line 13, DEFAULT_ARIA_LABEL in Logo.svelte is a hardcoded user-facing aria-label; replace it with an i18n lookup (e.g., call your locale/messages API or use the existing i18n helper to get the key "logo_aria_label") and use that returned string as the default for aria-label; update the Logo component to reference the i18n key instead of the literal and then add "logo_aria_label": "Geometric JF Fusion Logo" to every locale messages file so all locales supply the text.e2e/demo/playwright/page.e2e.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAssert the expected heading content, not just presence of any
<h1>.Line 5 currently passes if any visible
h1exists, even on the wrong content. Assert the expected heading text to make this smoke test deterministic.Proposed test assertion
- await expect(page.locator('h1')).toBeVisible() + await expect(page.getByRole('heading', { level: 1, name: 'Playwright e2e test demo' })).toBeVisible()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/demo/playwright/page.e2e.ts` at line 5, The test currently only asserts visibility of any <h1> via expect(page.locator('h1')).toBeVisible(); update this to assert the expected heading content instead by replacing the visibility-only assertion with a text assertion on the same locator (e.g., use expect(page.locator('h1')).toHaveText(...) or toContainText(...) with the precise expected heading string or regex) so the smoke test is deterministic; keep the same locator expression 'page.locator("h1")' and ensure the expected heading text matches the app copy.src/lib/simon/SimonBoard.svelte.spec.ts-28-66 (1)
28-66:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse
functioncallbacks in this spec block for consistency with repo rules.
describe/itcallbacks are arrow functions in this file.As per coding guidelines,
Use \function` syntax for function declarations, not arrow functions`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/simon/SimonBoard.svelte.spec.ts` around lines 28 - 66, Replace arrow-function callbacks in the test suite with named function expressions per repo style: change the describe callback (describe('SimonBoard', () => { ... })) and each it callback (it('renders without error ...', () => { ... })) to use function() { ... } syntax so the describe and it blocks use function callbacks instead of arrow functions; update all occurrences within SimonBoard.svelte.spec.ts (the describe('SimonBoard', ...) block and each it(...) test) accordingly.src/lib/simon/SimonScene.svelte.spec.ts-57-81 (1)
57-81:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse function syntax in test callbacks to satisfy repo rules.
describe/itcallbacks are arrow functions across this block, which conflicts with the repository rule.As per coding guidelines,
Use \function` syntax for function declarations, not arrow functions`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/simon/SimonScene.svelte.spec.ts` around lines 57 - 81, The test file uses arrow functions for test callbacks which violates the repo rule; update the callback syntax in the describe('SimonScene', ...) block and all its it(...) callbacks (the three it blocks: 'renders without error', 'calls make_credits_scroll_bounds with CREDITS_LINE_COUNT and HALF_D', and 'score module exposes all required score_data fields', plus the messages test) to use traditional function declarations (function() { ... }) instead of arrow functions so the describe and it callbacks conform to the repository guideline.src/routes/layout.css-12-12 (1)
12-12:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove quotes around
Orbitronto satisfy Stylelint.
font-family-name-quotesis failing on this declaration.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/routes/layout.css` at line 12, The font-family declaration using 'Orbitron' violates the stylelint rule font-family-name-quotes; update the declaration in layout.css by removing the quotes around Orbitron (e.g., change the font-family value to use Orbitron without quotes and keep any desired fallbacks like sans-serif) so the rule passes; locate the font-family declaration in the layout.css block (font-family: 'Orbitron';) and replace it with an unquoted font-family name.src/lib/messages/en.spec.ts-12-63 (1)
12-63:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSwitch test callbacks to
functionsyntax for guideline compliance.All
describe/itcallbacks here are arrow functions.As per coding guidelines,
Use \function` syntax for function declarations, not arrow functions`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/messages/en.spec.ts` around lines 12 - 63, The test file uses arrow-function callbacks for the describe/it blocks; change each arrow callback to a traditional function declaration so describe('messages', ...), it('contains all original keys for backward compatibility', ...), describe('base_messages', ...), it('contains no simon-specific keys', ...), describe('simon_messages', ...) and it('contains all simon-specific keys', ...) all use function() { ... } callbacks instead of () => { ... } to satisfy the guideline.src/lib/simon/SimonBoard.svelte-125-133 (1)
125-133:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRename
activetois_activeto satisfy the boolean naming convention.
{@constactive = is_lit(btn.color)}holds a boolean value but is missing the requiredis_prefix.As per coding guidelines: "Boolean variables must be prefixed with
is_,has_,should_,can_,will_, ordid_."🔧 Proposed fix
- {`@const` active = is_lit(btn.color)} + {`@const` is_active = is_lit(btn.color)} <T.MeshStandardMaterial - color={active ? lit : dim} - emissive={active ? lit : '#000000'} + color={is_active ? lit : dim} + emissive={is_active ? lit : '#000000'} emissiveIntensity={emissive_intensity} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/simon/SimonBoard.svelte` around lines 125 - 133, Rename the boolean template const `active` to `is_active` to follow the boolean naming convention: change the Svelte {`@const` active = is_lit(btn.color)} to {`@const` is_active = is_lit(btn.color)} and update all uses in this block (the T.MeshStandardMaterial props color and emissive where `active` is referenced) to use `is_active`; keep the existing calls to `btn_lit(btn)`, `btn_dim(btn)`, and the `emissive_intensity` prop unchanged.src/app.html-12-18 (1)
12-18:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd
.catch()toserviceWorker.register()promise to prevent silent failures.The
navigator.serviceWorker.register('/sw.js')call returns a promise, but it has no error handler. If registration fails, the error is swallowed silently and the user sees no indication in the console. Even though this is inline HTML, add a.catch()to log the failure:Suggested fix
<script> if ('serviceWorker' in navigator) { window.addEventListener('load', function () { - navigator.serviceWorker.register('/sw.js') + navigator.serviceWorker.register('/sw.js').catch(function (err) { + console.warn('SW registration failed:', err) + }) }) } </script>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app.html` around lines 12 - 18, The service worker registration promise (navigator.serviceWorker.register('/sw.js')) lacks error handling so failures are silent; update the inline load handler (window.addEventListener('load', ...)) to attach a .catch() to navigator.serviceWorker.register(...) and log the error (e.g., console.error with a clear message and the caught error) so registration failures surface in the console.
🧹 Nitpick comments (11)
design/tech-icons.html (1)
91-93: ⚡ Quick winImprove icon-label readability (size/contrast).
Line 92 uses
10pxtext with a muted color from Line 91; this is hard to read on dark backgrounds. Slightly increasing size and contrast would improve accessibility.Suggested fix
.icon-label { - color: `#6e7681`; - font-size: 10px; + color: `#8b949e`; + font-size: 12px; font-weight: 500; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@design/tech-icons.html` around lines 91 - 93, The icon label styling uses color and font-size that are too small and low-contrast (the CSS rules for color, font-size, font-weight shown); increase font-size from 10px to around 12px–13px and choose a higher-contrast color (e.g., a lighter muted shade or a semantic variable like --text-muted-strong) while keeping font-weight 500, so update the CSS declarations for color and font-size where those properties are defined to improve readability on dark backgrounds.sonar-project.properties (1)
14-16: 💤 Low valueConsider documenting the design/ exclusion.
The comment explains the rationale for excluding
.claude/andscripts/, but doesn't mentiondesign/. For maintainability, consider briefly noting thatdesign/hosts visual reference artifacts.📝 Suggested comment update
# Exclude upstream-synced bootstrap scripts and dev/CI tooling from analysis. # .claude/ hosts setup shell scripts synced from joshuafolkken/tasks (must not drift). # scripts/ hosts git / issue / overrides / telegram workflow tooling — not production code. +# design/ hosts visual reference artifacts and mockups — not production code. sonar.exclusions=.claude/**,scripts/**,design/**🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sonar-project.properties` around lines 14 - 16, The exclusion comment currently documents `.claude/` and `scripts/` but omits `design/`, so update the comment above the exclusions to mention that `design/` contains visual reference artifacts (mockups, assets) and is excluded from analysis for maintainability; locate the comment block that references `.claude/` and `scripts/` (the human-readable exclusion rationale in sonar-project.properties) and add a brief clause like "design/ hosts visual reference artifacts (mockups/assets) and is excluded" so future readers understand why `design/` is excluded.src/lib/components/Logo.svelte.spec.ts (1)
8-9: ⚡ Quick win
DEFAULT_SIZE_STRINGandDEFAULT_ARIA_LABELduplicate component internals — consider importing them.Both constants duplicate the component's own
DEFAULT_SIZEandDEFAULT_ARIA_LABELvalues. When the component's defaults change (or get refactored to i18n keys), these will silently diverge, causing confusing test failures that don't describe the actual regression.If the component exports its defaults (or its i18n keys), import and derive them here instead of hardcoding the values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/components/Logo.svelte.spec.ts` around lines 8 - 9, Tests duplicate the component's default constants (DEFAULT_SIZE_STRING and DEFAULT_ARIA_LABEL); instead import the canonical defaults exported by the component (e.g., DEFAULT_SIZE, DEFAULT_ARIA_LABEL or the component's exported defaults from Logo.svelte) and derive the test values from them (convert DEFAULT_SIZE to a string for DEFAULT_SIZE_STRING if needed) so the test follows the component's source of truth and won't diverge after refactors.src/lib/game/credits-config.ts (1)
13-33: ⚡ Quick winRemove redundant individual exports — rely solely on the
credits_scrollnamespace
make_credits_scroll_boundsandadvance_scrollare now exported both individually (lines 13 and 22) and as members of thecredits_scrollobject (line 33). This creates a dual public API and makes it easier to accidentally bypass the namespace grouping pattern.If test mocking requires direct access to
make_credits_scroll_bounds, mock thecredits_scrollnamespace object itself:vi.mock('../game/credits-config', () => ({ credits_scroll: { make_credits_scroll_bounds: vi.fn(), advance_scroll: vi.fn() }, // …other constants }))Then remove the
exportkeyword from the two function declarations, keeping the functions accessible only viacredits_scroll.As per coding guidelines,
**/*.{ts,tsx,js,svelte,svelte.ts}: "When a file contains multiple functions, group them into a namespace object withexport { my_module }syntax."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/game/credits-config.ts` around lines 13 - 33, Remove the redundant individual exports by turning the exported functions make_credits_scroll_bounds and advance_scroll into non-exported (module-private) functions and keep only the credits_scroll namespace object exported; specifically, remove the export keyword from the function declarations for make_credits_scroll_bounds and advance_scroll so callers and tests must import credits_scroll and access credits_scroll.make_credits_scroll_bounds / credits_scroll.advance_scroll, and update any imports/tests to mock or reference the credits_scroll object instead.src/routes/+layout.svelte (1)
23-38: 🏗️ Heavy liftPrefer Svelte bindings over direct DOM querying in
$effect.Manual
document.getElementById/querySelectorupdates are brittle and conflict with the repo’s Svelte DOM-manipulation restriction. Bind elements/state declaratively instead.As per coding guidelines,
DOM manipulation is restricted in Svelte components.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/routes/`+layout.svelte around lines 23 - 38, The $effect blocks directly query and mutate DOM (using LOADING_STATUS_ID, OVERLAY_ELEMENT_ID, OVERLAY_HIDDEN_CLASS, LOADING_PROGRESS_SELECTOR, LOADING_BAR_SELECTOR) which violates the project's Svelte DOM-manipulation rule; replace these imperative updates with declarative Svelte bindings: bind the DOM nodes via bind:this or use reactive expressions to interpolate loading.status_text and loading.progress into the template, use class:OVERLAY_HIDDEN_CLASS={...} on the overlay element driven by loading.is_visible, and bind:value on the <progress> (or use {loading.progress} text nodes) so the UI updates automatically instead of using document.getElementById/querySelector inside $effect. Ensure you remove the $effect blocks that perform manual DOM queries and update the template to reference the loading store/props directly.src/lib/simon/SimonScene.svelte.spec.ts (1)
68-80: ⚡ Quick winThese assertions mostly verify mock literals, not
SimonScenebehavior.The
score/messageschecks pass even ifSimonScenestops using those values. Consider asserting rendered output/props wiring instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/simon/SimonScene.svelte.spec.ts` around lines 68 - 80, The test currently just asserts the mock objects (score and messages) contain literal values instead of verifying SimonScene actually uses them; update the two "it" blocks to render the SimonScene component (use the existing test harness/render helper) with the score and messages props and then assert the rendered DOM or child component props reflect score.high_score, score.current_score, score.is_new_high_score, score.high_score_round, score.last_cleared_round and messages.score_high_score/messages.score_round/messages.score_current rather than checking the raw mock objects. Locate the specs referencing SimonScene, score, messages and replace the expect(...) on the mock with queries/assertions against the rendered output (textContent, getByText, or inspecting passed props on child components) so the test verifies actual prop wiring and rendering.src/hooks.server.spec.ts (1)
33-75: ⚡ Quick winExtract a
make_event()helper to eliminate six repeated restrictedasassertions.
{} as RequestEventappears six times across thehandletests. Each is both a restricted type assertion and duplicated code. A single typed helper removes the repetition and satisfies both guidelines.As per coding guidelines: "Type assertions using
asare restricted and should be minimized" and "No code duplication: extract to shared functions/modules immediately."♻️ Proposed fix
+function make_event(): RequestEvent { + return {} as RequestEvent +} + describe('handle', () => { it('adds X-Frame-Options: SAMEORIGIN', async () => { - const response = await handle({ event: {} as RequestEvent, resolve: make_resolve() }) + const response = await handle({ event: make_event(), resolve: make_resolve() }) ... }) it('adds X-Content-Type-Options: nosniff', async () => { - const response = await handle({ event: {} as RequestEvent, resolve: make_resolve() }) + const response = await handle({ event: make_event(), resolve: make_resolve() })Apply the same substitution to all six call sites.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks.server.spec.ts` around lines 33 - 75, Add a make_event() helper that returns a properly typed RequestEvent and replace the six occurrences of "{} as RequestEvent" in the handle tests with calls to make_event(); update test code that calls handle({ event: {} as RequestEvent, resolve: ... }) to handle({ event: make_event(), resolve: ... }) and ensure the helper is imported or declared in the spec (referencing the handle tests, the RequestEvent type and the make_resolve()/resolve mocks) so you remove the repeated restricted "as" assertions while keeping the same behavior.wrangler.jsonc (1)
14-24: Consider 10% sampling's impact on error visibility.Both
logs.head_sampling_rateandtraces.head_sampling_rateare set to0.1, meaning only 1 in 10 requests will be logged or traced. The sampling decision happens at request arrival, before your code executes — you can't selectively sample based on response status or other outcomes. This means ~90% of errors, exceptions, and slow requests will be silently discarded.To capture all errors while sampling successes, implement that logic in your Worker code: log errors unconditionally but use conditional logging for successes.
If full error coverage is needed for this sample app, raise
head_sampling_rateto1initially and tune down once traffic volume is understood.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wrangler.jsonc` around lines 14 - 24, The sampling rates in wrangler.jsonc ("observability.logs.head_sampling_rate" and "observability.traces.head_sampling_rate" set to 0.1) will drop ~90% of requests before your Worker code runs, hiding most errors; either increase those rates to 1 during initial testing or, better, implement unconditional error reporting in your Worker fetch handler (the global fetch event/function that handles requests) so that errors/exceptions are logged regardless of sampling while successes remain sampled—update the fetch handler to always emit error logs/traces and keep head_sampling_rate lower for normal success traffic.src/lib/simon/SimonBoard.svelte (1)
14-14: ⚡ Quick winExtract the three remaining magic number literals to named
UPPER_CASEconstants.
- Line 14:
36inMath.PI / 36is unexplained — the implicit meaning (5-degree gap in radians) is not obvious from the literal alone.- Line 111:
roughness={0.8}and Line 139:roughness={0.5}are inline magic numbers.As per coding guidelines: "All magic number literals except
0,1, and-1must be extracted to namedUPPER_CASEconstants."♻️ Proposed fix
+ const BUTTON_GAP_DIVISOR = 36 + const BACKING_ROUGHNESS = 0.8 + const CENTER_ROUGHNESS = 0.5 const BUTTON_GAP = Math.PI / BUTTON_GAP_DIVISOR- <T.MeshStandardMaterial color="#111111" roughness={0.8} /> + <T.MeshStandardMaterial color="#111111" roughness={BACKING_ROUGHNESS} />- <T.MeshStandardMaterial color="#222222" roughness={0.5} /> + <T.MeshStandardMaterial color="#222222" roughness={CENTER_ROUGHNESS} />Also applies to: 109-113, 137-140
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/simon/SimonBoard.svelte` at line 14, Extract the remaining magic numbers into UPPER_CASE constants and use them where referenced: replace the literal divisor 36 in the BUTTON_GAP calculation (const BUTTON_GAP = Math.PI / 36) with a named constant (e.g., BUTTON_GAP_DIVISOR or DEGREES_PER_GAP) so the 5-degree intent is explicit, and replace the inline roughness={0.8} and roughness={0.5} (used in the components around where roughness is passed) with named constants like ROUGHNESS_ACTIVE and ROUGHNESS_INACTIVE (or similar) and update their usage sites to reference those constants (ensure exported/defined at the top of SimonBoard.svelte alongside BUTTON_GAP)..github/workflows/auto-tag.yml (1)
25-34: 💤 Low valueEdge case:
git diff HEAD~1errors on a repo with a single commit.If
client_payload.shaever resolves to a root commit (no parent),git diff HEAD~1 --name-onlyfails and the step's exit code propagates without settingchanged.version_checkalready handles this case (lines 50, 62–66), so it would be consistent to do the same here — or simply forcechanged=truewhen no parent exists.♻️ Optional hardening
- name: Check if package.json changed id: pkg_changed run: | + if ! git rev-parse HEAD~1 >/dev/null 2>&1; then + echo "changed=true" >> $GITHUB_OUTPUT + echo "ℹ️ No parent commit, treating package.json as changed" + exit 0 + fi if git diff HEAD~1 --name-only | grep -q '^package.json$'; then echo "changed=true" >> $GITHUB_OUTPUT echo "✅ package.json changed in this commit" else echo "changed=false" >> $GITHUB_OUTPUT echo "ℹ️ package.json not changed, skipping tag creation" fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/auto-tag.yml around lines 25 - 34, The pkg_changed step can fail for a root commit because git diff HEAD~1 errors when HEAD has no parent; update the step (step id pkg_changed) to detect a root commit first (e.g., using git rev-parse --verify HEAD^ or git rev-list --max-parents=0 HEAD) and if no parent exists set "changed=true" and emit the same success message, otherwise run the existing git diff HEAD~1 --name-only check and set "changed=true"/"changed=false" as before so the step never exits with an error on single-commit repos.e2e/page.e2e.ts (1)
16-16: ⚡ Quick win
CHECK_SEEDandcompute_checklogic are duplicated fromscore.svelte.ts.
CHECK_SEED = 0x9e3779b9on line 16 and the inline computation on lines 267–270 replicate the logic ofcompute_checkinscore.svelte.ts. If the algorithm changes, both must be updated in sync.The cleanest fix is to extract
compute_checkandCHECK_SEEDinto a dependency-free utility (e.g.,src/lib/simon/score-check.ts) so both the score module and this E2E test can import from it without pulling in Svelte rune context.🔧 Proposed fix (after extracting to score-check.ts)
+import { compute_check, CHECK_SEED as SCORE_CHECK_SEED } from '../src/lib/simon/score-check' -const CHECK_SEED = 0x9e3779b9 ... -const stored_check = - (Math.imul(SAMPLE_HIGH_SCORE + 1, CHECK_SEED) ^ - Math.imul(SAMPLE_HIGH_ROUND + 1, CHECK_SEED >>> 1)) >>> - 0 +const stored_check = compute_check(SAMPLE_HIGH_SCORE, SAMPLE_HIGH_ROUND)As per coding guidelines: "No code duplication; extract repeated code to shared functions or modules immediately."
Also applies to: 267-270
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/page.e2e.ts` at line 16, Duplicate CHECK_SEED and compute_check logic: extract the constant CHECK_SEED and the compute_check function into a small dependency-free module (e.g., score-check.ts) and have both compute_check usages import from it; update e2e/page.e2e.ts to import CHECK_SEED and compute_check instead of defining them inline, and update score.svelte.ts to import the same exported symbols so there is a single source of truth for the algorithm.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.npmrc:
- Line 6: Replace the camelCase pnpm config key with kebab-case: change the
setting named confirmModulesPurge to confirm-modules-purge in the .npmrc so pnpm
recognizes it (leave the value as false for CI/automation).
In `@e2e/page.e2e.ts`:
- Line 1: The file e2e/page.e2e.ts exceeds the 300-line limit; split the large
test suite into smaller files so each stays under 300 lines — for example create
separate suites for "accessibility" tests, "security headers" tests, and
"loading-overlay" tests by moving the related describe/it blocks into new files
(e.g., page.accessibility.e2e.ts, page.security-headers.e2e.ts,
page.loading-overlay.e2e.ts), keep shared imports like readFileSync and any
helper functions in a shared helper module or re-import them, and ensure test
runner picks up the new files; after moving, remove the moved tests from
e2e/page.e2e.ts so its total lines drop below 300.
In `@package.json`:
- Around line 85-92: The devEngines change bumped pnpm from 10.x to 11.x but the
repo policy requires explicit user approval for devEngines changes; confirm with
the repository owner or PR author that upgrading packageManager (packageManager
and devEngines.packageManager.version) to "pnpm@11.0.8" was intentional and
approved, and if approved add an explicit note to the PR/commit message (or
revert the devEngines change back to the previous pinned version) so the change
is documented; ensure any follow-up commit references this approval before
merging.
In `@src/hooks.server.ts`:
- Around line 7-9: The CSP currently includes "script-src 'self' 'unsafe-inline'
blob:" and "style-src 'self' 'unsafe-inline'", which weakens XSS protection;
remove 'unsafe-inline' and implement nonce/hash-based CSP instead by generating
a per-request nonce in the SvelteKit handle hook (e.g., in src/hooks.server.ts),
attach the nonce to event.locals and the rendered page, update your templates to
add nonce attributes to inline <script> and <style> blocks, and change the
header values to include "nonce-{nonce}" (or appropriate hashes) so inline
assets are allowed only when presenting the generated nonce/hash.
In `@src/lib/simon/score.svelte.ts`:
- Line 86: Add explicit return types for both functions: annotate make_score_api
with a concrete return type (either an interface describing the API it returns
or an exported type alias) and annotate create_score to return ReturnType<typeof
make_score_api> (or define ScoreInstance before make_score_api and use that
type). Update the function signatures for make_score_api(...) and
create_score(...) to include these return-type annotations so all parameters and
returns are explicitly typed per guidelines.
- Around line 103-105: The returned API object currently uses arrow-value
properties for add_round_score and reset; change them to method shorthand
(function/method syntax) so add_round_score becomes a method declaration that
accepts (elapsed_ms: number, sequence_length: number, round: number): void and
calls add_round_score_impl(s, { elapsed_ms, sequence_length, round }, keys), and
change reset to a method reset(): void that calls reset_score_impl(s). Keep the
same identifiers (s, keys, add_round_score_impl, reset_score_impl) and return
structure, only replacing the arrow expressions with method syntax.
- Line 42: Replace the magic number 1000 used in the avg_s calculation with a
named UPPER_CASE constant (e.g., MS_PER_SECOND) and update the expression to use
that constant; either import MS_PER_SECOND from audio.ts if available or declare
a local const MS_PER_SECOND = 1000 at the top of the module, then change const
avg_s = elapsed_ms / 1000 / sequence_length to use MS_PER_SECOND (avg_s =
elapsed_ms / MS_PER_SECOND / sequence_length) so the literal is removed and
follows the project's magic-number rule.
In `@src/lib/simon/simon-flash.spec.ts`:
- Around line 128-133: The test uses unnecessary type assertions "as
ReturnType<typeof vi.fn>" when accessing mock call counts; remove these casts
and read the mock calls directly from
vi.mocked(simon_audio.play_tone).mock.calls.length for both the calls_before and
calls_after variables so Sonar no longer flags restricted "as" usage and the
test still asserts correctly against simon_audio.play_tone.
In `@src/lib/simon/simon-flash.ts`:
- Around line 25-27: The duplicate delay(ms: number): Promise<void> function in
simon-flash.ts and simon.svelte.ts should be extracted to a shared utility:
create a utils module that exports delay, remove the local delay definitions
from both simon-flash.ts and simon.svelte.ts, and import the exported delay in
each file (ensure the function is exported as export function delay(...) so
callers can import it by name). Make sure both modules use the imported delay
symbol and adjust any imports/exports accordingly.
In `@src/lib/simon/simon.svelte.ts`:
- Around line 34-36: The duplicate delay function (function name: delay) present
in simon.svelte.ts is the same as in simon-flash.ts — remove the local copy and
extract a single implementation into a shared utils module (e.g., simon
utils.ts) and update both simon.svelte.ts and simon-flash.ts to import delay
from that shared utils module; ensure exports/imports use the existing delay
signature (delay(ms: number): Promise<void>) and update any references to call
the imported function.
- Around line 193-196: The exported object uses arrow-function properties for
start, press, release, and reset; change them to ES method shorthand so they are
declared as start(), press(color), release(), and reset() methods that invoke
start_simon(s, t, score, colors), press_simon(s, color), release_simon(s, t,
score, colors), and reset_simon(s, t, score) respectively, preserving the same
parameter usage (s, t, score, colors) and typings (ButtonColor for press).
- Around line 39-43: Extract the magic numeric boundaries into UPPER_CASE
constants and use them in the comparisons: create named constants (e.g.
MAX_ROUNDS_1_5 = 5, MAX_ROUNDS_6_13 = 13, MAX_ROUNDS_14_20 = 20) and replace the
raw literals in the function that returns STEP_MS_1_5, STEP_MS_6_13,
STEP_MS_14_20, STEP_MS_21_PLUS so the if checks read like "if (len <=
MAX_ROUNDS_1_5) ...", "if (len <= MAX_ROUNDS_6_13) ...", and "if (len <=
MAX_ROUNDS_14_20) ...". Ensure the constants are exported or colocated per
module style and keep names UPPER_CASE to follow the guideline.
- Around line 162-198: make_simon_api is too long and lacks an explicit return
type; shorten it by collapsing each get accessor to a single-line form and add
an explicit return type (SimonInstance) to the function signature, ensuring the
SimonInstance type is declared before make_simon_api; keep the existing behavior
by returning the same properties and methods (start_simon, press_simon,
release_simon, reset_simon) and retaining parameter types SimonState,
SimonTimers, ScoreInstance, and ButtonColor.
In `@src/lib/simon/types.ts`:
- Around line 6-12: SimonBoardData currently types its phase field as string
allowing invalid values; change the type of the phase property from string to
the existing union type SimonPhase so the declaration for SimonBoardData.phase
uses SimonPhase (refer to the SimonBoardData type and the SimonPhase union in
this file) and update any imports/usages that rely on the broader string type if
needed to maintain type compatibility.
In `@src/routes/`+layout.svelte:
- Around line 19-21: The three initialization calls
switch_audio.init(click_url), game_state.set_alt(true), and
loading.set_step('initializing') are running during layout load and may execute
during SSR; move them into a client-only lifecycle by wrapping them in an
onMount callback (import onMount from 'svelte') or guard them with the browser
flag (import { browser } from '$app/environment') so they only run on the client
(e.g., onMount(() => { switch_audio.init(click_url); game_state.set_alt(true);
loading.set_step('initializing'); }) or if (browser) { ... }).
In `@src/routes/demo/`+page.svelte:
- Line 5: Replace the hardcoded anchor label "playwright" in the link (<a
href={resolve('/demo/playwright')}>playwright</a>) with a call to the project's
i18n message lookup using the key "demo.playwright" (use the app's message
helper such as $_/t/getMessage as appropriate) and import or reference that
helper in this Svelte page; also add the "demo.playwright" entry to the locale
files so the link renders a localized label.
In `@src/routes/demo/playwright/`+page.svelte:
- Line 1: Replace the hardcoded <h1>Playwright e2e test demo</h1> in the Svelte
page (+page.svelte) with an i18n lookup using your app's localization helper and
a new message key (for example "demo.playwright.title"); locate the <h1> element
and substitute the inline text with the i18n call used across the repo (e.g.,
the project’s $t, $_, or getMessage helper) and add the corresponding message
key/value to the messages file so the heading is sourced from localization.
---
Outside diff comments:
In `@src/lib/game/credits-config.ts`:
- Around line 22-31: The function advance_scroll currently accepts five
parameters; change its signature to accept a single bounds object instead of
separate start_z and end_z (e.g., advance_scroll(current_z, delta, bounds,
speed)) and update its implementation to reference bounds.start_z and
bounds.end_z; update all call sites that used make_credits_scroll_bounds (which
returns { start_z, end_z }) to pass that object directly as the bounds argument
so the function now has four parameters and aligns with the companion
make_credits_scroll_bounds return shape.
---
Minor comments:
In `@design/tech-icons.html`:
- Around line 27-29: The rule that currently sets "padding: 40px 48px" and
"width: 640px" will overflow narrow viewports; update that CSS rule by replacing
the fixed width with a fluid pattern — set width: 100% and max-width: 640px (and
ensure box-sizing: border-box is applied so the horizontal padding is included
in the computed width) so the card stays responsive on small screens while
capping its maximum size on larger viewports.
In `@e2e/demo/playwright/page.e2e.ts`:
- Line 5: The test currently only asserts visibility of any <h1> via
expect(page.locator('h1')).toBeVisible(); update this to assert the expected
heading content instead by replacing the visibility-only assertion with a text
assertion on the same locator (e.g., use
expect(page.locator('h1')).toHaveText(...) or toContainText(...) with the
precise expected heading string or regex) so the smoke test is deterministic;
keep the same locator expression 'page.locator("h1")' and ensure the expected
heading text matches the app copy.
In `@src/app.html`:
- Around line 12-18: The service worker registration promise
(navigator.serviceWorker.register('/sw.js')) lacks error handling so failures
are silent; update the inline load handler (window.addEventListener('load',
...)) to attach a .catch() to navigator.serviceWorker.register(...) and log the
error (e.g., console.error with a clear message and the caught error) so
registration failures surface in the console.
In `@src/lib/components/Logo.svelte`:
- Line 13: DEFAULT_ARIA_LABEL in Logo.svelte is a hardcoded user-facing
aria-label; replace it with an i18n lookup (e.g., call your locale/messages API
or use the existing i18n helper to get the key "logo_aria_label") and use that
returned string as the default for aria-label; update the Logo component to
reference the i18n key instead of the literal and then add "logo_aria_label":
"Geometric JF Fusion Logo" to every locale messages file so all locales supply
the text.
In `@src/lib/messages/en.spec.ts`:
- Around line 12-63: The test file uses arrow-function callbacks for the
describe/it blocks; change each arrow callback to a traditional function
declaration so describe('messages', ...), it('contains all original keys for
backward compatibility', ...), describe('base_messages', ...), it('contains no
simon-specific keys', ...), describe('simon_messages', ...) and it('contains all
simon-specific keys', ...) all use function() { ... } callbacks instead of () =>
{ ... } to satisfy the guideline.
In `@src/lib/simon/SimonBoard.svelte`:
- Around line 125-133: Rename the boolean template const `active` to `is_active`
to follow the boolean naming convention: change the Svelte {`@const` active =
is_lit(btn.color)} to {`@const` is_active = is_lit(btn.color)} and update all uses
in this block (the T.MeshStandardMaterial props color and emissive where
`active` is referenced) to use `is_active`; keep the existing calls to
`btn_lit(btn)`, `btn_dim(btn)`, and the `emissive_intensity` prop unchanged.
In `@src/lib/simon/SimonBoard.svelte.spec.ts`:
- Around line 28-66: Replace arrow-function callbacks in the test suite with
named function expressions per repo style: change the describe callback
(describe('SimonBoard', () => { ... })) and each it callback (it('renders
without error ...', () => { ... })) to use function() { ... } syntax so the
describe and it blocks use function callbacks instead of arrow functions; update
all occurrences within SimonBoard.svelte.spec.ts (the describe('SimonBoard',
...) block and each it(...) test) accordingly.
In `@src/lib/simon/SimonScene.svelte.spec.ts`:
- Around line 57-81: The test file uses arrow functions for test callbacks which
violates the repo rule; update the callback syntax in the describe('SimonScene',
...) block and all its it(...) callbacks (the three it blocks: 'renders without
error', 'calls make_credits_scroll_bounds with CREDITS_LINE_COUNT and HALF_D',
and 'score module exposes all required score_data fields', plus the messages
test) to use traditional function declarations (function() { ... }) instead of
arrow functions so the describe and it callbacks conform to the repository
guideline.
In `@src/routes/layout.css`:
- Line 12: The font-family declaration using 'Orbitron' violates the stylelint
rule font-family-name-quotes; update the declaration in layout.css by removing
the quotes around Orbitron (e.g., change the font-family value to use Orbitron
without quotes and keep any desired fallbacks like sans-serif) so the rule
passes; locate the font-family declaration in the layout.css block (font-family:
'Orbitron';) and replace it with an unquoted font-family name.
---
Nitpick comments:
In @.github/workflows/auto-tag.yml:
- Around line 25-34: The pkg_changed step can fail for a root commit because git
diff HEAD~1 errors when HEAD has no parent; update the step (step id
pkg_changed) to detect a root commit first (e.g., using git rev-parse --verify
HEAD^ or git rev-list --max-parents=0 HEAD) and if no parent exists set
"changed=true" and emit the same success message, otherwise run the existing git
diff HEAD~1 --name-only check and set "changed=true"/"changed=false" as before
so the step never exits with an error on single-commit repos.
In `@design/tech-icons.html`:
- Around line 91-93: The icon label styling uses color and font-size that are
too small and low-contrast (the CSS rules for color, font-size, font-weight
shown); increase font-size from 10px to around 12px–13px and choose a
higher-contrast color (e.g., a lighter muted shade or a semantic variable like
--text-muted-strong) while keeping font-weight 500, so update the CSS
declarations for color and font-size where those properties are defined to
improve readability on dark backgrounds.
In `@e2e/page.e2e.ts`:
- Line 16: Duplicate CHECK_SEED and compute_check logic: extract the constant
CHECK_SEED and the compute_check function into a small dependency-free module
(e.g., score-check.ts) and have both compute_check usages import from it; update
e2e/page.e2e.ts to import CHECK_SEED and compute_check instead of defining them
inline, and update score.svelte.ts to import the same exported symbols so there
is a single source of truth for the algorithm.
In `@sonar-project.properties`:
- Around line 14-16: The exclusion comment currently documents `.claude/` and
`scripts/` but omits `design/`, so update the comment above the exclusions to
mention that `design/` contains visual reference artifacts (mockups, assets) and
is excluded from analysis for maintainability; locate the comment block that
references `.claude/` and `scripts/` (the human-readable exclusion rationale in
sonar-project.properties) and add a brief clause like "design/ hosts visual
reference artifacts (mockups/assets) and is excluded" so future readers
understand why `design/` is excluded.
In `@src/hooks.server.spec.ts`:
- Around line 33-75: Add a make_event() helper that returns a properly typed
RequestEvent and replace the six occurrences of "{} as RequestEvent" in the
handle tests with calls to make_event(); update test code that calls handle({
event: {} as RequestEvent, resolve: ... }) to handle({ event: make_event(),
resolve: ... }) and ensure the helper is imported or declared in the spec
(referencing the handle tests, the RequestEvent type and the
make_resolve()/resolve mocks) so you remove the repeated restricted "as"
assertions while keeping the same behavior.
In `@src/lib/components/Logo.svelte.spec.ts`:
- Around line 8-9: Tests duplicate the component's default constants
(DEFAULT_SIZE_STRING and DEFAULT_ARIA_LABEL); instead import the canonical
defaults exported by the component (e.g., DEFAULT_SIZE, DEFAULT_ARIA_LABEL or
the component's exported defaults from Logo.svelte) and derive the test values
from them (convert DEFAULT_SIZE to a string for DEFAULT_SIZE_STRING if needed)
so the test follows the component's source of truth and won't diverge after
refactors.
In `@src/lib/game/credits-config.ts`:
- Around line 13-33: Remove the redundant individual exports by turning the
exported functions make_credits_scroll_bounds and advance_scroll into
non-exported (module-private) functions and keep only the credits_scroll
namespace object exported; specifically, remove the export keyword from the
function declarations for make_credits_scroll_bounds and advance_scroll so
callers and tests must import credits_scroll and access
credits_scroll.make_credits_scroll_bounds / credits_scroll.advance_scroll, and
update any imports/tests to mock or reference the credits_scroll object instead.
In `@src/lib/simon/SimonBoard.svelte`:
- Line 14: Extract the remaining magic numbers into UPPER_CASE constants and use
them where referenced: replace the literal divisor 36 in the BUTTON_GAP
calculation (const BUTTON_GAP = Math.PI / 36) with a named constant (e.g.,
BUTTON_GAP_DIVISOR or DEGREES_PER_GAP) so the 5-degree intent is explicit, and
replace the inline roughness={0.8} and roughness={0.5} (used in the components
around where roughness is passed) with named constants like ROUGHNESS_ACTIVE and
ROUGHNESS_INACTIVE (or similar) and update their usage sites to reference those
constants (ensure exported/defined at the top of SimonBoard.svelte alongside
BUTTON_GAP).
In `@src/lib/simon/SimonScene.svelte.spec.ts`:
- Around line 68-80: The test currently just asserts the mock objects (score and
messages) contain literal values instead of verifying SimonScene actually uses
them; update the two "it" blocks to render the SimonScene component (use the
existing test harness/render helper) with the score and messages props and then
assert the rendered DOM or child component props reflect score.high_score,
score.current_score, score.is_new_high_score, score.high_score_round,
score.last_cleared_round and
messages.score_high_score/messages.score_round/messages.score_current rather
than checking the raw mock objects. Locate the specs referencing SimonScene,
score, messages and replace the expect(...) on the mock with queries/assertions
against the rendered output (textContent, getByText, or inspecting passed props
on child components) so the test verifies actual prop wiring and rendering.
In `@src/routes/`+layout.svelte:
- Around line 23-38: The $effect blocks directly query and mutate DOM (using
LOADING_STATUS_ID, OVERLAY_ELEMENT_ID, OVERLAY_HIDDEN_CLASS,
LOADING_PROGRESS_SELECTOR, LOADING_BAR_SELECTOR) which violates the project's
Svelte DOM-manipulation rule; replace these imperative updates with declarative
Svelte bindings: bind the DOM nodes via bind:this or use reactive expressions to
interpolate loading.status_text and loading.progress into the template, use
class:OVERLAY_HIDDEN_CLASS={...} on the overlay element driven by
loading.is_visible, and bind:value on the <progress> (or use {loading.progress}
text nodes) so the UI updates automatically instead of using
document.getElementById/querySelector inside $effect. Ensure you remove the
$effect blocks that perform manual DOM queries and update the template to
reference the loading store/props directly.
In `@wrangler.jsonc`:
- Around line 14-24: The sampling rates in wrangler.jsonc
("observability.logs.head_sampling_rate" and
"observability.traces.head_sampling_rate" set to 0.1) will drop ~90% of requests
before your Worker code runs, hiding most errors; either increase those rates to
1 during initial testing or, better, implement unconditional error reporting in
your Worker fetch handler (the global fetch event/function that handles
requests) so that errors/exceptions are logged regardless of sampling while
successes remain sampled—update the fetch handler to always emit error
logs/traces and keep head_sampling_rate lower for normal success traffic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c4a16619-13d9-43fd-b4f4-9ac671041ae8
⛔ Files ignored due to path filters (14)
cspell.config.yamlis excluded by!*.config.*playwright.config.tsis excluded by!*.config.*pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamlprettier.config.jsis excluded by!*.config.*src/lib/assets/favicon.svgis excluded by!**/*.svgstatic/apple-touch-icon.pngis excluded by!**/*.png,!static/**static/fonts/Orbitron.ttfis excluded by!**/*.ttf,!static/**static/fonts/PressStart2P.ttfis excluded by!**/*.ttf,!static/**static/icon-192.pngis excluded by!**/*.png,!static/**static/icon-512.pngis excluded by!**/*.png,!static/**static/icon.svgis excluded by!**/*.svg,!static/**static/robots.txtis excluded by!static/**svelte.config.jsis excluded by!*.config.*vite.config.tsis excluded by!*.config.*
📒 Files selected for processing (53)
.github/workflows/auto-tag.yml.github/workflows/ci.yml.npmrcAGENTS.mdCLAUDE.mdGEMINI.mddesign/controls-preview.htmldesign/icon-proposals.htmldesign/tech-icons.htmle2e/demo/playwright/page.e2e.tse2e/page.e2e.tslefthook.ymlpackage.jsonpnpm-workspace.yamlsonar-project.propertiessrc/app.htmlsrc/hooks.server.spec.tssrc/hooks.server.tssrc/lib/assets/sound/dragon-studio-distorted-electronic-click-472367.opussrc/lib/components/Logo.sveltesrc/lib/components/Logo.svelte.spec.tssrc/lib/game/credits-config.tssrc/lib/messages/en.spec.tssrc/lib/messages/en.tssrc/lib/simon/SimonBoard.sveltesrc/lib/simon/SimonBoard.svelte.spec.tssrc/lib/simon/SimonScene.sveltesrc/lib/simon/SimonScene.svelte.spec.tssrc/lib/simon/audio.svelte.spec.tssrc/lib/simon/audio.tssrc/lib/simon/board-config.spec.tssrc/lib/simon/board-config.tssrc/lib/simon/credits.spec.tssrc/lib/simon/credits.tssrc/lib/simon/score.svelte.spec.tssrc/lib/simon/score.svelte.tssrc/lib/simon/simon-board-input.spec.tssrc/lib/simon/simon-board-input.tssrc/lib/simon/simon-flash.spec.tssrc/lib/simon/simon-flash.tssrc/lib/simon/simon.svelte.spec.tssrc/lib/simon/simon.svelte.tssrc/lib/simon/types.spec.tssrc/lib/simon/types.tssrc/routes/+layout.sveltesrc/routes/+page.sveltesrc/routes/+page.tssrc/routes/demo/+page.sveltesrc/routes/demo/playwright/+page.sveltesrc/routes/layout.csssrc/routes/page.svelte.spec.tstsconfig.jsonwrangler.jsonc
|
|
||
| engine-strict=true | ||
| minimum-release-age=1440 | ||
| confirmModulesPurge=false |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
pnpm confirmModulesPurge configuration option documentation
💡 Result:
The confirmModulesPurge configuration option in pnpm controls whether to show the interactive confirmation prompt when pnpm needs to remove and reinstall the node_modules directory from scratch (e.g., due to layout version mismatch).[1][2][3] It corresponds to the confirm-modules-purge key in configuration files. By default, the prompt is shown (equivalent to true). Set it to false to suppress the prompt and automatically proceed with the purge.[1][2] Configuration methods: - In .npmrc (project or user level): confirm-modules-purge=false[1][2] - CLI override: --config.confirmModulesPurge=false[3] - Flags like --yes or --force also bypass the prompt by setting this internally.[4][1] Note: This is a non-auth setting, so it may be excluded from global config.yaml (marked in configFileKey.ts).[2] Most non-registry settings go in pnpm-workspace.yaml, but sources specify .npmrc for this one.[5][2] As of pnpm 11.x (current as of 2026), official docs do not yet list it explicitly, but it was added via PR #11125 in March 2026.[1][2] Example in .npmrc: confirm-modules-purge=false This avoids needing --yes or --force every time for non-interactive scripts or CI.[1][3]
Citations:
- 1: feat: add confirm-modules-purge setting pnpm/pnpm#11125
- 2: feat: add confirm-modules-purge setting pnpm/pnpm#11125
- 3: Non-Interactive pnpm install --no-optional hangs without --config.confirmModulesPurge=false pnpm/pnpm#6778
- 4: pnpm/pnpm@5bf7768
- 5: https://pnpm.io/settings
Use kebab-case for the pnpm configuration key: confirm-modules-purge=false instead of confirmModulesPurge=false
The .npmrc file requires kebab-case formatting for configuration keys. The setting confirmModulesPurge uses camelCase, which pnpm will not recognize. Change line 6 to:
confirm-modules-purge=false
This option controls whether pnpm shows an interactive confirmation prompt when purging node_modules, and setting it to false is appropriate for CI/automation scenarios.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.npmrc at line 6, Replace the camelCase pnpm config key with kebab-case:
change the setting named confirmModulesPurge to confirm-modules-purge in the
.npmrc so pnpm recognizes it (leave the value as false for CI/automation).
| @@ -0,0 +1,341 @@ | |||
| import { readFileSync } from 'node:fs' | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
File exceeds the 300-line limit (342 lines).
Consider splitting the suite — e.g., accessibility + security headers into a separate file, or grouping loading-overlay tests into their own file.
As per coding guidelines: "File length must not exceed 300 lines."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/page.e2e.ts` at line 1, The file e2e/page.e2e.ts exceeds the 300-line
limit; split the large test suite into smaller files so each stays under 300
lines — for example create separate suites for "accessibility" tests, "security
headers" tests, and "loading-overlay" tests by moving the related describe/it
blocks into new files (e.g., page.accessibility.e2e.ts,
page.security-headers.e2e.ts, page.loading-overlay.e2e.ts), keep shared imports
like readFileSync and any helper functions in a shared helper module or
re-import them, and ensure test runner picks up the new files; after moving,
remove the moved tests from e2e/page.e2e.ts so its total lines drop below 300.
| "packageManager": "pnpm@11.0.8+sha512.4c4097e1dd2d42372c4e7fa5a791ff28fc75a484c7ac192e64b1df0fdef17594ba982f9b4fed9adfb3c757846f565b799b2763fb3733d1de1bcb82cf46684912", | ||
| "devEngines": { | ||
| "packageManager": { | ||
| "name": "pnpm", | ||
| "version": ">=11.0.0-0", | ||
| "onFail": "error" | ||
| } | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Cross-check that CI and any other workflow references match pnpm 11.
rg -n --no-heading 'pnpm[/@-]?(action-setup|version|@10|@11|10\.|11\.)' .github/workflows .npmrc 2>/dev/null
echo "---"
fd -t f 'package.json' | xargs rg -n '"packageManager"|"devEngines"'Repository: joshuafolkken/game-kit
Length of output: 509
🌐 Web query:
pnpm/action-setup v6.0.5 pnpm 11 compatibility
💡 Result:
Yes, pnpm/action-setup v6.0.5 is compatible with pnpm 11. The v6 series explicitly added support for pnpm v11 [1][2][3]. It installs recent pnpm 11 versions, such as v11.0.0-rc.5 in v6.0.3, with commits updating pnpm to 11.0.0-rc.0 and earlier bumps to 11.0.0-beta.4-1 [1][4]. v6.0.5, released on 2026-05-02, continues this support as the latest in the v6 series [3][5]. Official README examples use v6 with pnpm version 10 or from packageManager, and examples were updated to v6 around pnpm 11 rollout [6][7][8]. pnpm 11 requires Node.js 22+, and the action uses Node.js 24 [1][9]. Known issue #227: v6 may fail to read exact pnpm version from package_json_file or devEngines.packageManager, installing bootstrap RC instead (workaround: specify version explicitly) [10]. No evidence of incompatibility in v6.0.5; recent fixes address PATH/npm issues [11][12]. Official pnpm CI docs still show v4 but v6 is recommended for pnpm 11 [13].
Citations:
- 1: Bump pnpm/action-setup from 4 to 6 actions/setup-node#1528
- 2: https://github.com/pnpm/action-setup/releases
- 3: https://github.com/pnpm/action-setup
- 4: https://github.com/pnpm/action-setup/releases/tag/v6.0.3
- 5: https://github.com/pnpm/action-setup/tags
- 6: pnpm/action-setup@v4...v6
- 7: https://github.com/pnpm/action-setup/
- 8: pnpm/action-setup@08c4be7
- 9: https://pnpm.io/blog/releases/11.0
- 10: action-setup@v6 does not install pnpm version specified in
package_json_filepnpm/action-setup#227 - 11: https://github.com/pnpm/action-setup/releases/tag/v6.0.4
- 12: pnpm/action-setup@v6.0.3...v6.0.4
- 13: https://pnpm.io/continuous-integration
🏁 Script executed:
# Check if there are any comments or approval evidence in git history for this change
git log -1 --format=%B -- package.json 2>/dev/null | head -30Repository: joshuafolkken/game-kit
Length of output: 188
🏁 Script executed:
# Check the actual tag/commit of the action-setup to understand its release date
git log --oneline --all .github/workflows/ci.yml 2>/dev/null | head -5Repository: joshuafolkken/game-kit
Length of output: 193
Confirm the pnpm major version bump in devEngines received explicit user approval.
packageManager and devEngines.packageManager.version were bumped from pnpm@10.33.2 to pnpm@11.0.8 (a major release). The coding guideline requires explicit user confirmation for devEngines changes: "Never modify the devEngines field in package.json without explicit user confirmation; after any dependency-update command, verify it is unchanged."
The CI setup (pnpm/action-setup@v6.0.5) is compatible with pnpm 11, so CI will not break. However, no explicit approval message is visible in the commit history for this major version change. Please confirm this upgrade was intentionally approved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 85 - 92, The devEngines change bumped pnpm from
10.x to 11.x but the repo policy requires explicit user approval for devEngines
changes; confirm with the repository owner or PR author that upgrading
packageManager (packageManager and devEngines.packageManager.version) to
"pnpm@11.0.8" was intentional and approved, and if approved add an explicit note
to the PR/commit message (or revert the devEngines change back to the previous
pinned version) so the change is documented; ensure any follow-up commit
references this approval before merging.
| "script-src 'self' 'unsafe-inline' blob:", | ||
| "style-src 'self' 'unsafe-inline'", | ||
| "img-src 'self' data: blob:", |
There was a problem hiding this comment.
unsafe-inline in CSP weakens XSS protection.
If possible, migrate inline script/style usage to nonce/hash-based CSP instead of allowing global inline execution.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks.server.ts` around lines 7 - 9, The CSP currently includes
"script-src 'self' 'unsafe-inline' blob:" and "style-src 'self'
'unsafe-inline'", which weakens XSS protection; remove 'unsafe-inline' and
implement nonce/hash-based CSP instead by generating a per-request nonce in the
SvelteKit handle hook (e.g., in src/hooks.server.ts), attach the nonce to
event.locals and the rendered page, update your templates to add nonce
attributes to inline <script> and <style> blocks, and change the header values
to include "nonce-{nonce}" (or appropriate hashes) so inline assets are allowed
only when presenting the generated nonce/hash.
| } | ||
|
|
||
| export function calculate_time_coefficient(elapsed_ms: number, sequence_length: number): number { | ||
| const avg_s = elapsed_ms / 1000 / sequence_length |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Extract the 1000 divisor to a named constant.
The raw numeric literal 1000 in elapsed_ms / 1000 / sequence_length is a magic number. audio.ts already defines MS_PER_SECOND = 1000; a shared constant (or a local one here) should be used instead.
🔧 Proposed fix
+const MS_PER_SECOND = 1_000
+
export function calculate_time_coefficient(elapsed_ms: number, sequence_length: number): number {
- const avg_s = elapsed_ms / 1000 / sequence_length
+ const avg_s = elapsed_ms / MS_PER_SECOND / sequence_length
return Math.max(MIN_TIME_COEFF, 1 - avg_s * TIME_COEFF_DECAY)
}As per coding guidelines: "All magic numbers (numeric literals) except 0, 1, and -1 must be extracted to named UPPER_CASE constants."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const avg_s = elapsed_ms / 1000 / sequence_length | |
| const MS_PER_SECOND = 1_000 | |
| export function calculate_time_coefficient(elapsed_ms: number, sequence_length: number): number { | |
| const avg_s = elapsed_ms / MS_PER_SECOND / sequence_length | |
| return Math.max(MIN_TIME_COEFF, 1 - avg_s * TIME_COEFF_DECAY) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/simon/score.svelte.ts` at line 42, Replace the magic number 1000 used
in the avg_s calculation with a named UPPER_CASE constant (e.g., MS_PER_SECOND)
and update the expression to use that constant; either import MS_PER_SECOND from
audio.ts if available or declare a local const MS_PER_SECOND = 1000 at the top
of the module, then change const avg_s = elapsed_ms / 1000 / sequence_length to
use MS_PER_SECOND (avg_s = elapsed_ms / MS_PER_SECOND / sequence_length) so the
literal is removed and follows the project's magic-number rule.
| phase: string | ||
| round: number | ||
| flash_colors: readonly ButtonColor[] | ||
| flash_intensity: number | ||
| } | ||
|
|
||
| export type SimonPhase = 'idle' | 'showing' | 'player_input' | 'round_complete' | 'gameover' |
There was a problem hiding this comment.
Narrow SimonBoardData.phase to SimonPhase instead of string.
Current typing allows invalid phase values despite defining a strict SimonPhase union in the same file.
Proposed fix
export interface SimonBoardData {
active_color: ButtonColor | null
pressed_color: ButtonColor | null
- phase: string
+ phase: SimonPhase
round: number
flash_colors: readonly ButtonColor[]
flash_intensity: number
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/simon/types.ts` around lines 6 - 12, SimonBoardData currently types
its phase field as string allowing invalid values; change the type of the phase
property from string to the existing union type SimonPhase so the declaration
for SimonBoardData.phase uses SimonPhase (refer to the SimonBoardData type and
the SimonPhase union in this file) and update any imports/usages that rely on
the broader string type if needed to maintain type compatibility.
| switch_audio.init(click_url) | ||
| game_state.set_alt(true) | ||
| loading.set_step('initializing') |
There was a problem hiding this comment.
Guard initialization side effects to client-only lifecycle.
These calls run during layout initialization and can execute during SSR unless internals are fully server-safe. Move to onMount (or guard with browser) to avoid SSR/runtime hazards.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/routes/`+layout.svelte around lines 19 - 21, The three initialization
calls switch_audio.init(click_url), game_state.set_alt(true), and
loading.set_step('initializing') are running during layout load and may execute
during SSR; move them into a client-only lifecycle by wrapping them in an
onMount callback (import onMount from 'svelte') or guard them with the browser
flag (import { browser } from '$app/environment') so they only run on the client
(e.g., onMount(() => { switch_audio.init(click_url); game_state.set_alt(true);
loading.set_step('initializing'); }) or if (browser) { ... }).
| import { resolve } from '$app/paths' | ||
| </script> | ||
|
|
||
| <a href={resolve('/demo/playwright')}>playwright</a> |
There was a problem hiding this comment.
Localize the link label instead of hardcoding it.
Line 5 uses a hardcoded user-visible label (playwright). Please replace it with an i18n message key.
As per coding guidelines, "All user-visible strings (labels, buttons, toasts, validation errors, page titles) must use message keys for i18n; never hardcode strings."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/routes/demo/`+page.svelte at line 5, Replace the hardcoded anchor label
"playwright" in the link (<a href={resolve('/demo/playwright')}>playwright</a>)
with a call to the project's i18n message lookup using the key "demo.playwright"
(use the app's message helper such as $_/t/getMessage as appropriate) and import
or reference that helper in this Svelte page; also add the "demo.playwright"
entry to the locale files so the link renders a localized label.
| @@ -0,0 +1 @@ | |||
| <h1>Playwright e2e test demo</h1> | |||
There was a problem hiding this comment.
Replace hardcoded heading text with an i18n message key.
Line 1 hardcodes a user-facing string in markup. Please source this from the localization messages instead of inline text.
As per coding guidelines, "All user-visible strings (labels, buttons, toasts, validation errors, page titles) must use message keys for i18n; never hardcode strings."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/routes/demo/playwright/`+page.svelte at line 1, Replace the hardcoded
<h1>Playwright e2e test demo</h1> in the Svelte page (+page.svelte) with an i18n
lookup using your app's localization helper and a new message key (for example
"demo.playwright.title"); locate the <h1> element and substitute the inline text
with the i18n call used across the repo (e.g., the project’s $t, $_, or
getMessage helper) and add the corresponding message key/value to the messages
file so the heading is sourced from localization.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e-app # Conflicts: # cspell.config.yaml # package.json # playwright.config.ts # pnpm-lock.yaml # pnpm-workspace.yaml
…gression) #24 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…view mode #24 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sets PREVIEW_WORKERS=1 in playwright.config.ts when PLAYWRIGHT_PREVIEW=1 so workerd SQLite lock conflicts are avoided during pre-push hook. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@package.json`:
- Around line 103-106: The change to package.json's "overrides" block (the
"overrides" object and entries for "cookie" and "serialize-javascript") violates
repo policy; either attach explicit owner/user approval for modifying
pnpm.overrides to this PR or revert the changes in the "overrides" object back
to their previous values; specifically update the "overrides" object in
package.json to restore the prior entries for "cookie" and
"serialize-javascript" if approval is not provided, or add the approval artifact
to the PR description if you intend to keep the new overrides.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 808eeea5-ee54-4395-ad03-03e78f02eed9
⛔ Files ignored due to path filters (3)
cspell.config.yamlis excluded by!*.config.*playwright.config.tsis excluded by!*.config.*pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (3)
lefthook.ymlpackage.jsonpnpm-workspace.yaml
| "overrides": { | ||
| "cookie": "^0.7.0" | ||
| "cookie": "^0.7.0", | ||
| "serialize-javascript": ">=7.0.5" | ||
| }, |
There was a problem hiding this comment.
pnpm.overrides was modified without explicit approval.
Line 103–106 changes override entries, which is blocked by repo policy unless explicitly approved by the user/owner. Please either attach that approval in this PR or restore previous override values before merge.
As per coding guidelines, "Never remove, modify, or bypass entries in pnpm.overrides in package.json without explicit user approval."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 103 - 106, The change to package.json's
"overrides" block (the "overrides" object and entries for "cookie" and
"serialize-javascript") violates repo policy; either attach explicit owner/user
approval for modifying pnpm.overrides to this PR or revert the changes in the
"overrides" object back to their previous values; specifically update the
"overrides" object in package.json to restore the prior entries for "cookie" and
"serialize-javascript" if approval is not provided, or add the approval artifact
to the PR description if you intend to keep the new overrides.
|


closes #24