Skip to content

Conversation

@ingoau
Copy link
Member

@ingoau ingoau commented Aug 9, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a combined Stopwatch & Timer tool with start, pause, reset, lap, and preset functionalities, accessible from the Tools menu.
    • Added a new navigation entry and homepage listing for the Stopwatch & Timer tool.
  • Improvements

    • Sidebar menu button highlighting is now more accurate.
    • Sidebar authentication UI styling improved for consistency.
    • Homepage tools grid layout adapts better to different screen sizes.
    • Enhanced accessibility and styling refinements across UI components including sidebar, dropdown menus, tooltips, tables, notifications, and mobile sidebar behavior.
    • Improved avatar loading state handling and carousel button accessibility.
    • Refined tooltip positioning and chart label logic for better clarity.
    • Replaced static hero background with dynamic particle animation for a more engaging homepage experience.
    • Mobile sidebar updated to use a drawer component with improved styling and behavior.
    • Added a "Report Bug" button linking to GitHub issue template.
    • Updated button component styling with rounded corners.
    • Adjusted tooltip arrow positioning for better visual alignment.
    • Enhanced table styling with background clipping and extended hover effects.
  • Chores

    • Updated various dependencies to their latest versions for improved stability and performance.
    • Added new dependencies for particle animations and adjusted package configurations accordingly.

ingoau and others added 19 commits August 8, 2025 13:29
Bumps [paneforge](https://github.com/svecosystem/paneforge/tree/HEAD/packages/paneforge) from 1.0.0-next.5 to 1.0.2.
- [Release notes](https://github.com/svecosystem/paneforge/releases)
- [Changelog](https://github.com/svecosystem/paneforge/blob/main/packages/paneforge/CHANGELOG.md)
- [Commits](https://github.com/svecosystem/paneforge/commits/paneforge@1.0.2/packages/paneforge)

---
updated-dependencies:
- dependency-name: paneforge
  dependency-version: 1.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [tailwind-variants](https://github.com/heroui-inc/tailwind-variants) from 1.0.0 to 2.1.0.
- [Release notes](https://github.com/heroui-inc/tailwind-variants/releases)
- [Changelog](https://github.com/heroui-inc/tailwind-variants/blob/main/CHANGELOG.md)
- [Commits](heroui-inc/tailwind-variants@v1.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: tailwind-variants
  dependency-version: 2.1.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Inglan <96573515+Inglan@users.noreply.github.com>
…of groupItem.url

Co-authored-by: Inglan <96573515+Inglan@users.noreply.github.com>
…33-4a02-b60b-4a2bd7551ac4

Fix sidebar sub-item active state to check individual URLs instead of parent group URL
…/tailwind-variants-2.1.0

Bump tailwind-variants from 1.0.0 to 2.1.0
…/paneforge-1.0.2

Bump paneforge from 1.0.0-next.5 to 1.0.2
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

Walkthrough

This change introduces a new Stopwatch & Timer tool as a Svelte page, updates navigation and homepage tool listings to include it, and adjusts sidebar and authentication UI styling and logic. Several dependencies are updated in package.json, including some version downgrades and a package relocation. The sidebar's active state logic and click behavior are refined, and grid layouts on the homepage are improved for responsiveness. Multiple UI components receive minor style and accessibility improvements.

Changes

Cohort / File(s) Change Summary
Dependency Version Updates
package.json
Updates multiple dependencies and devDependencies, including Svelte, Tailwind, TypeScript, Clerk, and others. Downgrades layerchart and moves @lucide/svelte from dependencies to devDependencies. Adds new dependencies @tsparticles/svelte and tsparticles. No scripts or config changes.
Sidebar Logic and UI Adjustments
src/lib/components/app-sidebar.svelte, src/lib/components/sidebar-auth.svelte
Simplifies sidebar active state logic; adds onclick handler to open sidebar on group menu clicks; adds list-none CSS class to fallback auth dialog trigger for styling; hides sidebar toggle button on mobile devices.
Navigation Structure Updates
src/lib/navigation.ts
Adds "Stopwatch & Timer" item under "Tools" submenu in navigation.
Homepage Tool Listing & Layout
src/routes/+page.svelte
Adds "Stopwatch & Timer" to homepage tool list; replaces static CSS grid background with dynamic particle animation using @tsparticles/svelte; adjusts grid layout responsiveness; minor padding and z-index tweaks.
New Stopwatch & Timer Tool
src/routes/tools/stopwatch-timer/+page.svelte
Adds a new Svelte page implementing a combined Stopwatch and Timer tool with tabs, timer/stopwatch logic, lap recording, notifications, and responsive UI.
Avatar Component Update
src/lib/components/ui/avatar/avatar.svelte
Adds a new bindable loadingStatus prop bound to the AvatarPrimitive.Root component for reactive loading state.
Carousel Accessibility Update
src/lib/components/ui/carousel/carousel-previous.svelte
Replaces native disabled attribute with aria-disabled on previous button for accessibility compliance.
Chart Style Injection Change
src/lib/components/ui/chart/chart-style.svelte
Replaces raw HTML injection with direct <style> element rendering to remove ESLint disable comment.
Chart Tooltip Logic Refinement
src/lib/components/ui/chart/chart-tooltip.svelte
Uses nullish coalescing instead of logical OR for fallback values; tightens conditions to exclude only undefined values rather than all falsy values.
Chart Utils Condition Update
src/lib/components/ui/chart/chart-utils.ts
Changes truthy check to explicit undefined check for payloadPayload existence.
Command Item CSS Selector Update
src/lib/components/ui/command/command-item.svelte
Broadens disabled attribute selector to match any presence of data-disabled attribute, not just data-disabled="true".
Dropdown Menu CSS Variable Rename
src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte, src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
Renames CSS custom properties from --radix-dropdown-menu-content-* to --bits-dropdown-menu-content-*; adds outline-none class to dropdown menu content.
Sidebar Rail CSS Adjustment
src/lib/components/ui/sidebar/sidebar-rail.svelte
Adjusts pseudo-element after:left position by 1px to the left using a calc expression.
Mobile Sidebar Component Swap
src/lib/components/ui/sidebar/sidebar.svelte
Replaces mobile sidebar from Sheet to Drawer component; updates markup, props, and styles accordingly; adds rounded corners and custom CSS variable for transform; adjusts sidebar icon width calculation on desktop.
Sonner Component CSS Variable Update
src/lib/components/ui/sonner/sonner.svelte
Updates CSS custom properties for background, text, and border colors to new variable names.
Table Cell, Head, and Row CSS Updates
src/lib/components/ui/table/table-cell.svelte, src/lib/components/ui/table/table-head.svelte, src/lib/components/ui/table/table-row.svelte
Adds bg-clip-padding class to table cells and headers; extends hover background effect on table rows to child elements with svelte-css-wrapper class.
Tooltip Arrow Transform Fix
src/lib/components/ui/tooltip/tooltip-content.svelte
Reverses horizontal and vertical translation directions for tooltip arrow on bottom and left sides to correct positioning.
Particles Engine Initialization
src/routes/+layout.svelte
Adds initialization of full tsparticles engine using loadFull and particlesInit for particle animations in the layout; updates Sidebar.Trigger class to use clsx with fixed positioning and button variant styles.
Play Component UI and Feature Update
src/lib/components/play.svelte
Removes comments drawer and related markup; adds a "Report Bug" button linking to GitHub issue template with icon; adjusts container padding and iframe border radius; changes button container layout to fit height grid.
Button Component Style Update
src/lib/components/ui/button/button.svelte
Changes base button border-radius from rounded-md to rounded-lg.
Game Page Load Function Addition
src/routes/g/[id]/+page.ts
Adds a new load function extracting a game ID from URL and fetching game data; throws 404 error if game not found.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant NavBar as Navigation/Menu
    participant Home as Homepage
    participant StopwatchPage as Stopwatch & Timer Page

    User->>NavBar: Opens navigation menu
    NavBar-->>User: Shows "Stopwatch & Timer" link
    User->>Home: Views tool list
    Home-->>User: Displays "Stopwatch & Timer" card
    User->>StopwatchPage: Navigates to /tools/stopwatch-timer
    StopwatchPage-->>User: Renders Stopwatch & Timer UI (tabs, controls)
    User->>StopwatchPage: Interacts with stopwatch/timer (start, pause, lap, reset)
    StopwatchPage-->>User: Updates UI, plays sound on timer finish
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–25 minutes

Possibly related PRs

Suggested reviewers

  • nathblade16

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch main

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Aug 9, 2025

Deploying edutools-testing with  Cloudflare Pages  Cloudflare Pages

Latest commit: b6d56f0
Status: ✅  Deploy successful!
Preview URL: https://fe0f32c3.edutools-testing.pages.dev
Branch Preview URL: https://main.edutools-testing.pages.dev

View logs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (1)
src/lib/components/sidebar-auth.svelte (1)

101-114: list-none placed on Dialog.Trigger won’t remove bullets on nested li; target li directly

list-style is not inherited. Apply it to the li children (Sidebar.MenuItem) instead of the Trigger wrapper.

Apply this diff so the wrapper targets its direct li children:

-    <Dialog.Trigger class="flex list-none flex-col gap-2">
+    <Dialog.Trigger class="flex flex-col gap-2 [&>li]:list-none">
🧹 Nitpick comments (6)
src/routes/+page.svelte (1)

34-35: Responsive grid tweak improves small-screen UX

Switching to a single column on small screens and 3 cols at md is sensible.

Consider de-duplicating the tools list by deriving it from the navigation (or a shared tools config) to avoid divergence:

  • Option A: import createMainNavigation() and map the “Tools” group items here.
  • Option B: extract a shared tools array (e.g., lib/tools.ts) used by both navigation and this page.
src/routes/tools/stopwatch-timer/+page.svelte (5)

28-38: No hours handling in formatTime

After 99 minutes the display rolls over incorrectly (e.g., 125 min → “125:00”).
If hour-level use is expected, extend the formatter or cap minutes at 59 and show hours.


120-141: AudioContext leak

The created AudioContext is never closed, keeping an extra audio thread alive until page unload. Add:

 oscillator.stop(audioContext.currentTime + 1);
+oscillator.onended = () => audioContext.close();

143-150: Reactive block does nothing

progress is calculated but never used, so the $effect runs unnecessarily every tick.
Either bind progress to a progress-bar element or remove the block to save CPU.


240-257: Lap removal index off-by-one

removeLap(stopwatchLaps.length - index - 1) duplicates the earlier lapIndex math; simpler & safer:

- onclick={() => removeLap(stopwatchLaps.length - index - 1)}
+ onclick={() => removeLap(index)}

Avoids errors if the surrounding logic changes.


312-317: Quick-set buttons mutate bound values without type coercion

bind:value on input type="number" still gives numbers as strings in some browsers; assigning
preset.minutes (number) is fine but later comparisons like (timerMinutes === 0) may face string vs number.
Ensure numeric coercion:

timerMinutes = Number(preset.minutes);
timerSeconds = Number(preset.seconds);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1c19c5 and c426c8b.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • package.json (3 hunks)
  • src/lib/components/app-sidebar.svelte (3 hunks)
  • src/lib/components/sidebar-auth.svelte (1 hunks)
  • src/lib/navigation.ts (1 hunks)
  • src/routes/+page.svelte (2 hunks)
  • src/routes/tools/stopwatch-timer/+page.svelte (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.*

⚙️ CodeRabbit Configuration File

**/*.*: Do not correct spelling errors or grammar mistakes.

Files:

  • src/lib/navigation.ts
  • src/lib/components/app-sidebar.svelte
  • src/routes/+page.svelte
  • src/lib/components/sidebar-auth.svelte
  • src/routes/tools/stopwatch-timer/+page.svelte
  • package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/lib/navigation.ts (1)

65-68: Add “Stopwatch & Timer” nav item — LGTM

Entry is consistent with other Tools children and matches the new route.

src/routes/+page.svelte (1)

16-18: Tool list updated with Stopwatch & Timer — OK

New entry is consistent with existing entries and links to the expected route.

src/lib/components/app-sidebar.svelte (2)

131-135: Ensuring the sidebar opens when a group is clicked — good addition

Explicitly calling sidebar.setOpen(true) on group click improves discoverability in collapsed mode.


211-211: Non-group isActive equality check — LGTM

Direct match for leaf routes is appropriate here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (1)
src/lib/components/ui/chart/chart-style.svelte (1)

15-23: Quote and escape the data attribute value in the generated selector.

If id contains special characters (e.g., spaces, quotes, brackets), the unquoted selector [data-chart=${id}] can break. Quote and escape the value to harden the CSS.

Example (outside this range):

const esc = (v: string) =>
  // Use native CSS.escape when available; otherwise minimal escaping for quoted strings
  (typeof CSS !== 'undefined' && CSS.escape) ? CSS.escape(v) : v.replace(/["\\\n\r\f]/g, (m) => `\\${m}`);

let content = `${prefix} [data-chart="${esc(id)}"] {\n`;
🧹 Nitpick comments (2)
src/lib/components/ui/chart/chart-utils.ts (1)

44-49: Change to explicit undefined check is fine; consider simplifying the type narrowing for readability.

Current logic is correct and consistent with the PR’s nullish semantics. You can make the property access clearer and avoid the verbose keyof typeof assertions:

-  } else if (
-    payloadPayload !== undefined &&
-    key in payloadPayload &&
-    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'
-  ) {
-    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
-  }
+  } else if (payloadPayload !== undefined) {
+    const maybe = (payloadPayload as Record<string, unknown>)[key];
+    if (typeof maybe === 'string') {
+      configLabelKey = maybe;
+    }
+  }
src/lib/components/ui/avatar/avatar.svelte (1)

15-19: Expose busy state to assistive tech.

If loadingStatus reflects image load, expose it via aria-busy for better a11y.

 <AvatarPrimitive.Root
   bind:ref
   bind:loadingStatus
   data-slot="avatar"
-  class={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
+  class={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
+  aria-busy={loadingStatus === 'loading'}
   {...restProps}
 />
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c426c8b and c461576.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • package.json (3 hunks)
  • src/lib/components/ui/avatar/avatar.svelte (1 hunks)
  • src/lib/components/ui/carousel/carousel-previous.svelte (1 hunks)
  • src/lib/components/ui/chart/chart-style.svelte (1 hunks)
  • src/lib/components/ui/chart/chart-tooltip.svelte (2 hunks)
  • src/lib/components/ui/chart/chart-utils.ts (1 hunks)
  • src/lib/components/ui/command/command-item.svelte (1 hunks)
  • src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte (1 hunks)
  • src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte (1 hunks)
  • src/lib/components/ui/sidebar/sidebar-rail.svelte (1 hunks)
  • src/lib/components/ui/sidebar/sidebar.svelte (3 hunks)
  • src/lib/components/ui/sonner/sonner.svelte (1 hunks)
  • src/lib/components/ui/table/table-cell.svelte (1 hunks)
  • src/lib/components/ui/table/table-head.svelte (1 hunks)
  • src/lib/components/ui/table/table-row.svelte (1 hunks)
  • src/lib/components/ui/tooltip/tooltip-content.svelte (1 hunks)
✅ Files skipped from review due to trivial changes (9)
  • src/lib/components/ui/table/table-head.svelte
  • src/lib/components/ui/sidebar/sidebar-rail.svelte
  • src/lib/components/ui/table/table-cell.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
  • src/lib/components/ui/sonner/sonner.svelte
  • src/lib/components/ui/table/table-row.svelte
  • src/lib/components/ui/tooltip/tooltip-content.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
  • src/lib/components/ui/command/command-item.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.*

⚙️ CodeRabbit Configuration File

**/*.*: Do not correct spelling errors or grammar mistakes.

Files:

  • src/lib/components/ui/carousel/carousel-previous.svelte
  • src/lib/components/ui/chart/chart-utils.ts
  • src/lib/components/ui/chart/chart-style.svelte
  • src/lib/components/ui/avatar/avatar.svelte
  • src/lib/components/ui/sidebar/sidebar.svelte
  • src/lib/components/ui/chart/chart-tooltip.svelte
🔇 Additional comments (9)
src/lib/components/ui/chart/chart-style.svelte (1)

33-35: Switch to a real <style> element — cleaner and safer.

Avoids raw HTML injection, keeps SSR/hydration clean, and updates reactively. LGTM.

src/lib/components/ui/chart/chart-tooltip.svelte (4)

57-57: Good switch to nullish coalescing for label key.

Using ?? preserves intentionally empty strings and zeros, preventing unintended fallbacks.


63-65: Correct use of nullish fallback when resolving config label.

?.label ?? label ensures only null/undefined triggers the fallback; avoids clobbering empty strings.


66-66: Tightened undefined check is appropriate.

value === undefined allows valid falsy values (e.g., '', 0) to render.


144-148: Undefined-only guard for item value is correct.

This shows 0 and other falsy-but-valid values while still hiding truly absent values.

src/lib/components/ui/avatar/avatar.svelte (1)

7-7: Verify bindable loadingStatus on AvatarPrimitive.RootProps for bits-ui@2.8.11

We weren’t able to locate the bits-ui source locally to confirm that AvatarPrimitive.RootProps exposes a loadingStatus prop. Please double-check against your installed bits-ui v2.8.11 (in your local node_modules) or the official API docs to ensure:

  • The loadingStatus prop exists on AvatarPrimitive.RootProps
  • It’s marked as bindable in the TypeScript definitions
  • The runtime implementation supports binding to loadingStatus

Affected file & lines:

  • src/lib/components/ui/avatar/avatar.svelte:
    • Line 7: loadingStatus = $bindable('loading'),
    • Line 15: bind:loadingStatus

No other occurrences of loadingStatus were found in the codebase. If this prop isn’t available, update the component or types to avoid mismatches.

src/lib/components/ui/sidebar/sidebar.svelte (3)

2-2: LGTM: Drawer import aligns with the mobile migration.

Import swap is consistent with the new Drawer-based mobile sidebar.


89-90: Confirmed matching +2px offset for sidebar icon width calc

The +2px adjustment is applied identically in both variants (Line 76 and Line 89), so there is no gap/container width mismatch. No changes required.


76-77: Verified: the +2px accounts for the two 1px borders and matches token math
SIDEBAR_WIDTH_ICON is 3rem (48 px) and spacing(4) is 1rem (16 px), so calc(48 px + 16 px + 2 px) = 66 px. The extra 2 px covers the combined 1 px border on each side, preserving pixel‐perfect layout across densities. No change needed.

{variant}
{size}
disabled={!emblaCtx.canScrollPrev}
aria-disabled={!emblaCtx.canScrollPrev}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard interactions when using aria-disabled (button remains operable).

Switching to aria-disabled does not prevent clicks/keys. Either keep native disabled or defensively no-op the handlers and remove pointer events when not scrollable.

Option A — keep native semantics:

- aria-disabled={!emblaCtx.canScrollPrev}
+ disabled={!emblaCtx.canScrollPrev}
+ aria-disabled={!emblaCtx.canScrollPrev}

Option B — keep aria-disabled but guard behavior and visuals:

- onclick={emblaCtx.scrollPrev}
- onkeydown={emblaCtx.handleKeyDown}
+ onclick={() => emblaCtx.canScrollPrev && emblaCtx.scrollPrev()}
+ onkeydown={(e) => emblaCtx.canScrollPrev && emblaCtx.handleKeyDown(e)}

And consider visual non-interactivity:

 class={cn(
   'absolute size-8 rounded-full',
   emblaCtx.orientation === 'horizontal'
     ? 'top-1/2 -left-12 -translate-y-1/2'
     : '-top-12 left-1/2 -translate-x-1/2 rotate-90',
+  !emblaCtx.canScrollPrev && 'pointer-events-none opacity-50',
   className
 )}

Also applies to: 31-33

🤖 Prompt for AI Agents
In src/lib/components/ui/carousel/carousel-previous.svelte at line 23 and also
lines 31-33, aria-disabled is used but it does not prevent button interactions.
To fix this, either replace aria-disabled with the native disabled attribute to
prevent interaction or, if keeping aria-disabled, add logic to no-op click and
key handlers and apply CSS to disable pointer events and visually indicate
non-interactivity when scrolling is not possible.

Comment on lines +33 to +35
<svelte:element this={'style'}>
{themeContents}
</svelte:element>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Support strict CSP by allowing a nonce on the dynamic <style>.

Inline styles get blocked without 'unsafe-inline'. Expose an optional nonce and set it on the style element.

Apply within this block:

-    <svelte:element this={'style'}>
+    <svelte:element this={'style'} nonce={nonce}>
       {themeContents}
     </svelte:element>

Also update props in the script (outside this range):

let { id, config, nonce }: { id: string; config: ChartConfig; nonce?: string } = $props();
🤖 Prompt for AI Agents
In src/lib/components/ui/chart/chart-style.svelte around lines 33 to 35, the
dynamic <style> element does not support a nonce attribute, which is required
for strict Content Security Policy (CSP) compliance. To fix this, add an
optional nonce prop to the component's script props definition, then set this
nonce attribute on the <svelte:element this={'style'}> tag. This allows inline
styles to be safely used without enabling 'unsafe-inline' in CSP.

Comment on lines 37 to 41
<Drawer.Root
bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
direction="left"
{...restProps}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mobile Drawer ignores side, drops ref, and spreads restProps on the wrong element.

  • direction is hard-coded to "left"; side prop is ignored (regression for right-sided sidebars).
  • ref isn’t forwarded in the mobile branch anymore.
  • {...restProps} should likely target the rendered content container (parity with desktop), not the Drawer root.

Proposed fix:

- <Drawer.Root
-   bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
-   direction="left"
-   {...restProps}
- >
+ <Drawer.Root
+   bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
+   direction={side}
+ >
-   <Drawer.Content
+   <Drawer.Content
+     bind:this={ref}
      data-sidebar="sidebar"
      data-slot="sidebar"
      data-mobile="true"
-     class="bg-sidebar text-sidebar-foreground !top-2 !bottom-2 !left-2 rounded-xl !border-r-0 p-0 after:hidden"
-     style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE}; --initial-transform: calc(100% + 8px)"
+     class={cn(
+       'bg-sidebar text-sidebar-foreground !top-2 !bottom-2 rounded-xl !border-r-0 p-0 after:hidden',
+       side === 'left' ? '!left-2' : '!right-2'
+     )}
+     style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE}; --initial-transform: calc(100% + 8px)"
+     {...restProps}
    >
      <Drawer.Header class="sr-only">
        <Drawer.Title>Sidebar</Drawer.Title>
        <Drawer.Description>Displays the mobile sidebar.</Drawer.Description>
      </Drawer.Header>
      <div class="flex h-full w-full flex-col">
        {@render children?.()}
      </div>
    </Drawer.Content>
  </Drawer.Root>

Also applies to: 42-48, 49-53, 56-57

🤖 Prompt for AI Agents
In src/lib/components/ui/sidebar/sidebar.svelte around lines 37 to 41 and also
lines 42 to 48, 49 to 53, and 56 to 57, the mobile Drawer component incorrectly
hardcodes the direction prop to "left" instead of using the side prop, does not
forward the ref, and spreads restProps on the Drawer.Root element instead of the
content container. To fix this, replace the hardcoded direction with the side
prop, ensure the ref is forwarded properly in the mobile branch, and move the
{...restProps} spread from Drawer.Root to the content container element to
maintain parity with the desktop version.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/routes/+layout.svelte (1)

51-53: Initialize engine on client only; guard against SSR/HMR overhead

Consider running the init only in the browser to avoid needless SSR work and potential HMR double-inits during dev.

Apply this diff within this block:

-void particlesInit(async (engine) => {
-  await loadFull(engine);
-});
+if (browser) {
+  void particlesInit(async (engine) => {
+    await loadFull(engine);
+  });
+}

Add this import outside the selected lines:

import { browser } from '$app/environment';
src/routes/+page.svelte (4)

40-41: Lower FPS cap to reduce battery/CPU usage

120 FPS is overkill for most displays. Cap at 60 unless explicitly needed.

-        fpsLimit: 120,
+        fpsLimit: 60,

26-29: Remove commented-out legacy hero background markup

Keep the template lean; this block is obsolete after adopting Particles.

-    <!-- <div
-      class="hero-background absolute top-0 left-0 h-full w-full bg-[linear-gradient(to_right,var(--muted)_1px,transparent_1px),linear-gradient(to_bottom,var(--muted)_1px,transparent_1px)] [mask-image:linear-gradient(0deg,_transparent,_var(--background))] bg-[size:24px_24px]"
-    ></div> -->

369-384: Remove unused CSS for old background

The .hero-background styles are no longer used; safe to delete.

-<style>
-  .hero-background {
-    animation-name: background;
-    animation-duration: 0.5s;
-    animation-timing-function: linear;
-    animation-iteration-count: infinite;
-  }
-  @keyframes background {
-    0% {
-      background-position: 0 0;
-    }
-    100% {
-      background-position: 24px 24px;
-    }
-  }
-</style>

22-26: Optional: respect prefers-reduced-motion

Consider disabling movement or lowering particle count when users prefer reduced motion.

Outside this block, you can set a reactive options object:

<script lang="ts">
  import { onMount } from 'svelte';
  let particlesOptions: any = /* current options */;
  onMount(() => {
    const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
    if (reduce) {
      particlesOptions = {
        ...particlesOptions,
        fpsLimit: 30,
        particles: {
          ...particlesOptions.particles,
          move: { ...particlesOptions.particles.move, enable: false }
        }
      };
    }
  });
</script>

Then bind it:

<Particles options={particlesOptions} ... />
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df8ff2f and 82e9442.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • package.json (2 hunks)
  • src/routes/+layout.svelte (2 hunks)
  • src/routes/+page.svelte (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.*

⚙️ CodeRabbit Configuration File

**/*.*: Do not correct spelling errors or grammar mistakes.

Files:

  • src/routes/+layout.svelte
  • src/routes/+page.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/routes/+layout.svelte (1)

27-29: LGTM: Global tsparticles imports added appropriately

Imports of loadFull and particlesInit are correct for enabling engine setup.

src/routes/+page.svelte (3)

9-9: LGTM: Particles component import

This enables the new hero background; looks fine.


17-19: Stopwatch & Timer route and navigation verified

All references and routes for /tools/stopwatch-timer are in place:

  • src/routes/tools/stopwatch-timer/ directory exists
  • Navigation entry in src/lib/navigation.ts (lines 66–67)
  • Listing in src/routes/+page.svelte (lines 17–19)

No further updates needed.


1-10: Import path $lib/gmaes is valid—no broken import
The module is defined in src/lib/gmaes.ts and all getGameById references correctly import from $lib/gmaes. No changes are required here.

Likely an incorrect or invalid review comment.

Comment on lines 29 to 33
<Particles
id="hero-background"
class="absolute top-0 right-0 bottom-0 left-0"
options={{
autoPlay: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Hide decorative canvas from assistive tech

Particles is purely decorative. Add aria-hidden and role="presentation" on the canvas host.

-    <Particles
+    <Particles
+      aria-hidden="true"
+      role="presentation"
       id="hero-background"
       class="absolute top-0 right-0 bottom-0 left-0"
📝 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.

Suggested change
<Particles
id="hero-background"
class="absolute top-0 right-0 bottom-0 left-0"
options={{
autoPlay: true,
<Particles
aria-hidden="true"
role="presentation"
id="hero-background"
class="absolute top-0 right-0 bottom-0 left-0"
options={{
autoPlay: true,
🤖 Prompt for AI Agents
In src/routes/+page.svelte around lines 29 to 33, the Particles component
renders a decorative canvas that should be hidden from assistive technologies.
Add the attributes aria-hidden="true" and role="presentation" to the Particles
component or its canvas host element to ensure it is ignored by screen readers
and other assistive tools.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/routes/g/[id]/+page.ts (1)

1-1: Add typing for load (PageLoad) to catch issues at compile-time

Typing the load function helps validate return shape and parameters.

Option A (annotation):

 import { error } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
-export function load({ params }) {
+export const load: PageLoad = ({ params }) => {
   const game = getGameById(params.id);
   if (!game) throw error(404, 'Not Found');
   return { game };
-}
+};

Option B (satisfies for better inference):

import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load = (({ params }) => {
  const game = getGameById(params.id);
  if (!game) throw error(404, 'Not Found');
  return { game };
}) satisfies PageLoad;

Also applies to: 4-4

src/lib/components/ui/button/button.svelte (1)

7-7: Corner radius mismatch: default/icon → rounded-lg, but sm/lg → rounded-md

  • In src/lib/components/ui/button/button.svelte, the base style now uses rounded-lg (line 7), but the sm (line 21) and lg (line 23) sizes still force rounded-md.
  • A repo scan shows these overrides affect dozens of buttonVariants(...) usages across routes and UI components (e.g., backup, layout, calendar, pagination, alert-dialog).

Choose one for consistency or confirm mixed radii are intentional:

Option A — inherit base rounded-lg for all sizes:

- sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
- lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 px-6 has-[>svg]:px-4',

Option B — explicitly align to rounded-lg:

- sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
- lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ sm: 'h-8 gap-1.5 rounded-lg px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-lg px-6 has-[>svg]:px-4',

Please verify design intent or apply one of the above.

src/lib/components/play.svelte (1)

69-71: LGTM on layout tweaks; optional modernization for iframe border attribute

Looks good. Minor optional tweak: frameborder is obsolete in HTML5. Prefer CSS.

-  <iframe src={gmaedata?.url} frameborder="0" class="grow rounded-lg" title={gmaedata?.name}
-  ></iframe>
+  <iframe src={gmaedata?.url} class="grow rounded-lg border-0" title={gmaedata?.name}></iframe>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d4a747 and b6d56f0.

📒 Files selected for processing (3)
  • src/lib/components/play.svelte (4 hunks)
  • src/lib/components/ui/button/button.svelte (1 hunks)
  • src/routes/g/[id]/+page.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.*

⚙️ CodeRabbit Configuration File

**/*.*: Do not correct spelling errors or grammar mistakes.

Files:

  • src/lib/components/ui/button/button.svelte
  • src/routes/g/[id]/+page.ts
  • src/lib/components/play.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (2)
src/routes/g/[id]/+page.ts (1)

2-2: No issue with import specifier in src/routes/g/[id]/+page.ts

The module at $lib/gmaes is defined in src/lib/gmaes.ts and indeed exports getGameById. The import is correct as written—there is no $lib/games module to reference. Please disregard the previous suggestion to rename the import path.

Likely an incorrect or invalid review comment.

src/lib/components/play.svelte (1)

97-97: LGTM: h-fit for the controls grid

This should reduce extra whitespace and avoid forcing vertical space. No issues spotted.

import clsx from 'clsx';
import posthog from 'posthog-js';
import { onMount } from 'svelte';
import { Bug } from '@lucide/svelte';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Align Lucide import style and verify package entrypoint

Other icons in this file use the scoped icon-path import pattern. For consistency (and to avoid potential bundler/treeshaking surprises depending on your Lucide version), consider importing Bug the same way.

Apply:

-import { Bug } from '@lucide/svelte';
+import Bug from '@lucide/svelte/icons/bug';

Verification script to check current usage and package versions:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Lucide scoped imports:"
rg -n "from ['\"]@lucide/svelte" || true

echo -e "\nLucide unscoped imports:"
rg -n "from ['\"]lucide-svelte" || true

echo -e "\nPackage.json Lucide versions (scoped/unscoped, deps/devDeps):"
jq -r '
  .dependencies["@lucide/svelte"],
  .devDependencies["@lucide/svelte"],
  .dependencies["lucide-svelte"],
  .devDependencies["lucide-svelte"]
' package.json 2>/dev/null

Length of output: 10129


Align Bug icon import with scoped icon-path pattern

To maintain consistency with other icons and ensure optimal tree-shaking, use the direct icon-path import for the Bug icon:

  • File: src/lib/components/play.svelte, line 26
-import { Bug } from '@lucide/svelte';
+import Bug from '@lucide/svelte/icons/bug';
📝 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.

Suggested change
import { Bug } from '@lucide/svelte';
import Bug from '@lucide/svelte/icons/bug';
🤖 Prompt for AI Agents
In src/lib/components/play.svelte at line 26, the Bug icon is imported from
'@lucide/svelte', which is inconsistent with the scoped icon-path pattern used
for other icons. Change the import to use the direct icon-path import for the
Bug icon to maintain consistency and improve tree-shaking.

Comment on lines +174 to +181
<Button
variant="outline"
href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
target="_blank"
>
<Bug />
Report Bug
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add rel='noopener noreferrer' for security; also ensure Button supports href

Opening a new tab without rel can expose window.opener (reverse tabnabbing). Add rel. Also, if Button doesn’t natively support href, use an with buttonVariants.

Apply:

-			<Button
-				variant="outline"
-				href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
-				target="_blank"
-			>
-				<Bug />
-				Report Bug
-			</Button>
+			<Button
+				variant="outline"
+				href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
+				target="_blank"
+				rel="noopener noreferrer"
+			>
+				<Bug class="h-6 w-6" />
+				Report Bug
+			</Button>

If Button lacks href support, swap to:

<a
  href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
  target="_blank"
  rel="noopener noreferrer"
  class={buttonVariants({ variant: 'outline' })}
>
  <Bug class="h-6 w-6" />
  Report Bug
</a>

Optional check to confirm the repo slug matches this project’s remote:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

REMOTE="$(git remote get-url origin 2>/dev/null || echo '')"
echo "Origin remote: $REMOTE"

# Extract owner/repo if GitHub-style
SLUG="$(sed -E 's#.*github.com[:/ ]([^/]+/[^/.]+)(\.git)?#\1#;t;d' <<<"$REMOTE" || true)"
echo "Detected slug: $SLUG"

echo "Configured bug-report URL slug: EducationalTools/src"
if [[ "$SLUG" != "EducationalTools/src" ]]; then
  echo "Note: The bug-report URL points to EducationalTools/src, which differs from the detected slug: $SLUG"
  echo "Please confirm this is intentional."
fi

Length of output: 587


Add rel="noopener noreferrer" to external-link Button

Verified that the GitHub remote slug (EducationalTools/src) matches the configured URL and that our <Button> component accepts an href prop. Update the component in src/lib/components/play.svelte (lines 174–181) as follows:

-			<Button
-				variant="outline"
-				href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
-				target="_blank"
-			>
-				<Bug />
-				Report Bug
-			</Button>
+			<Button
+				variant="outline"
+				href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
+				target="_blank"
+				rel="noopener noreferrer"
+			>
+				<Bug class="h-6 w-6" />
+				Report Bug
+			</Button>
📝 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.

Suggested change
<Button
variant="outline"
href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
target="_blank"
>
<Bug />
Report Bug
</Button>
<Button
variant="outline"
href="https://github.com/EducationalTools/src/issues/new?template=bug_report.yml"
target="_blank"
rel="noopener noreferrer"
>
<Bug class="h-6 w-6" />
Report Bug
</Button>
🤖 Prompt for AI Agents
In src/lib/components/play.svelte around lines 174 to 181, the external link
Button lacks the rel="noopener noreferrer" attribute, which is important for
security and performance when using target="_blank". Add rel="noopener
noreferrer" to the Button component to ensure safe handling of the external
link.

Comment on lines +4 to +7
export function load({ url }) {
let gmae = getGameById(url.pathname.split('/').slice(2).join('/'));
if (!gmae) throw error(404, 'Not Found');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

load must return serializable data; use params.id instead of parsing URL

SvelteKit provides route params; parsing url.pathname is brittle. Also, the function currently returns nothing.

Apply this diff:

-export function load({ url }) {
-	let gmae = getGameById(url.pathname.split('/').slice(2).join('/'));
-	if (!gmae) throw error(404, 'Not Found');
-}
+export function load({ params }) {
+  const game = getGameById(params.id);
+  if (!game) throw error(404, 'Not Found');
+  return { game };
+}
📝 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.

Suggested change
export function load({ url }) {
let gmae = getGameById(url.pathname.split('/').slice(2).join('/'));
if (!gmae) throw error(404, 'Not Found');
}
export function load({ params }) {
let game = getGameById(params.id);
if (!game) throw error(404, 'Not Found');
return { game };
}
🤖 Prompt for AI Agents
In src/routes/g/[id]/+page.ts around lines 4 to 7, the load function incorrectly
parses the URL pathname to get the game ID and does not return any data. Replace
the URL parsing with using the provided params.id to get the game ID, and ensure
the function returns a serializable object containing the game data or throws a
404 error if not found.

@ingoau ingoau closed this Aug 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants