Skip to content

perf(ci): implement repository-wide CI optimization and Nx Cloud integration#12102

Merged
FoushWare merged 43 commits intomainfrom
feat/optimize-ci-and-deploy-sync
May 6, 2026
Merged

perf(ci): implement repository-wide CI optimization and Nx Cloud integration#12102
FoushWare merged 43 commits intomainfrom
feat/optimize-ci-and-deploy-sync

Conversation

@FoushWare
Copy link
Copy Markdown
Owner

@FoushWare FoushWare commented Apr 17, 2026

This PR implements a major performance overhaul of the CI/CD pipeline, reducing total build and deployment duration by ~80% (from 10m to under 3m).

Key Changes

  • Prebuilt Deployment Strategy: Moved the build phase to the GitHub runner for both Admin and Website apps.
  • Improved Caching: Implemented a hybrid caching model (Nx Cloud + Local Next.js cache + pnpm store).
  • Nx Cloud Integration: Connected the workspace to Nx Cloud for remote caching and performance monitoring.
  • Robust Status Syncing: Optimized the deployment scripts to ensure GitHub Actions correctly reflects Vercel build outcomes.
  • Documentation: Added a troubleshooting guide for deployment-specific issues.

Verification Results

  • Admin Build: 1m 33s
  • Website Build: 2m 52s
  • Total Pipeline: 2m 58s (Parallel execution)

Summary by CodeRabbit

  • New Features
    • Learning mode switcher; guided-learning now uses milestone-based progress instead of daily goals
  • Improvements
    • Redesigned hero with glassmorphism and entrance animations
    • Updated learning plan cards, stats, labels, and mastery visuals; layout and spacing refinements across pages
    • Guided-learning plumbing & utilities switched to milestone/topic model (new milestone ranges/estimates)
  • Documentation
    • Added deployment troubleshooting guide
  • Tests
    • Expanded unit and integration tests for guided-learning, UI components, and utilities

Copilot AI review requested due to automatic review settings April 17, 2026 22:37
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR migrates guided-learning from day-based schedules to milestone-based plans (types, data, hooks, components), adds a LearningModeSwitcher and UI/layout refinements, adds extensive tests, moves studyPlans into utilities and re-exports them, and substantially changes the GitHub Actions → Vercel deployment workflow to use prebuilt deploys with Next.js caching and frozen lockfile installs.

Changes

Cohort / File(s) Summary
Deployment & CI/CD
.github/workflows/manual-deploy-parallel.yml, nx.json, failed_job_log.txt, tests/config/playwright.config.ts, .husky/pre-commit
Switched Vercel workflow to prebuilt npx vercel deploy --prebuilt, added Next.js cache steps, changed installs to pnpm --frozen-lockfile, removed manifest mutation / Nx-plugin bypass and readiness-inspect/probe logic, added NX_CLOUD_ACCESS_TOKEN env, updated Playwright dotenv loading and pre-commit to use npm.
Guided Learning UI & Hooks
apps/website/src/app/features/guided-learning/components/... (e.g., ActivePlanView.tsx, GuidedLearningHeader.tsx, LearningPlanCard.tsx, PlanSelectionView.tsx), .../hooks/useActivePlan.ts, .../hooks/useGuidedLearningPlans.ts, page.tsx
Replaced day-based props/state (dailyGoals, currentDay, daysRange) with milestone-based shapes (milestones, currentMilestoneId, milestoneRange), updated labels/CTAs/metrics and progress calculations, updated styles and component wiring.
Study Plan Types & Data
libs/types/src/lib/studyPlans.ts, libs/utilities/src/lib/studyPlans.ts, apps/website/src/app/lib/studyPlans.ts
Introduced StudyMilestone, replaced schedule/weeks with milestones, switched estimatedTimePerDayestimatedTotalTime, refactored concrete plans into libs/utilities and deleted legacy apps/website studyPlans module.
Guided Learning Utilities & Types
apps/website/src/app/features/guided-learning/utils/*, apps/website/src/app/features/guided-learning/types/guided-learning.types.ts
Removed daily-goals generator and day-based helpers; added sortPlansByDifficulty, getMilestoneRange, changed grade color/text mapping and question-range logic; simplified LearningPlan to extend StudyPlan.
New Component: Learning Mode Switcher
libs/common-ui/src/common/LearningModeSwitcher.tsx, libs/common-ui/src/common/NavbarSimple.tsx
Added LearningModeSwitcher component (client-only) and integrated it into NavbarSimple for desktop/mobile; exposes optional isScrolled prop and uses useUserType setter.
Layout & Styling Updates
libs/common-ui/src/components/molecules/HeroSection.tsx, libs/common-ui/src/components/organisms/HomePageLayout.tsx, apps/website/src/app/layout.tsx
Refined hero to glassmorphism with staggered animations and decorative blobs, reorganized homepage containers/max-widths, and wrapped app children in a <main> with top padding.
API Route Changes & Tests
apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts, apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts
GET handler now seeds from hardcoded studyPlans and merges DB rows when available; DB failures warn and fallback to hardcoded plans. Added tests for GET/POST/DELETE behaviors including merge/fallback cases.
Tests & Mocks
apps/website/.../__tests__/*.test.tsx, libs/common-ui/src/common/__tests__/*, libs/utilities/src/lib/*.spec.ts, libs/utilities/src/lib/test-utils/mocks/lucide-react.tsx
Added/updated Jest + Playwright tests covering ActivePlanView, LearningPlanCard, plan-helpers, LearningModeSwitcher, studyPlans integrity; added Trophy icon mock.
Scripts & Exports
package.json, apps/website/package.json, apps/website/tests/config/playwright.config.ts, libs/utilities/src/index.ts
Added dev:website:test and adjusted dev/test ports (3000/3001), aligned Playwright server command to new dev script, and re-exported studyPlans from libs/utilities index.
Docs
docs/admin/deployment-troubleshooting.md
New troubleshooting doc explaining Admin deployment failures caused by destructive CI edits and remediation (manifest fixes, Vercel build command/outputDirectory).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #12082: Modifies the same manual-deploy workflow and Vercel deploy strategy (Nx-plugin bypass, prebuilt vs inspect flow).
  • PR #12080: Also changes .github/workflows/manual-deploy-parallel.yml around prebuilt deploy vs inspect/readiness probing; directly related to CI deploy behavior.
  • PR #12062: Addresses monorepo Vercel deployment issues and removes destructive package.json edits similar to changes in this PR.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately captures the main objective: CI optimization and Nx Cloud integration. It's specific, concise, and reflects the core changes across the monorepo.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/optimize-ci-and-deploy-sync

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR connects the workspace to Nx Cloud and updates the manual parallel deployment workflow to deploy prebuilt artifacts to Vercel (building on the GitHub runner) with added caching to reduce CI/CD runtime.

Changes:

  • Added nxCloudId to nx.json to enable Nx Cloud connectivity.
  • Updated .github/workflows/manual-deploy-parallel.yml to use pnpm install --frozen-lockfile, cache Next.js build cache directories, run vercel build, and deploy via vercel deploy --prebuilt.
  • Patched Vercel project settings in the workflow to use pnpm --filter <app> build and set installCommand to true.

Reviewed changes

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

File Description
nx.json Adds Nx Cloud workspace connection (nxCloudId).
.github/workflows/manual-deploy-parallel.yml Switches manual deploy to local vercel build + --prebuilt deploy and introduces caching + frozen lockfile installs.

Comment on lines +123 to +126
for attempt in 1 2 3 4 5 6; do
echo "Deploy website attempt ${attempt}/6"
deploy_log="$(mktemp)"
timeout 20m npx vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log" || true
if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$deploy_log"; then
echo "Deploy output indicates explicit build failure; failing deployment step."
exit 1
fi
prod_url="$(grep -Eo 'https://[^ ]+\.vercel\.app' "$deploy_log" | tail -n 1 || true)"
if [ -n "$prod_url" ]; then
echo "Deploy command produced URL: $prod_url"
inspect_log="$(mktemp)"
if timeout 12m npx vercel inspect "$prod_url" --wait --timeout 12m --token=${{ secrets.VERCEL_TOKEN }} >"$inspect_log" 2>&1; then
cat "$inspect_log"
deploy_output=$(npx vercel deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }})
if [ $? -eq 0 ]; then
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

With set -euo pipefail, the assignment deploy_output=$(npx vercel deploy ...) will cause the script to exit immediately on a non-zero deploy, so the retry loop will never retry. Restructure to run the deploy in the if condition (e.g., if deploy_output=$(...); then ... else ... fi) or otherwise suppress errexit for that command so failures fall through to the retry logic.

Copilot uses AI. Check for mistakes.
if [ $? -eq 0 ]; then
# Extract URL from output if possible, or use standard prod URL for aliasing
prod_url=$(echo "$deploy_output" | grep -Eo 'https://[^ ]+\.vercel\.app' | tail -n 1 || echo "https://elzatona-web.com")
echo "Admin deployment is ready in production."
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This log line is in the website deploy job but says "Admin deployment is ready"; it should reference the website deployment to avoid confusing CI logs and on-call troubleshooting.

Suggested change
echo "Admin deployment is ready in production."
echo "Website deployment is ready in production."

Copilot uses AI. Check for mistakes.
Comment on lines 127 to 130
# Extract URL from output if possible, or use standard prod URL for aliasing
prod_url=$(echo "$deploy_output" | grep -Eo 'https://[^ ]+\.vercel\.app' | tail -n 1 || echo "https://elzatona-web.com")
echo "Admin deployment is ready in production."
set_website_aliases "$prod_url"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

If the deploy output parsing fails, prod_url falls back to https://elzatona-web.com, but vercel alias set expects a deployment URL/id (not an existing alias/domain). This will likely make aliasing fail or point aliases at the wrong target. Better to fail fast when no deployment URL can be extracted (or query the deployment via vercel inspect/vercel ls to retrieve the actual deployment URL) before calling set_website_aliases.

Copilot uses AI. Check for mistakes.
with:
path: |
apps/website/.next/cache
key: ${{ runner.os }}-website-next-cache-${{ github.sha }}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The Next.js cache key is based on github.sha, which effectively creates a new cache entry every commit and reduces cache hit rates across PR updates (restore-keys helps, but you still churn caches). For better CI performance and lower cache storage churn, consider keying on the lockfile (e.g., hashFiles('pnpm-lock.yaml')) plus an app identifier, and optionally include a Next.js/version component if needed.

Suggested change
key: ${{ runner.os }}-website-next-cache-${{ github.sha }}
key: ${{ runner.os }}-website-next-cache-${{ hashFiles('pnpm-lock.yaml') }}

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +185
key: ${{ runner.os }}-next-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-next-cache-
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The admin Next.js cache key is also tied to github.sha, which causes frequent cache misses/churn. Consider using a lockfile-based key (e.g., hashFiles('pnpm-lock.yaml')) with an admin prefix so the cache is reusable across commits while still invalidating when deps change.

Suggested change
key: ${{ runner.os }}-next-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-next-cache-
key: ${{ runner.os }}-admin-next-cache-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-admin-next-cache-

Copilot uses AI. Check for mistakes.
-H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"buildCommand":"npm run build","outputDirectory":"apps/website/.next"}' \
-d '{"buildCommand":"pnpm --filter website build","outputDirectory":"apps/website/.next","installCommand":"true"}' \
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Patching Vercel project settings to set installCommand to true will globally change the project behavior on Vercel (including dashboard/preview builds), not just this workflow. Since --prebuilt deployments don’t need Vercel to install dependencies anyway, consider avoiding changing installCommand (or restoring the previous value at the end) to prevent unexpected breakages for non-GitHub-Actions deployments.

Suggested change
-d '{"buildCommand":"pnpm --filter website build","outputDirectory":"apps/website/.next","installCommand":"true"}' \
-d '{"buildCommand":"pnpm --filter website build","outputDirectory":"apps/website/.next"}' \

Copilot uses AI. Check for mistakes.
-H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"buildCommand":"npm run build","outputDirectory":".next"}' \
-d '{"buildCommand":"pnpm --filter admin build","outputDirectory":"apps/admin/.next","installCommand":"true"}' \
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Same concern for the admin project: setting installCommand to true via the API PATCH changes the project’s global Vercel configuration and may break preview/dashboard builds that expect installs. Consider not mutating installCommand here (or resetting it), and rely on the local vercel build + vercel deploy --prebuilt path for this workflow.

Suggested change
-d '{"buildCommand":"pnpm --filter admin build","outputDirectory":"apps/admin/.next","installCommand":"true"}' \
-d '{"buildCommand":"pnpm --filter admin build","outputDirectory":"apps/admin/.next"}' \

Copilot uses AI. Check for mistakes.
Comment on lines +203 to 208
- name: Validate and build (Local Vercel Build)
if: ${{ inputs.validate_build_admin }}
run: pnpm --dir apps/admin run build
run: |
npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
npx vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

vercel deploy --prebuilt in the admin job also depends on .vercel/output from vercel build, but the build step is conditional (if: inputs.validate_build_admin). If validation is turned off, the deploy will fail on a clean runner. Consider always running vercel pull + vercel build when using --prebuilt, or falling back to a standard vercel deploy when skipping the build.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to 99
- name: Validate and build (Local Vercel Build)
if: ${{ inputs.validate_build_website }}
run: pnpm --dir apps/website run build
run: |
npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
npx vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

vercel deploy --prebuilt requires a locally generated .vercel/output (from vercel build). Right now the build step is conditional (if: inputs.validate_build_website), so disabling validation will make the deploy step fail on a fresh runner because there is no prebuilt output to upload. Consider always running vercel pull + vercel build (and keep an input only for skipping extra checks, not the build itself), or switch back to non-prebuilt deploy when validation is disabled.

Copilot uses AI. Check for mistakes.
- Overhauled landing page aesthetics with glassmorphism and premium typography
- Refactored Guided Learning from day-based to modular milestone-based logic
- Updated StudyPlan data structures and hooks for flexible progress tracking
- Implemented Vitest unit tests and Playwright E2E verification suite
- Resolved build issues and optimized test-specific CI scripts
- Standardized husky pre-commit hook to use npm
Copy link
Copy Markdown

@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: 19

Caution

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

⚠️ Outside diff range comments (8)
apps/website/src/app/features/guided-learning/components/PlanSelectionView.tsx (1)

1-111: ⚠️ Potential issue | 🟡 Minor

Resolve Prettier formatting issue flagged by CI.

The pipeline reports a code style issue detected by format:check:tracked. Run the project's formatting command (typically pnpm format or npm run format) to auto-fix before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/website/src/app/features/guided-learning/components/PlanSelectionView.tsx`
around lines 1 - 111, Prettier formatting failed for the PlanSelectionView
component; run the repository formatter (e.g., pnpm format or npm run format)
and commit the auto-formatted changes so the file containing the
PlanSelectionView function and its JSX (including LearningPlanCard usage and the
completedPlans block) matches the project's style; re-run format:check:tracked
(or CI) to confirm the formatting issue is resolved before pushing.
package.json (1)

1-170: ⚠️ Potential issue | 🟡 Minor

CI format:check is failing across 15 files — run pnpm run format.

The CI quality gate is red on Prettier. Please run pnpm run format and commit the diff before requesting review again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 1 - 170, CI is failing Prettier formatting across
15 files; run the repository formatting script and commit the changes to satisfy
the quality gate: execute the existing npm script "format" (pnpm run format) to
apply Prettier fixes, review the changed files, stage and commit the resulting
diffs, then push the commit so "format:check:tracked" / "format" checks in CI
will pass.
apps/website/tests/config/playwright.config.ts (1)

203-215: ⚠️ Potential issue | 🟡 Minor

Stale comment — references dev:light:test but command now runs dev:website:test.

Line 203's comment still says "Use dev:light:test to ensure Next.js loads .env.test.local for test database", but the command on line 205 is npm run dev:website:test. Also, since npm resolves to apps/website/package.json's dev:website:test (a bare next dev --port 3000), the .env.test.local loading behavior depends entirely on Next.js's built-in NODE_ENV=test cascade — not on dotenv-cli. Update the comment to match reality, and see my note on apps/website/package.json line 7 for the deeper divergence.

🛠️ Suggested comment update
-        // Use dev:light:test to ensure Next.js loads .env.test.local for test database
+        // Use dev:website:test on port 3000; Next.js loads .env.test.local via NODE_ENV=test cascade
         command:
           "npm run dev:website:test", // Use light mode for 8GB RAM with test environment
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/tests/config/playwright.config.ts` around lines 203 - 215,
Update the stale comment to accurately describe the current command and
behavior: replace the "dev:light:test" reference with the actual command "npm
run dev:website:test" and note that Next.js will load .env.test.local via the
NODE_ENV/NEXT_PUBLIC_APP_ENV set to "test" (not via dotenv-cli); reference the
command string "npm run dev:website:test" and the env keys APP_ENV,
NEXT_PUBLIC_APP_ENV, NODE_ENV so readers know why the test DB is picked up.
.github/workflows/manual-deploy-parallel.yml (1)

123-141: ⚠️ Potential issue | 🔴 Critical

🐛 Retry loop is broken: set -e + command substitution will short-circuit retries, plus a copy-paste log message.

Three issues packed into this website deploy loop:

  1. Retries never happen on failure. set -euo pipefail combined with deploy_output=$(npx vercel deploy ...) makes the whole step exit the moment vercel deploy returns non-zero — the subsequent if [ $? -eq 0 ] check and the retry logic below become unreachable. You need to execute the command in an if condition (or append || true) so set -e doesn't abort.
  2. Stdout is captured, so users won't see Vercel's progress live in the Actions UI — only after the command completes (or never, if it fails silently as above).
  3. "Admin deployment is ready in production." is logged inside the website job (line 129) — copy-paste from the admin job.
🛠️ Proposed fix
           for attempt in 1 2 3 4 5 6; do
             echo "Deploy website attempt ${attempt}/6"
-            deploy_output=$(npx vercel deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }})
-            if [ $? -eq 0 ]; then
-                # Extract URL from output if possible, or use standard prod URL for aliasing
-                prod_url=$(echo "$deploy_output" | grep -Eo 'https://[^ ]+\.vercel\.app' | tail -n 1 || echo "https://elzatona-web.com")
-                echo "Admin deployment is ready in production."
-                set_website_aliases "$prod_url"
-                exit 0
-            fi
+            deploy_log="$(mktemp)"
+            if npx vercel deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log"; then
+                prod_url=$(grep -Eo 'https://[^ ]+\.vercel\.app' "$deploy_log" | tail -n 1 || echo "https://elzatona-web.com")
+                echo "Website deployment is ready in production: ${prod_url}"
+                set_website_aliases "$prod_url"
+                exit 0
+            fi

Note: pipe + tee works because pipefail is set — the overall pipeline's status reflects vercel deploy's exit code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/manual-deploy-parallel.yml around lines 123 - 141, The
deploy loop currently aborts because command substitution with set -e causes the
step to exit on non-zero and it also hides live output and logs the wrong
message; change the loop so the vercel command is executed in a conditional
(e.g. if npx vercel ...; then) or run it piped through tee while still checking
its exit status so set -e doesn't short-circuit (preserve capturing into
deploy_output while streaming stdout), ensure the exit check uses that
conditional rather than testing $?, and update the success echo from "Admin
deployment is ready in production." to "Website deployment is ready in
production." before calling set_website_aliases with the extracted prod_url.
apps/website/package.json (1)

1-33: ⚠️ Potential issue | 🟡 Minor

CI format:check is failing — run Prettier.

Pipeline reports 15 files with Prettier issues. Please run pnpm run format at the repo root before pushing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/package.json` around lines 1 - 33, CI failed due to Prettier
formatting errors; run the repository formatter and commit the fixes. From the
repo root run the project's format command (pnpm run format) or execute a
Prettier write (e.g., pnpm -w prettier --write .) to fix the 15 files, then
stage and push the changed files; also add a consistent "format" script to the
root package.json if missing so future contributors can run pnpm run format
easily (note: the apps/website/package.json currently defines "scripts" but no
local "format" script).
apps/website/src/app/features/guided-learning/components/GuidedLearningHeader.tsx (1)

5-12: ⚠️ Potential issue | 🔴 Critical

Remove the stale required daysRange prop.

The component no longer uses daysRange, and GuidedLearningPage no longer passes it. Keeping it required will break the updated call site.

Proposed fix
 interface GuidedLearningHeaderProps {
   readonly questionsRange: string;
-  readonly daysRange: string;
 }

 export function GuidedLearningHeader({
   questionsRange,
 }: Readonly<GuidedLearningHeaderProps>) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/website/src/app/features/guided-learning/components/GuidedLearningHeader.tsx`
around lines 5 - 12, Remove the stale daysRange prop from the component props:
delete daysRange from the GuidedLearningHeaderProps interface and remove it from
the GuidedLearningHeader parameter destructuring so the function only takes
questionsRange (update the Readonly<GuidedLearningHeaderProps> usage
accordingly); also scan for any internal references to daysRange inside
GuidedLearningHeader and remove or replace them so the component compiles
without that prop.
apps/website/src/app/features/guided-learning/page.tsx (1)

25-31: ⚠️ Potential issue | 🟠 Major

Remove the unused milestoneRange binding.

Line 30 destructures milestoneRange, but the page no longer passes or renders it.

Proposed fix
   const {
     plans,
     isLoading: plansLoading,
     error,
     questionsRange,
-    milestoneRange,
   } = useGuidedLearningPlans();

As per coding guidelines, No unused variables in TypeScript or explicit disable with reason.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/features/guided-learning/page.tsx` around lines 25 - 31,
The destructured binding milestoneRange from useGuidedLearningPlans is unused;
remove milestoneRange from the destructuring in page.tsx so the hook return is
only assigned to plans, isLoading (plansLoading), error, and questionsRange.
Locate the const { ... } = useGuidedLearningPlans() statement and delete the
milestoneRange identifier (or if you prefer, replace the full destructure with
an explicit list excluding milestoneRange) to satisfy the "no unused variables"
rule.
apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts (1)

26-39: ⚠️ Potential issue | 🟠 Major

Validate migrated localStorage before restoring milestone state.

A legacy active-guided-plan can parse but have no milestones, and plan-progress-${plan.id} can point to a removed milestone. In both cases this hook restores an invalid active plan instead of clearing or falling back.

Suggested fix
-    const stored = localStorage.getItem("active-guided-plan");
-    if (stored) {
-      try {
-        const plan = JSON.parse(stored) as LearningPlan;
-        setCurrentPlan(plan);
-        setMilestones(plan.milestones || []);
-
-        // Track current milestone from storage or default to first
-        const storedMilestone = localStorage.getItem(`plan-progress-${plan.id}`);
-        if (storedMilestone) {
-          setCurrentMilestoneId(storedMilestone);
-        } else if (plan.milestones && plan.milestones.length > 0) {
-          setCurrentMilestoneId(plan.milestones[0].id);
-        }
-      } catch {
-        console.warn("Failed to parse active guided plan from localStorage");
-        localStorage.removeItem("active-guided-plan");
-      }
-    }
+    try {
+      const stored = localStorage.getItem("active-guided-plan");
+      if (!stored) return;
+
+      const parsedPlan = JSON.parse(stored) as Partial<LearningPlan>;
+      if (
+        typeof parsedPlan.id !== "string" ||
+        !Array.isArray(parsedPlan.milestones)
+      ) {
+        localStorage.removeItem("active-guided-plan");
+        return;
+      }
+
+      const plan = parsedPlan as LearningPlan;
+      const storedMilestone = localStorage.getItem(`plan-progress-${plan.id}`);
+      const nextMilestoneId =
+        storedMilestone &&
+        plan.milestones.some((milestone) => milestone.id === storedMilestone)
+          ? storedMilestone
+          : plan.milestones[0]?.id ?? null;
+
+      setCurrentPlan(plan);
+      setMilestones(plan.milestones);
+      setCurrentMilestoneId(nextMilestoneId);
+    } catch (error) {
+      console.warn("Failed to restore active guided plan from localStorage", error);
+      localStorage.removeItem("active-guided-plan");
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts` around
lines 26 - 39, The hook currently restores a parsed legacy plan without
validating milestones or the saved milestone id; update useActivePlan to verify
that plan.milestones is a non-empty array and that any stored milestone id (from
localStorage.getItem(`plan-progress-${plan.id}`)) exists in plan.milestones
before calling setCurrentPlan, setMilestones, and setCurrentMilestoneId; if
plan.milestones is missing/empty or the stored milestone id is not found, clear
the invalid keys from localStorage (remove "active-guided-plan" and
`plan-progress-${plan.id}`) and call
setCurrentPlan(null)/setMilestones([])/setCurrentMilestoneId(null) or, if
milestones exist but no stored id, fallback to plan.milestones[0].id; use
explicit checks (Array.isArray(plan.milestones), find by id) around
setCurrentPlan, setMilestones, and setCurrentMilestoneId to locate this logic in
useActivePlan.
🧹 Nitpick comments (1)
apps/website/src/app/lib/studyPlans.test.ts (1)

15-18: Use @ts-expect-error so the legacy-field checks keep guarding the type.

@ts-ignore will keep passing even if schedule or estimatedTimePerDay are reintroduced. @ts-expect-error makes that regression visible.

Proposed fix
-    // `@ts-ignore` - checking that legacy schedule is gone
+    // `@ts-expect-error` - checking that legacy schedule is gone
     expect(plan?.schedule).toBeUndefined();
@@
-    // `@ts-ignore`
+    // `@ts-expect-error` - checking that legacy estimatedTimePerDay is gone
     expect(plan?.estimatedTimePerDay).toBeUndefined();

Also applies to: 33-37

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/app/lib/studyPlans.test.ts` around lines 15 - 18, Replace
the non-asserting TypeScript ignores in the tests with assertions so regressions
surface: in apps/website/src/app/lib/studyPlans.test.ts, change the two
occurrences that use "@ts-ignore" before checking legacy fields on the result of
getStudyPlanById("one-week-intensive") (the assertions expecting plan?.schedule
toBeUndefined and the later check for plan?.estimatedTimePerDay) to
"@ts-expect-error" so the TypeScript compiler fails if those legacy fields are
ever reintroduced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/manual-deploy-parallel.yml:
- Around line 125-130: The fallback prod_url currently set from deploy_output
uses the alias domain ("https://elzatona-web.com"), which can cause vercel alias
to no-op or fail; update the deployment logic around deploy_output/prod_url (the
block that runs `npx vercel deploy --prebuilt --prod --yes --token=...` and then
calls set_website_aliases) so that when the grep extraction fails you do not
default to the alias—either fail loudly (exit with non-zero and log the full
deploy_output) or replace the fallback with a robust lookup (use `vercel ls
--prod` or `vercel inspect` to retrieve the latest deployment URL) and only call
set_website_aliases with a real deployment URL; ensure deploy_output, prod_url,
and set_website_aliases are updated accordingly.
- Around line 178-185: The cache key for the "Setup Vercel and Next.js Caching"
step is too generic and may collide with other jobs; update the cache key and
restore-keys to include an "admin" scope (e.g., change ${{ runner.os
}}-next-cache-${{ github.sha }} and ${{ runner.os }}-next-cache- to include
"admin" such as ${{ runner.os }}-admin-next-cache-${{ github.sha }} and ${{
runner.os }}-admin-next-cache-) so this job's cache is uniquely namespaced to
admin.
- Around line 94-107: The deploy uses vercel deploy --prebuilt which requires
.vercel/output produced by the build steps gated by inputs
validate_build_website/validate_build_admin; make those inputs non-optional or
add a pre-deploy guard in the deploy job to fail if .vercel/output is missing
(e.g., check for the directory and exit with error) so deploy never runs with a
stale/missing .vercel/output; update the workflow so either
validate_build_website and validate_build_admin default to true or include an
explicit conditional/step before vercel deploy --prebuilt that verifies
.vercel/output exists and fails fast if it does not.
- Around line 88-92: Update the JSON payload in the curl PATCH request that sets
Vercel project settings: replace the current "installCommand":"true" with the
recommended pattern ("installCommand": null) or an empty string
("installCommand": "") in the -d '{"buildCommand":"pnpm --filter website
build","outputDirectory":"apps/website/.next","installCommand":"true"}' payload
so the API explicitly skips install per Vercel docs; ensure the modified payload
preserves buildCommand and outputDirectory exactly as shown.

In `@apps/website/package.json`:
- Line 7: The app-level npm script "dev:website:test" diverges from the root
script and prevents .env.test.local and NODE_OPTIONS from being applied; either
remove this app-level "dev:website:test" so Playwright's npm run resolves to the
root script, or change the app-level "dev:website:test" to match the root
behavior by invoking dotenv to load .env.test.local and setting
NODE_OPTIONS=--max-old-space-size=1536 (so Playwright's webServer.command: "npm
run dev:website:test" and the comment in playwright.config.ts remain correct).
Ensure you update the script named dev:website:test and keep Playwright's
webServer.command and the env-loading semantics consistent.

In `@apps/website/src/app/features/guided-learning/components/ActivePlanView.tsx`:
- Around line 29-35: The current calculation of currentMilestoneIndex (using
milestones.findIndex(...) + 1) can produce 1/0 when milestones is empty; change
the logic to guard the empty state by checking totalMilestones (or
milestones.length) first and set currentMilestoneIndex to 0 or null when there
are no milestones, update any rendering logic that uses currentMilestoneIndex
(and the related display code around lines 94-96) to show an empty/placeholder
state (e.g., “—” or “0/0”) instead of "1/0", keeping the existing variables
completedMilestones, totalMilestones, progressPercent and currentMilestoneId to
locate and adjust the code paths.
- Around line 47-49: The JSX currently falls back to plan.name which doesn't
exist on the LearningPlan/StudyPlan type; update the rendering in ActivePlanView
to use only plan.title (remove the || plan.name fallback) and ensure any
references to plan.name in the ActivePlanView component are eliminated so the
code compiles against the StudyPlan/LearningPlan types.

In
`@apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx`:
- Around line 63-65: Remove the nonexistent fallback to plan.name and use
plan.title only: in LearningPlanCard (the h3 rendering that currently uses
{plan.title || plan.name}) replace that expression with just plan.title, and do
the same fix in ActivePlanView.tsx where a similar expression appears (around
the heading/render at line ~48) so code conforms to the LearningPlan/StudyPlan
type which guarantees title is present.

In
`@apps/website/src/app/features/guided-learning/types/guided-learning.types.ts`:
- Around line 1-6: The LearningPlan interface references a non-existent type
LearningPlanSection via the sections field; remove the legacy sections?:
LearningPlanSection[] property from the LearningPlan interface (which already
extends StudyPlan and therefore has milestones: StudyMilestone[]) to fix the
TypeScript compile error and rely on StudyPlan.milestones instead.

In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts`:
- Around line 71-73: Update the type signature of getMilestoneRange so the
parameter uses unknown[] instead of any[]: change plans: { milestones?: any[]
}[] to plans: { milestones?: unknown[] }[] in the getMilestoneRange function
declaration; ensure no other references rely on any-specific operations (the
function only reads .length, so no runtime changes needed).

In `@apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts`:
- Around line 46-75: Ensure the hardcoded fallback is always used first and that
Supabase errors never cause a 500: initialize plans from hardcodedPlans
immediately (keep plans = [...hardcodedPlans]) and acquire the client with
getSupabaseClient/getSupabaseClientWithAnonKey inside a try/catch that only logs
and returns plans on failure instead of throwing. When fetching from supabase
(.from("learning_plans").select(...)), wrap the fetch in try/catch and validate
each dbPlans item before merging by checking it has the new shape (e.g., a
milestones array and expected fields, and does NOT have legacy schedule), filter
out invalid/legacy rows, dedupe by id (use hardcodedIds = new Set(plans.map(p =>
p.id))) and then append only validated db plans; ensure any errors are caught
and logged so the route still returns the hardcoded plans.

In `@apps/website/src/app/lib/studyPlans.ts`:
- Around line 597-602: The milestone object with id "long-m3" uses the slug
"performance-optimization" in its topics array; update that entry to the
human-readable topic title that matches StudyTopic.title (e.g., "Performance
Optimization") so it consistently matches other milestones and consumers
expecting StudyTopic.title; modify the topics array on the "long-m3" object to
replace the slug with the correct title string.

In `@docs/admin/deployment-troubleshooting.md`:
- Around line 1-32: Remove the trailing space in the document (the extra space
after "Vercel." in the "Silent Build Failures on Vercel" section) and reformat
the file with Prettier; run `pnpm run format` or `prettier --write
docs/admin/deployment-troubleshooting.md` and commit the formatted file so the
CI Prettier check passes.

In `@libs/common-ui/src/common/LearningModeSwitcher.tsx`:
- Around line 21-37: Remove the trailing spaces after the opening JSX tags that
are causing Prettier format failures in LearningModeSwitcher.tsx (notably the
first container <div and the inner active-pill <div> around the
isScrolled/isGuided logic), then reformat the file with the repo Prettier
settings (run pnpm prettier --write or your project's formatter command) so
indentation and spacing match the rest of libs/common-ui; ensure no extra spaces
exist after tag names and that the JSX attributes follow the project's 2-space
indentation style.
- Around line 40-63: The two toggle buttons in LearningModeSwitcher lack ARIA
semantics; update the component so the container wrapping the buttons (around
the Compass/Map buttons) uses role="group" with an aria-label (e.g., "Learning
mode") and add aria-pressed to each button driven by isGuided
(aria-pressed={isGuided} on the "Guided" button and aria-pressed={!isGuided} on
the "Free Style" button); keep the existing onClick handlers (setUserType) and
visible labels but ensure buttons have these ARIA attributes so screen readers
can announce the active state.

In `@libs/common-ui/src/components/molecules/HeroSection.tsx`:
- Around line 23-80: Prettier formatting failed for HeroSection.tsx; run the
repo formatter and commit the changes: run pnpm prettier --write (or your
monorepo's nx format:write) targeting
libs/common-ui/src/components/molecules/HeroSection.tsx (or run the global
format script), then stage and push the updated file so the CI
`format:check:tracked` passes; no code changes to AnimatedTitle, CTAButton,
UserStatistics, showAnimation, or personalizedContent logic are required—only
apply and commit the formatting fixes.
- Around line 57-66: The outer wrapper's anonymous "group" lets Tailwind's
group-hover variants trigger on both the glow and the CTAButton, causing the
shine to activate when hovering the blur; fix by converting the wrapper to a
named group (e.g., add a class like "group-wrapper" and use
"group-wrapper-hover:" scoped variants on the blur) and ensure the CTAButton
itself uses its own distinct named group (e.g., "group-cta") so its group-hover
styles only fire when hovering the button; also add pointer-events-none to the
glow element (the absolute -inset-1 bg-gradient... div) so hovering the halo
doesn't pass pointer events to the button's hover state.

In `@libs/common-ui/src/components/organisms/HomePageLayout.tsx`:
- Around line 38-78: Prettier formatting failed for the changed component; run
the project's formatter and re-stage the changes so the file containing the
HomePageLayout component is formatted according to the repo rules (fix
whitespace/linebreaks in the JSX wrapper and props), then re-run the pipeline;
ensure you run the same script invoked by format:check:tracked and commit the
formatted result.

In `@libs/types/src/lib/studyPlans.ts`:
- Around line 15-18: The StudyPlan type now requires milestones:
StudyMilestone[] and estimatedTotalTime: number, but the study plan data in the
utilities module still uses the legacy schedule field; update the data source
(the StudyPlan[] objects in libs/utilities/src/lib/studyPlans.ts) to either
replace schedule with a proper milestones array (each item matching
StudyMilestone) and add a numeric estimatedTotalTime for each plan, or add a
migration step that converts existing schedule -> milestones and computes
estimatedTotalTime before exporting the StudyPlan[]; ensure no objects keep the
old schedule field so strict type-checking against StudyPlan, StudyMilestone,
milestones and estimatedTotalTime passes.

---

Outside diff comments:
In @.github/workflows/manual-deploy-parallel.yml:
- Around line 123-141: The deploy loop currently aborts because command
substitution with set -e causes the step to exit on non-zero and it also hides
live output and logs the wrong message; change the loop so the vercel command is
executed in a conditional (e.g. if npx vercel ...; then) or run it piped through
tee while still checking its exit status so set -e doesn't short-circuit
(preserve capturing into deploy_output while streaming stdout), ensure the exit
check uses that conditional rather than testing $?, and update the success echo
from "Admin deployment is ready in production." to "Website deployment is ready
in production." before calling set_website_aliases with the extracted prod_url.

In `@apps/website/package.json`:
- Around line 1-33: CI failed due to Prettier formatting errors; run the
repository formatter and commit the fixes. From the repo root run the project's
format command (pnpm run format) or execute a Prettier write (e.g., pnpm -w
prettier --write .) to fix the 15 files, then stage and push the changed files;
also add a consistent "format" script to the root package.json if missing so
future contributors can run pnpm run format easily (note: the
apps/website/package.json currently defines "scripts" but no local "format"
script).

In
`@apps/website/src/app/features/guided-learning/components/GuidedLearningHeader.tsx`:
- Around line 5-12: Remove the stale daysRange prop from the component props:
delete daysRange from the GuidedLearningHeaderProps interface and remove it from
the GuidedLearningHeader parameter destructuring so the function only takes
questionsRange (update the Readonly<GuidedLearningHeaderProps> usage
accordingly); also scan for any internal references to daysRange inside
GuidedLearningHeader and remove or replace them so the component compiles
without that prop.

In
`@apps/website/src/app/features/guided-learning/components/PlanSelectionView.tsx`:
- Around line 1-111: Prettier formatting failed for the PlanSelectionView
component; run the repository formatter (e.g., pnpm format or npm run format)
and commit the auto-formatted changes so the file containing the
PlanSelectionView function and its JSX (including LearningPlanCard usage and the
completedPlans block) matches the project's style; re-run format:check:tracked
(or CI) to confirm the formatting issue is resolved before pushing.

In `@apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts`:
- Around line 26-39: The hook currently restores a parsed legacy plan without
validating milestones or the saved milestone id; update useActivePlan to verify
that plan.milestones is a non-empty array and that any stored milestone id (from
localStorage.getItem(`plan-progress-${plan.id}`)) exists in plan.milestones
before calling setCurrentPlan, setMilestones, and setCurrentMilestoneId; if
plan.milestones is missing/empty or the stored milestone id is not found, clear
the invalid keys from localStorage (remove "active-guided-plan" and
`plan-progress-${plan.id}`) and call
setCurrentPlan(null)/setMilestones([])/setCurrentMilestoneId(null) or, if
milestones exist but no stored id, fallback to plan.milestones[0].id; use
explicit checks (Array.isArray(plan.milestones), find by id) around
setCurrentPlan, setMilestones, and setCurrentMilestoneId to locate this logic in
useActivePlan.

In `@apps/website/src/app/features/guided-learning/page.tsx`:
- Around line 25-31: The destructured binding milestoneRange from
useGuidedLearningPlans is unused; remove milestoneRange from the destructuring
in page.tsx so the hook return is only assigned to plans, isLoading
(plansLoading), error, and questionsRange. Locate the const { ... } =
useGuidedLearningPlans() statement and delete the milestoneRange identifier (or
if you prefer, replace the full destructure with an explicit list excluding
milestoneRange) to satisfy the "no unused variables" rule.

In `@apps/website/tests/config/playwright.config.ts`:
- Around line 203-215: Update the stale comment to accurately describe the
current command and behavior: replace the "dev:light:test" reference with the
actual command "npm run dev:website:test" and note that Next.js will load
.env.test.local via the NODE_ENV/NEXT_PUBLIC_APP_ENV set to "test" (not via
dotenv-cli); reference the command string "npm run dev:website:test" and the env
keys APP_ENV, NEXT_PUBLIC_APP_ENV, NODE_ENV so readers know why the test DB is
picked up.

In `@package.json`:
- Around line 1-170: CI is failing Prettier formatting across 15 files; run the
repository formatting script and commit the changes to satisfy the quality gate:
execute the existing npm script "format" (pnpm run format) to apply Prettier
fixes, review the changed files, stage and commit the resulting diffs, then push
the commit so "format:check:tracked" / "format" checks in CI will pass.

---

Nitpick comments:
In `@apps/website/src/app/lib/studyPlans.test.ts`:
- Around line 15-18: Replace the non-asserting TypeScript ignores in the tests
with assertions so regressions surface: in
apps/website/src/app/lib/studyPlans.test.ts, change the two occurrences that use
"@ts-ignore" before checking legacy fields on the result of
getStudyPlanById("one-week-intensive") (the assertions expecting plan?.schedule
toBeUndefined and the later check for plan?.estimatedTimePerDay) to
"@ts-expect-error" so the TypeScript compiler fails if those legacy fields are
ever reintroduced.
🪄 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: 826da84f-2b72-4832-9b58-97e3892770b3

📥 Commits

Reviewing files that changed from the base of the PR and between f66eba7 and fedfc42.

📒 Files selected for processing (27)
  • .github/workflows/manual-deploy-parallel.yml
  • .husky/pre-commit
  • apps/website/package.json
  • apps/website/src/app/features/guided-learning/components/ActivePlanView.tsx
  • apps/website/src/app/features/guided-learning/components/GuidedLearningHeader.tsx
  • apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx
  • apps/website/src/app/features/guided-learning/components/PlanSelectionView.tsx
  • apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts
  • apps/website/src/app/features/guided-learning/hooks/useGuidedLearningPlans.ts
  • apps/website/src/app/features/guided-learning/page.tsx
  • apps/website/src/app/features/guided-learning/types/guided-learning.types.ts
  • apps/website/src/app/features/guided-learning/utils/daily-goals.ts
  • apps/website/src/app/features/guided-learning/utils/index.ts
  • apps/website/src/app/features/guided-learning/utils/plan-helpers.ts
  • apps/website/src/app/layout.tsx
  • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
  • apps/website/src/app/lib/studyPlans.test.ts
  • apps/website/src/app/lib/studyPlans.ts
  • apps/website/tests/config/playwright.config.ts
  • docs/admin/deployment-troubleshooting.md
  • libs/common-ui/src/common/LearningModeSwitcher.tsx
  • libs/common-ui/src/common/NavbarSimple.tsx
  • libs/common-ui/src/components/molecules/HeroSection.tsx
  • libs/common-ui/src/components/organisms/HomePageLayout.tsx
  • libs/types/src/lib/studyPlans.ts
  • nx.json
  • package.json
💤 Files with no reviewable changes (2)
  • apps/website/src/app/features/guided-learning/utils/index.ts
  • apps/website/src/app/features/guided-learning/utils/daily-goals.ts

Comment on lines 88 to 92
curl -fsS -X PATCH \
-H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"buildCommand":"npm run build","outputDirectory":"apps/website/.next"}' \
-d '{"buildCommand":"pnpm --filter website build","outputDirectory":"apps/website/.next","installCommand":"true"}' \
"https://api.vercel.com/v9/projects/${{ secrets.VERCEL_PROJECT_ID_WEBSITE }}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Vercel project settings installCommand skip install with prebuilt deploy

💡 Result:

To skip the install step in Vercel project settings for a prebuilt deployment, use the Install Command override in the Build & Development Settings. In the Vercel dashboard: 1. Go to your Project Settings > Build & Development. 2. Enable the Override toggle for Install Command. 3. Leave the Install Command field empty. This skips the install step entirely, similar to how leaving the Build Command empty skips the build step for static projects. For prebuilt deployments via CLI: - Run vercel build locally to generate .vercel/output (this may run install unless configured otherwise). - Then vercel deploy --prebuilt to upload prebuilt artifacts without re-running build/install on Vercel servers. In vercel.json, set "installCommand": null or "" (type allows null, likely skips). Note: Prebuilt deployments skip the build process on Vercel (including install), using local build output. For Git deployments with prebuilt assets already in repo (e.g., static site), empty installCommand + empty buildCommand + set Output Directory serves directly without install/build.

Citations:


Use null or empty string for installCommand instead of "true".

Vercel's documented pattern for skipping install is "installCommand": null or "installCommand": "". The current "true" works incidentally (as a shell no-op) but deviates from the recommended configuration and could become fragile if Vercel changes how it handles unrecognized commands. Update to match Vercel's API expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/manual-deploy-parallel.yml around lines 88 - 92, Update
the JSON payload in the curl PATCH request that sets Vercel project settings:
replace the current "installCommand":"true" with the recommended pattern
("installCommand": null) or an empty string ("installCommand": "") in the -d
'{"buildCommand":"pnpm --filter website
build","outputDirectory":"apps/website/.next","installCommand":"true"}' payload
so the API explicitly skips install per Vercel docs; ensure the modified payload
preserves buildCommand and outputDirectory exactly as shown.

Comment thread .github/workflows/manual-deploy-parallel.yml
Comment thread .github/workflows/manual-deploy-parallel.yml Outdated
Comment on lines +178 to +185
- name: Setup Vercel and Next.js Caching
uses: actions/cache@v4
with:
path: |
apps/admin/.next/cache
key: ${{ runner.os }}-next-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-next-cache-
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Admin cache key isn't scoped to "admin" — collision risk with any future ${runner.os}-next-cache-* entry.

The website job uses ${{ runner.os }}-website-next-cache-... (line 74-76), but the admin job uses the generic ${{ runner.os }}-next-cache-.... If another workflow/job ever writes under the same prefix, admin could restore a website (or other) .next/cache. Low severity today, but easy to fix and consistent with the website job.

🛠️ Proposed fix
-          key: ${{ runner.os }}-next-cache-${{ github.sha }}
+          key: ${{ runner.os }}-admin-next-cache-${{ github.sha }}
           restore-keys: |
-            ${{ runner.os }}-next-cache-
+            ${{ runner.os }}-admin-next-cache-
📝 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
- name: Setup Vercel and Next.js Caching
uses: actions/cache@v4
with:
path: |
apps/admin/.next/cache
key: ${{ runner.os }}-next-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-next-cache-
- name: Setup Vercel and Next.js Caching
uses: actions/cache@v4
with:
path: |
apps/admin/.next/cache
key: ${{ runner.os }}-admin-next-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-admin-next-cache-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/manual-deploy-parallel.yml around lines 178 - 185, The
cache key for the "Setup Vercel and Next.js Caching" step is too generic and may
collide with other jobs; update the cache key and restore-keys to include an
"admin" scope (e.g., change ${{ runner.os }}-next-cache-${{ github.sha }} and
${{ runner.os }}-next-cache- to include "admin" such as ${{ runner.os
}}-admin-next-cache-${{ github.sha }} and ${{ runner.os }}-admin-next-cache-) so
this job's cache is uniquely namespaced to admin.

Comment thread apps/website/package.json
"private": true,
"scripts": {
"dev": "next dev",
"dev:website:test": "next dev --port 3000",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Two scripts with the same name behave differently — Playwright will pick this one, which doesn't load .env.test.local explicitly.

Root package.json defines dev:website:test as ... dotenv -e .env.test.local -- nx serve website --port=3000 with explicit memory cap and env forcing. This app-level one is a plain next dev --port 3000. Playwright's webServer.command: "npm run dev:website:test" (run with cwd inside apps/website) resolves to this script, not the root one, so:

  • .env.test.local isn't loaded via dotenv-cli — you're relying on Next.js's built-in env cascade plus Playwright's webServer.env forcing NODE_ENV=test. That usually works, but silently diverges from the documented "loads .env.test.local" comment in playwright.config.ts (line 203).
  • No NODE_OPTIONS=--max-old-space-size=1536, so the 8GB-RAM guardrail mentioned elsewhere is lost in E2E runs.

Consider aligning one of the two (either drop this app-level script and point Playwright at the root script via npm run --prefix ../.. dev:website:test, or replicate the env loading + memory flag here).

🧰 Tools
🪛 GitHub Actions: CI Checks (Lint, Type Check, Build)

[error] format check failed: Code style issues found in 15 files. Command 'pnpm run format:check:tracked' (bash scripts/ci-quality-gate.sh format) exited with code 1.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/package.json` at line 7, The app-level npm script
"dev:website:test" diverges from the root script and prevents .env.test.local
and NODE_OPTIONS from being applied; either remove this app-level
"dev:website:test" so Playwright's npm run resolves to the root script, or
change the app-level "dev:website:test" to match the root behavior by invoking
dotenv to load .env.test.local and setting
NODE_OPTIONS=--max-old-space-size=1536 (so Playwright's webServer.command: "npm
run dev:website:test" and the comment in playwright.config.ts remain correct).
Ensure you update the script named dev:website:test and keep Playwright's
webServer.command and the env-loading semantics consistent.

Comment thread libs/common-ui/src/common/LearningModeSwitcher.tsx Outdated
Comment thread libs/common-ui/src/components/molecules/HeroSection.tsx
Comment on lines +57 to +66
<div className="relative inline-block group">
<div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
<CTAButton
href={personalizedContent.ctaLink}
text={personalizedContent.cta}
icon={personalizedContent.icon}
color={personalizedContent.color}
showAnimation={showAnimation}
/>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Tailwind CSS support named groups like group/nameandgroup-hover/name:* in the version commonly used (Tailwind v3.2+)?

💡 Result:

Yes, Tailwind CSS supports named groups using the syntax group/name on the parent element and group-hover/name:* (or other state variants like group-hover/name:bg-red-500) on child elements, starting from version 3.2 and available in all subsequent versions including v3.2+ which is the commonly used version as of 2026. This feature was introduced in Tailwind CSS v3.2 via variant modifiers for nested groups and multiple peers. The official documentation explicitly documents this syntax for differentiating nested groups: apply group/{name} to the specific parent and use group-hover/{name} (etc.) on descendants. Examples from official sources: - Parent:

  • - Child: This works with all group state variants (hover, focus, etc.) and requires no configuration.

    Citations:


  • Use named groups to prevent hover state leakage.

    The nested unnamed .group here will cause Tailwind to match group-hover:* variants on both the outer wrapper and the inner CTAButton, triggering the shine animation when hovering over the blur halo instead of just the button itself.

    Use named groups to scope them independently:

    Fix using named groups
    -              <div className="relative inline-block group">
    -                <div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
    +              <div className="relative inline-block group/cta">
    +                <div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 group-hover/cta:opacity-50 transition duration-1000 group-hover/cta:duration-200 pointer-events-none"></div>
                     <CTAButton

    Adding pointer-events-none on the glow prevents accidental hover triggers from the blur halo tail.

    📝 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
    <div className="relative inline-block group">
    <div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
    <CTAButton
    href={personalizedContent.ctaLink}
    text={personalizedContent.cta}
    icon={personalizedContent.icon}
    color={personalizedContent.color}
    showAnimation={showAnimation}
    />
    </div>
    <div className="relative inline-block group/cta">
    <div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 group-hover/cta:opacity-50 transition duration-1000 group-hover/cta:duration-200 pointer-events-none"></div>
    <CTAButton
    href={personalizedContent.ctaLink}
    text={personalizedContent.cta}
    icon={personalizedContent.icon}
    color={personalizedContent.color}
    showAnimation={showAnimation}
    />
    </div>
    🧰 Tools
    🪛 GitHub Actions: CI Checks (Lint, Type Check, Build)

    [warning] Code style issue detected by format:check:tracked (Prettier).

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/common-ui/src/components/molecules/HeroSection.tsx` around lines 57 -
    66, The outer wrapper's anonymous "group" lets Tailwind's group-hover variants
    trigger on both the glow and the CTAButton, causing the shine to activate when
    hovering the blur; fix by converting the wrapper to a named group (e.g., add a
    class like "group-wrapper" and use "group-wrapper-hover:" scoped variants on the
    blur) and ensure the CTAButton itself uses its own distinct named group (e.g.,
    "group-cta") so its group-hover styles only fire when hovering the button; also
    add pointer-events-none to the glow element (the absolute -inset-1
    bg-gradient... div) so hovering the halo doesn't pass pointer events to the
    button's hover state.
    

    Comment thread libs/common-ui/src/components/organisms/HomePageLayout.tsx
    Comment thread libs/types/src/lib/studyPlans.ts
    Copy link
    Copy Markdown

    @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: 4

    Caution

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

    ⚠️ Outside diff range comments (1)
    libs/utilities/src/lib/studyPlans.ts (1)

    670-731: ⚠️ Potential issue | 🟡 Minor

    3-Month plan has very sparse task coverage.

    The 3-month comprehensive plan advertises 36 total hours but only defines 4 tasks totaling ~9 hours (js-advanced-study 90m, js-advanced-practice 90m, complex-design 180m, expert-mock 180m). Compared to the 1-week plan (13 tasks) and 1-month plan (12 tasks), this will likely feel empty in the UI once users drill into milestones. Worth adding more tasks per milestone so the task list matches the advertised duration.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/utilities/src/lib/studyPlans.ts` around lines 670 - 731, The 3-month
    plan's milestones (ids: "m1","m2","m3") only contain four tasks (task ids:
    "js-advanced-study","js-advanced-practice","complex-design","expert-mock") whose
    estimatedTime totals far less than the advertised 36 hours; update the
    milestones array by adding more tasks (or splitting existing ones) to each
    milestone so the sum of estimatedTime across tasks matches ~2160 minutes (36h),
    ensuring each new task object includes id, title, description, type,
    estimatedTime and optional resourceUrl; modify the tasks arrays inside the
    milestone objects in libs/utilities/src/lib/studyPlans.ts (look for the
    milestones variable) to add multiple smaller study/practice/reading/review tasks
    per milestone and balance time distribution across m1, m2 and m3.
    
    ♻️ Duplicate comments (2)
    apps/website/src/app/features/guided-learning/utils/plan-helpers.ts (1)

    71-71: ⚠️ Potential issue | 🟠 Major

    Replace any[] with unknown[].

    getMilestoneRange only reads .length, so unknown[] keeps the strict-typing posture without weakening inference.

    Proposed fix
    -export function getMilestoneRange(plans: { milestones?: any[] }[]): string {
    +export function getMilestoneRange(plans: { milestones?: unknown[] }[]): string {

    As per coding guidelines: "No any types in TypeScript without explicit eslint-disable and justification comment."

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts` at line
    71, Update the parameter type in getMilestoneRange to avoid using any: change
    the plans parameter from { milestones?: any[] }[] to { milestones?: unknown[]
    }[] so the function preserves strict typing while still allowing reads like
    .length; keep the rest of the function unchanged and do not add eslint-disable
    comments.
    
    libs/common-ui/src/components/molecules/HeroSection.tsx (1)

    65-66: ⚠️ Potential issue | 🟡 Minor

    Scope the CTA hover group and ignore the glow for pointer events.

    The anonymous outer group can leak hover state into nested group-hover:* styles inside CTAButton, and the absolute glow can trigger hover outside the button bounds. Use a named group and make the glow non-interactive.

    🎯 Proposed scoped hover fix
    -              <div className="relative inline-block group">
    -                <div className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
    +              <div className="relative inline-block group/cta">
    +                <div
    +                  aria-hidden="true"
    +                  className="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-25 pointer-events-none group-hover/cta:opacity-50 transition duration-1000 group-hover/cta:duration-200"
    +                />

    Verify the workspace Tailwind version supports named groups before applying this exact syntax:

    #!/bin/bash
    # Description: Check Tailwind version references. Named groups require Tailwind support for group modifiers.
    
    rg -n -C2 '"tailwindcss"\s*:' --iglob 'package.json'
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/common-ui/src/components/molecules/HeroSection.tsx` around lines 65 -
    66, The outer anonymous hover group and glowing absolute element can leak hover
    state and catch pointer events; scope the hover by renaming the wrapper group
    (e.g., change class "group" to a named group like "group-cta") and update any
    nested group-hover usages in CTAButton to use that named group (e.g.,
    "group-cta/group-hover:*"); also make the glow element non-interactive by adding
    pointer-events-none to the absolute glow div. Verify your Tailwind version
    supports named groups before applying the named-group modifier changes.
    
    🧹 Nitpick comments (2)
    apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx (1)

    37-39: Nit: toLowerCase() is redundant given the typed union.

    StudyPlan.difficulty is already typed as "beginner" | "intermediate" | "advanced" in libs/types/src/lib/studyPlans.ts, so toLowerCase() plus the || difficultyColors.intermediate fallback is defensive against a case that the type system rules out. Not a defect — just dead insurance. If you want to keep the belt-and-suspenders for runtime safety against bad data, leaving it is fine; otherwise simplify to plan.difficulty as keyof typeof difficultyColors.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx`
    around lines 37 - 39, The code calls plan.difficulty.toLowerCase() before
    casting to difficultyColors keys, which is redundant because
    StudyPlan.difficulty is already the union "beginner"|"intermediate"|"advanced";
    simplify by removing the runtime toLowerCase() and cast plan.difficulty directly
    (update the binding that defines planDifficulty in LearningPlanCard.tsx to use
    plan.difficulty as keyof typeof difficultyColors and remove the unnecessary
    fallback logic tied to toLowerCase()).
    
    apps/website/src/app/features/guided-learning/utils/plan-helpers.ts (1)

    31-45: Unknown difficulties will sort to the top.

    difficultyOrder[diffX] || 0 maps any unknown difficulty to 0, which places it before beginner (1). If a plan ever arrives with a stray/typo difficulty, it silently jumps to the front of the list. Minor, but mapping unknown to Number.POSITIVE_INFINITY (or 99) pushes such entries to the end, which is usually the safer default.

    Proposed tweak
    -    return (difficultyOrder[diffA] || 0) - (difficultyOrder[diffB] || 0);
    +    return (
    +      (difficultyOrder[diffA] ?? Number.POSITIVE_INFINITY) -
    +      (difficultyOrder[diffB] ?? Number.POSITIVE_INFINITY)
    +    );
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts` around
    lines 31 - 45, The sortPlansByDifficulty function currently treats unknown
    difficulty keys as 0 which sorts them before "beginner"; change the fallback for
    difficulty lookup in the comparator to a large value (e.g.,
    Number.POSITIVE_INFINITY or 99) so unknown values are pushed to the end. Update
    the comparator where diffA/diffB are used with (difficultyOrder[diffA] ??
    Number.POSITIVE_INFINITY) and (difficultyOrder[diffB] ??
    Number.POSITIVE_INFINITY) (or use 99), keeping the same diffA/diffB variables
    and difficultyOrder map to locate the change.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Inline comments:
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts`:
    - Around line 47-69: getQuestionsRange currently returns two different semantic
    formats (counts like "A-B" or time like "A-B Hours"), which can cause callers to
    mislabel values; change getQuestionsRange to return a discriminated shape
    instead of a raw string (for example { kind: "questions" | "hours", label:
    string, min: number, max: number }) so callers can decide how to render; update
    the function logic that currently reads plans -> totalQuestions and plans ->
    duration?.totalHours to populate the appropriate kind and numeric min/max, and
    then update callers of getQuestionsRange to use the discriminant (kind) and
    label or format the string themselves.
    
    In `@failed_job_log.txt`:
    - Around line 1-415: Remove the committed CI run log file (failed_job_log.txt)
    from the repository, commit the deletion (git rm failed_job_log.txt; git commit
    -m "chore: remove CI run log"), add a rule to .gitignore to prevent future
    checkins (e.g. failed_job_log.txt or a logs/ pattern), include a short sanitized
    excerpt in docs if needed, and if the log is already in main history scrub it
    from the repo using a history-rewrite tool (git filter-repo or BFG) and
    force-push the cleaned branch; attach full raw logs to the GitHub Actions
    artifacts instead of committing them.
    - Line 415: Update all GitHub workflow files that reference actions pinned to v4
    by replacing actions/checkout@v4, actions/setup-node@v4, actions/cache@v4, and
    pnpm/action-setup@v4 with their v5 releases (actions/checkout@v5,
    actions/setup-node@v5, actions/cache@v5, pnpm/action-setup@v5); search for those
    exact strings in your .github/workflows/*.yml files and perform the version
    bumps, then run CI to verify; if you need a temporary opt-in instead of bumping,
    set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true runner/workflow environment
    variable, but prefer updating the action versions.
    
    In `@libs/common-ui/src/components/molecules/HeroSection.tsx`:
    - Around line 26-27: In HeroSection (the two decorative blob <div>s that
    currently include "animate-pulse"), make the animation respect user motion
    preferences by replacing "animate-pulse" with "motion-safe:animate-pulse
    motion-reduce:animate-none" and mark each decorative element as non-semantic by
    adding aria-hidden="true"; locate the elements by their existing class strings
    (e.g., "absolute top-10 left-10 w-72 h-72 bg-indigo-300..." and "absolute
    bottom-10 right-10 w-72 h-72 bg-purple-300...") and apply these changes to both
    blobs.
    
    ---
    
    Outside diff comments:
    In `@libs/utilities/src/lib/studyPlans.ts`:
    - Around line 670-731: The 3-month plan's milestones (ids: "m1","m2","m3") only
    contain four tasks (task ids:
    "js-advanced-study","js-advanced-practice","complex-design","expert-mock") whose
    estimatedTime totals far less than the advertised 36 hours; update the
    milestones array by adding more tasks (or splitting existing ones) to each
    milestone so the sum of estimatedTime across tasks matches ~2160 minutes (36h),
    ensuring each new task object includes id, title, description, type,
    estimatedTime and optional resourceUrl; modify the tasks arrays inside the
    milestone objects in libs/utilities/src/lib/studyPlans.ts (look for the
    milestones variable) to add multiple smaller study/practice/reading/review tasks
    per milestone and balance time distribution across m1, m2 and m3.
    
    ---
    
    Duplicate comments:
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts`:
    - Line 71: Update the parameter type in getMilestoneRange to avoid using any:
    change the plans parameter from { milestones?: any[] }[] to { milestones?:
    unknown[] }[] so the function preserves strict typing while still allowing reads
    like .length; keep the rest of the function unchanged and do not add
    eslint-disable comments.
    
    In `@libs/common-ui/src/components/molecules/HeroSection.tsx`:
    - Around line 65-66: The outer anonymous hover group and glowing absolute
    element can leak hover state and catch pointer events; scope the hover by
    renaming the wrapper group (e.g., change class "group" to a named group like
    "group-cta") and update any nested group-hover usages in CTAButton to use that
    named group (e.g., "group-cta/group-hover:*"); also make the glow element
    non-interactive by adding pointer-events-none to the absolute glow div. Verify
    your Tailwind version supports named groups before applying the named-group
    modifier changes.
    
    ---
    
    Nitpick comments:
    In
    `@apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx`:
    - Around line 37-39: The code calls plan.difficulty.toLowerCase() before casting
    to difficultyColors keys, which is redundant because StudyPlan.difficulty is
    already the union "beginner"|"intermediate"|"advanced"; simplify by removing the
    runtime toLowerCase() and cast plan.difficulty directly (update the binding that
    defines planDifficulty in LearningPlanCard.tsx to use plan.difficulty as keyof
    typeof difficultyColors and remove the unnecessary fallback logic tied to
    toLowerCase()).
    
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts`:
    - Around line 31-45: The sortPlansByDifficulty function currently treats unknown
    difficulty keys as 0 which sorts them before "beginner"; change the fallback for
    difficulty lookup in the comparator to a large value (e.g.,
    Number.POSITIVE_INFINITY or 99) so unknown values are pushed to the end. Update
    the comparator where diffA/diffB are used with (difficultyOrder[diffA] ??
    Number.POSITIVE_INFINITY) and (difficultyOrder[diffB] ??
    Number.POSITIVE_INFINITY) (or use 99), keeping the same diffA/diffB variables
    and difficultyOrder map to locate the change.
    
    🪄 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: f8af5858-733b-490e-a536-afe390cb289e

    📥 Commits

    Reviewing files that changed from the base of the PR and between fedfc42 and 30c087f.

    📒 Files selected for processing (17)
    • apps/website/src/app/features/guided-learning/components/ActivePlanView.tsx
    • apps/website/src/app/features/guided-learning/components/GuidedLearningHeader.tsx
    • apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx
    • apps/website/src/app/features/guided-learning/components/PlanSelectionView.tsx
    • apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts
    • apps/website/src/app/features/guided-learning/page.tsx
    • apps/website/src/app/features/guided-learning/types/guided-learning.types.ts
    • apps/website/src/app/features/guided-learning/utils/plan-helpers.ts
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • apps/website/src/app/lib/studyPlans.ts
    • apps/website/tests/config/playwright.config.ts
    • docs/admin/deployment-troubleshooting.md
    • failed_job_log.txt
    • libs/common-ui/src/common/LearningModeSwitcher.tsx
    • libs/common-ui/src/components/molecules/HeroSection.tsx
    • libs/common-ui/src/components/organisms/HomePageLayout.tsx
    • libs/utilities/src/lib/studyPlans.ts
    ✅ Files skipped from review due to trivial changes (2)
    • apps/website/src/app/features/guided-learning/components/PlanSelectionView.tsx
    • docs/admin/deployment-troubleshooting.md
    🚧 Files skipped from review as they are similar to previous changes (8)
    • apps/website/tests/config/playwright.config.ts
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • apps/website/src/app/features/guided-learning/components/GuidedLearningHeader.tsx
    • libs/common-ui/src/common/LearningModeSwitcher.tsx
    • apps/website/src/app/features/guided-learning/types/guided-learning.types.ts
    • libs/common-ui/src/components/organisms/HomePageLayout.tsx
    • apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts
    • apps/website/src/app/lib/studyPlans.ts

    Comment thread apps/website/src/app/features/guided-learning/utils/plan-helpers.ts
    Comment thread failed_job_log.txt
    Comment on lines +1 to +415
    Deploy Admin Set up job 2026-04-17T21:49:40.1792838Z Current runner version: '2.333.1'
    Deploy Admin Set up job 2026-04-17T21:49:40.1814075Z ##[group]Runner Image Provisioner
    Deploy Admin Set up job 2026-04-17T21:49:40.1814844Z Hosted Compute Agent
    Deploy Admin Set up job 2026-04-17T21:49:40.1815275Z Version: 20260213.493
    Deploy Admin Set up job 2026-04-17T21:49:40.1815801Z Commit: 5c115507f6dd24b8de37d8bbe0bb4509d0cc0fa3
    Deploy Admin Set up job 2026-04-17T21:49:40.1816410Z Build Date: 2026-02-13T00:28:41Z
    Deploy Admin Set up job 2026-04-17T21:49:40.1816947Z Worker ID: {9b823b10-5fdd-4177-ae5b-ee3693d7aefd}
    Deploy Admin Set up job 2026-04-17T21:49:40.1817501Z Azure Region: westcentralus
    Deploy Admin Set up job 2026-04-17T21:49:40.1818043Z ##[endgroup]
    Deploy Admin Set up job 2026-04-17T21:49:40.1819093Z ##[group]Operating System
    Deploy Admin Set up job 2026-04-17T21:49:40.1819629Z Ubuntu
    Deploy Admin Set up job 2026-04-17T21:49:40.1820055Z 24.04.4
    Deploy Admin Set up job 2026-04-17T21:49:40.1820659Z LTS
    Deploy Admin Set up job 2026-04-17T21:49:40.1821098Z ##[endgroup]
    Deploy Admin Set up job 2026-04-17T21:49:40.1821550Z ##[group]Runner Image
    Deploy Admin Set up job 2026-04-17T21:49:40.1822051Z Image: ubuntu-24.04
    Deploy Admin Set up job 2026-04-17T21:49:40.1822558Z Version: 20260413.86.1
    Deploy Admin Set up job 2026-04-17T21:49:40.1823421Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20260413.86/images/ubuntu/Ubuntu2404-Readme.md
    Deploy Admin Set up job 2026-04-17T21:49:40.1824759Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20260413.86
    Deploy Admin Set up job 2026-04-17T21:49:40.1825557Z ##[endgroup]
    Deploy Admin Set up job 2026-04-17T21:49:40.1826476Z ##[group]GITHUB_TOKEN Permissions
    Deploy Admin Set up job 2026-04-17T21:49:40.1827967Z Contents: read
    Deploy Admin Set up job 2026-04-17T21:49:40.1828500Z Metadata: read
    Deploy Admin Set up job 2026-04-17T21:49:40.1828984Z ##[endgroup]
    Deploy Admin Set up job 2026-04-17T21:49:40.1830946Z Secret source: Actions
    Deploy Admin Set up job 2026-04-17T21:49:40.1831650Z Prepare workflow directory
    Deploy Admin Set up job 2026-04-17T21:49:40.2093414Z Prepare all required actions
    Deploy Admin Set up job 2026-04-17T21:49:40.2125691Z Getting action download info
    Deploy Admin Set up job 2026-04-17T21:49:40.6277710Z Download action repository 'actions/checkout@v4' (SHA:34e114876b0b11c390a56381ad16ebd13914f8d5)
    Deploy Admin Set up job 2026-04-17T21:49:40.7467332Z Download action repository 'pnpm/action-setup@v4' (SHA:b906affcce14559ad1aafd4ab0e942779e9f58b1)
    Deploy Admin Set up job 2026-04-17T21:49:41.7547115Z Download action repository 'actions/setup-node@v4' (SHA:49933ea5288caeca8642d1e84afbd3f7d6820020)
    Deploy Admin Set up job 2026-04-17T21:49:41.8884348Z Download action repository 'actions/cache@v4' (SHA:0057852bfaa89a56745cba8c7296529d2fc39830)
    Deploy Admin Set up job 2026-04-17T21:49:42.2554390Z Complete job name: Deploy Admin
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3083483Z ##[group]Run actions/checkout@v4
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3083959Z with:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3084185Z ref: feat/optimize-ci-and-deploy-sync
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3084470Z repository: FoushWare/elzatona_web
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3084858Z token: ***
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3085065Z ssh-strict: true
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3085277Z ssh-user: git
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3085495Z persist-credentials: true
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3085728Z clean: true
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3085944Z sparse-checkout-cone-mode: true
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3086198Z fetch-depth: 1
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3086396Z fetch-tags: false
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3086597Z show-progress: true
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3086796Z lfs: false
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3086977Z submodules: false
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3087173Z set-safe-directory: true
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3087641Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3924143Z Syncing repository: FoushWare/elzatona_web
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3925511Z ##[group]Getting Git version info
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3925984Z Working directory is '/home/runner/work/elzatona_web/elzatona_web'
    Deploy Admin Checkout repository 2026-04-17T21:49:42.3926774Z [command]/usr/bin/git version
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4488056Z git version 2.53.0
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4506709Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4518316Z Temporarily overriding HOME='/home/runner/work/_temp/10bbbbd5-1677-4e7b-aec4-c0c87085f301' before making global git config changes
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4519009Z Adding repository directory to the temporary git global config as a safe directory
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4529552Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/elzatona_web/elzatona_web
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4555557Z Deleting the contents of '/home/runner/work/elzatona_web/elzatona_web'
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4558329Z ##[group]Initializing the repository
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4561830Z [command]/usr/bin/git init /home/runner/work/elzatona_web/elzatona_web
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4638784Z hint: Using 'master' as the name for the initial branch. This default branch name
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4639579Z hint: will change to "main" in Git 3.0. To configure the initial branch name
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4640447Z hint: to use in all of your new repositories, which will suppress this warning,
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4641018Z hint: call:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4641312Z hint:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4641726Z hint: git config --global init.defaultBranch <name>
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4642179Z hint:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4642603Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4643296Z hint: 'development'. The just-created branch can be renamed via this command:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4643834Z hint:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4644134Z hint: git branch -m <name>
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4644473Z hint:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4644932Z hint: Disable this message with "git config set advice.defaultBranchName false"
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4645824Z Initialized empty Git repository in /home/runner/work/elzatona_web/elzatona_web/.git/
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4649534Z [command]/usr/bin/git remote add origin https://github.com/FoushWare/elzatona_web
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4674461Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4675063Z ##[group]Disabling automatic garbage collection
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4677852Z [command]/usr/bin/git config --local gc.auto 0
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4701509Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4702064Z ##[group]Setting up auth
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4706624Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4729256Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
    Deploy Admin Checkout repository 2026-04-17T21:49:42.4978726Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5002821Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5182110Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5205656Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5393859Z [command]/usr/bin/git config --local http.https://github.com/.extraheader AUTHORIZATION: basic ***
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5419826Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5420596Z ##[group]Fetching the repository
    Deploy Admin Checkout repository 2026-04-17T21:49:42.5427568Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/feat/optimize-ci-and-deploy-sync*:refs/remotes/origin/feat/optimize-ci-and-deploy-sync* +refs/tags/feat/optimize-ci-and-deploy-sync*:refs/tags/feat/optimize-ci-and-deploy-sync*
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3160847Z From https://github.com/FoushWare/elzatona_web
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3161764Z * [new branch] feat/optimize-ci-and-deploy-sync -> origin/feat/optimize-ci-and-deploy-sync
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3185167Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3185960Z ##[group]Determining the checkout info
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3191981Z [command]/usr/bin/git branch --list --remote origin/feat/optimize-ci-and-deploy-sync
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3211755Z origin/feat/optimize-ci-and-deploy-sync
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3215369Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3219364Z [command]/usr/bin/git sparse-checkout disable
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3746742Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3769738Z ##[group]Checking out the ref
    Deploy Admin Checkout repository 2026-04-17T21:49:45.3773705Z [command]/usr/bin/git checkout --progress --force -B feat/optimize-ci-and-deploy-sync refs/remotes/origin/feat/optimize-ci-and-deploy-sync
    Deploy Admin Checkout repository 2026-04-17T21:49:45.7538516Z Switched to a new branch 'feat/optimize-ci-and-deploy-sync'
    Deploy Admin Checkout repository 2026-04-17T21:49:45.7539366Z branch 'feat/optimize-ci-and-deploy-sync' set up to track 'origin/feat/optimize-ci-and-deploy-sync'.
    Deploy Admin Checkout repository 2026-04-17T21:49:45.7562655Z ##[endgroup]
    Deploy Admin Checkout repository 2026-04-17T21:49:45.7598898Z [command]/usr/bin/git log -1 --format=%H
    Deploy Admin Checkout repository 2026-04-17T21:49:45.7617639Z ceb93242f18483b5d1227a8efb9f8bd607a42fd4
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7772976Z ##[group]Run pnpm/action-setup@v4
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7827870Z with:
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7828084Z version: 10.33.0
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7828261Z dest: ~/setup-pnpm
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7828444Z run_install: null
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7828626Z cache: false
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7828828Z cache_dependency_path: pnpm-lock.yaml
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7829089Z package_json_file: package.json
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7829316Z standalone: false
    Deploy Admin Install pnpm 2026-04-17T21:49:45.7829490Z ##[endgroup]
    Deploy Admin Install pnpm 2026-04-17T21:49:45.8909521Z ##[group]Running self-installer...
    Deploy Admin Install pnpm 2026-04-17T21:49:46.4357716Z Progress: resolved 1, reused 0, downloaded 0, added 0
    Deploy Admin Install pnpm 2026-04-17T21:49:46.4443587Z Packages: +1
    Deploy Admin Install pnpm 2026-04-17T21:49:46.4444129Z +
    Deploy Admin Install pnpm 2026-04-17T21:49:46.8629595Z Progress: resolved 1, reused 0, downloaded 1, added 1, done
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0262160Z
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0262649Z dependencies:
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0262909Z + pnpm 10.33.0
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0263015Z
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0291355Z Done in 1s
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0438226Z ##[endgroup]
    Deploy Admin Install pnpm 2026-04-17T21:49:47.0441259Z Installation Completed!
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0566648Z ##[group]Run actions/setup-node@v4
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0566868Z with:
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567026Z node-version: 20
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567191Z cache: pnpm
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567341Z always-auth: false
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567515Z check-latest: false
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567783Z token: ***
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567943Z env:
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0568155Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.0568413Z ##[endgroup]
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.3153221Z Found in cache @ /opt/hostedtoolcache/node/20.20.2/x64
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.3159347Z ##[group]Environment details
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.9824514Z node: v20.20.2
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.9824881Z npm: 10.8.2
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.9825042Z yarn: 1.22.22
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.9825952Z ##[endgroup]
    Deploy Admin Setup Node.js 2026-04-17T21:49:47.9845294Z [command]/home/runner/setup-pnpm/node_modules/.bin/pnpm store path --silent
    Deploy Admin Setup Node.js 2026-04-17T21:49:48.2512537Z /home/runner/setup-pnpm/node_modules/.bin/store/v10
    Deploy Admin Setup Node.js 2026-04-17T21:49:48.4660494Z Cache hit for: node-cache-Linux-x64-pnpm-c1fa4408a2f88a7a3398f75e9407cf077d6dc825f6d3648966b2ca686a6fa52f
    Deploy Admin Setup Node.js 2026-04-17T21:49:49.6962787Z Received 33554432 of 436469304 (7.7%), 32.0 MBs/sec
    Deploy Admin Setup Node.js 2026-04-17T21:49:50.7011151Z Received 192937984 of 436469304 (44.2%), 91.9 MBs/sec
    Deploy Admin Setup Node.js 2026-04-17T21:49:51.6980388Z Received 377487360 of 436469304 (86.5%), 119.9 MBs/sec
    Deploy Admin Setup Node.js 2026-04-17T21:49:52.3791755Z Received 436469304 of 436469304 (100.0%), 113.0 MBs/sec
    Deploy Admin Setup Node.js 2026-04-17T21:49:52.3792562Z Cache Size: ~416 MB (436469304 B)
    Deploy Admin Setup Node.js 2026-04-17T21:49:52.3820597Z [command]/usr/bin/tar -xf /home/runner/work/_temp/4753f2c7-5887-4950-a697-1e1831cb846d/cache.tzst -P -C /home/runner/work/elzatona_web/elzatona_web --use-compress-program unzstd
    Deploy Admin Setup Node.js 2026-04-17T21:49:57.4183725Z Cache restored successfully
    Deploy Admin Setup Node.js 2026-04-17T21:49:57.4359607Z Cache restored from key: node-cache-Linux-x64-pnpm-c1fa4408a2f88a7a3398f75e9407cf077d6dc825f6d3648966b2ca686a6fa52f
    Deploy Admin Enable Corepack 2026-04-17T21:49:57.4576167Z ##[group]Run corepack enable
    Deploy Admin Enable Corepack 2026-04-17T21:49:57.4576448Z corepack enable
    Deploy Admin Enable Corepack 2026-04-17T21:49:57.4600917Z shell: /usr/bin/bash -e {0}
    Deploy Admin Enable Corepack 2026-04-17T21:49:57.4601127Z env:
    Deploy Admin Enable Corepack 2026-04-17T21:49:57.4601343Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Enable Corepack 2026-04-17T21:49:57.4601595Z ##[endgroup]
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.5569423Z ##[group]Run pnpm install --frozen-lockfile
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.5569734Z pnpm install --frozen-lockfile
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.5588524Z shell: /usr/bin/bash -e {0}
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.5588730Z env:
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.5588938Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.5589185Z ##[endgroup]
    Deploy Admin Install root dependencies 2026-04-17T21:49:57.6308583Z ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.33.0.tgz
    Deploy Admin Install root dependencies 2026-04-17T21:49:58.8950414Z Scope: all 13 workspace projects
    Deploy Admin Install root dependencies 2026-04-17T21:49:58.9895940Z Lockfile is up to date, resolution step is skipped
    Deploy Admin Install root dependencies 2026-04-17T21:49:59.1134895Z Progress: resolved 1, reused 0, downloaded 0, added 0
    Deploy Admin Install root dependencies 2026-04-17T21:49:59.2622246Z Packages: +2431
    Deploy Admin Install root dependencies 2026-04-17T21:49:59.2622999Z ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Deploy Admin Install root dependencies 2026-04-17T21:50:00.1144346Z Progress: resolved 2431, reused 360, downloaded 0, added 0
    Deploy Admin Install root dependencies 2026-04-17T21:50:01.1149684Z Progress: resolved 2431, reused 1085, downloaded 0, added 0
    Deploy Admin Install root dependencies 2026-04-17T21:50:02.1151068Z Progress: resolved 2431, reused 2282, downloaded 0, added 0
    Deploy Admin Install root dependencies 2026-04-17T21:50:03.1154314Z Progress: resolved 2431, reused 2417, downloaded 0, added 81
    Deploy Admin Install root dependencies 2026-04-17T21:50:04.1165302Z Progress: resolved 2431, reused 2417, downloaded 0, added 103
    Deploy Admin Install root dependencies 2026-04-17T21:50:05.1171370Z Progress: resolved 2431, reused 2417, downloaded 0, added 378
    Deploy Admin Install root dependencies 2026-04-17T21:50:06.1167251Z Progress: resolved 2431, reused 2417, downloaded 0, added 677
    Deploy Admin Install root dependencies 2026-04-17T21:50:07.1166003Z Progress: resolved 2431, reused 2417, downloaded 0, added 903
    Deploy Admin Install root dependencies 2026-04-17T21:50:08.1171558Z Progress: resolved 2431, reused 2417, downloaded 0, added 1043
    Deploy Admin Install root dependencies 2026-04-17T21:50:09.1177070Z Progress: resolved 2431, reused 2417, downloaded 0, added 1084
    Deploy Admin Install root dependencies 2026-04-17T21:50:10.1177952Z Progress: resolved 2431, reused 2417, downloaded 0, added 1202
    Deploy Admin Install root dependencies 2026-04-17T21:50:11.1183109Z Progress: resolved 2431, reused 2417, downloaded 0, added 1411
    Deploy Admin Install root dependencies 2026-04-17T21:50:12.1183918Z Progress: resolved 2431, reused 2417, downloaded 0, added 1861
    Deploy Admin Install root dependencies 2026-04-17T21:50:13.1187277Z Progress: resolved 2431, reused 2417, downloaded 0, added 1939
    Deploy Admin Install root dependencies 2026-04-17T21:50:14.2940851Z Progress: resolved 2431, reused 2417, downloaded 0, added 1940
    Deploy Admin Install root dependencies 2026-04-17T21:50:15.2234768Z Progress: resolved 2431, reused 2417, downloaded 0, added 2431, done
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3987436Z
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3988020Z dependencies:
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3988397Z + @daily-co/daily-js 0.84.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3988764Z + @heroicons/react 2.2.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3989104Z + @monaco-editor/react 4.7.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3989600Z + @radix-ui/react-checkbox 1.3.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3990022Z + @radix-ui/react-collapsible 1.1.12
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3990595Z + @radix-ui/react-dialog 1.1.15
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3990951Z + @radix-ui/react-label 2.1.8
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3991399Z + @radix-ui/react-popover 1.1.15
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3991784Z + @radix-ui/react-progress 1.1.8
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3992167Z + @radix-ui/react-scroll-area 1.2.10
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3992565Z + @radix-ui/react-select 2.2.6
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3992950Z + @radix-ui/react-separator 1.1.8
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3993329Z + @radix-ui/react-slot 1.2.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3993683Z + @radix-ui/react-switch 1.2.6
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994020Z + @radix-ui/react-tabs 1.1.13
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994322Z + @reactour/tour 3.8.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994596Z + @sentry/nextjs 10.46.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994939Z + @supabase/supabase-js 2.100.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3995311Z + @tanstack/react-query 5.95.2
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3995729Z + @tanstack/react-query-devtools 5.95.2
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3996149Z + @types/bcryptjs 2.4.6
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3996465Z + @types/dompurify 3.2.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3996778Z + @types/jsonwebtoken 9.0.10
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3997176Z + @types/react-syntax-highlighter 15.5.13
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3997576Z + @types/swagger-jsdoc 6.0.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3997952Z + @types/swagger-ui-react 5.18.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3998307Z + @vercel/speed-insights 1.3.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3998614Z + axios 1.15.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3998861Z + bcryptjs 3.0.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3999648Z + cheerio 1.2.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.3999984Z + class-variance-authority 0.7.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4000529Z + cloudinary 2.9.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4000825Z + clsx 2.1.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001081Z + critters 0.0.23
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001249Z + date-fns 4.1.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001424Z + dompurify 3.4.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001587Z + dotenv 17.3.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001852Z + firebase 12.11.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4002054Z + html-to-image 1.11.13
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4002524Z + jotai 2.19.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4002741Z + jsonwebtoken 9.0.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003000Z + lucide-react 0.542.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003172Z + next 16.2.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003346Z + next-auth 4.24.13
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003520Z + node-cron 4.2.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003697Z + node-fetch 2.7.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003862Z + nuqs 2.8.9
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004018Z + react 19.2.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004290Z + react-day-picker 9.14.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004578Z + react-dom 19.2.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004783Z + react-markdown 10.1.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005019Z + react-resizable-panels 2.1.9
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005408Z + react-syntax-highlighter 16.1.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005736Z + shiki 3.23.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005989Z + sonner 2.0.7
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006193Z + swagger-jsdoc 6.2.8
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006386Z + swagger-ui-react 5.32.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006581Z + tailwind-merge 3.5.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006776Z + tailwindcss-animate 1.0.7
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006985Z + validator 13.15.26
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007152Z + xss 1.0.15
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007311Z + zod 4.3.6
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007407Z
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007489Z devDependencies:
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007677Z + @eslint/eslintrc 3.3.5
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007971Z + @mermaid-js/mermaid-cli 11.12.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008227Z + @nx/eslint 22.6.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008397Z + @nx/jest 22.6.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008564Z + @nx/next 22.6.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008728Z + @nx/vite 22.6.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008902Z + @playwright/test 1.58.2
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4009182Z + @swc-node/register 1.11.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4009477Z + @swc/core 1.15.21
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4009753Z + @testing-library/jest-dom 6.9.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4010090Z + @testing-library/react 16.3.2
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4010731Z + @testing-library/user-event 14.6.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011062Z + @types/jest 30.0.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011330Z + @types/node 20.19.37
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011599Z + @types/react 19.2.14
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011883Z + @types/react-dom 19.2.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4012198Z + @vitest/coverage-v8 3.2.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4012498Z + autoprefixer 10.4.27
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4012803Z + dotenv-cli 11.0.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013001Z + eslint 9.39.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013186Z + eslint-config-next 15.5.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013380Z + glob 11.1.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013536Z + husky 9.1.7
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013775Z + jest 30.3.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014052Z + jest-environment-jsdom 30.3.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014346Z + js-yaml 3.14.2
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014559Z + lint-staged 16.4.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014756Z + minimatch 10.2.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014917Z + msw 2.12.14
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015080Z + null-loader 4.0.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015242Z + nx 22.6.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015404Z + oe-sonar-mcp 1.0.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015571Z + prettier 3.8.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015743Z + puppeteer 23.11.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015907Z + semver 7.7.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016083Z + tailwindcss 3.4.19
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016245Z + ts-jest 29.4.6
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016406Z + tsx 4.21.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016602Z + typescript 5.9.3
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016767Z + vercel 50.44.0
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016968Z + vite 7.3.2
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4017146Z + vite-tsconfig-paths 6.1.1
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4017352Z + vitest 3.2.4
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4017448Z
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4380704Z . prepare$ husky
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4799410Z . prepare: Done
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4925212Z ╭ Warning ─────────────────────────────────────────────────────────────────────╮
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4926208Z │ │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4927435Z │ Ignored build scripts: @firebase/util@1.15.0, @parcel/watcher@2.5.6, │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4928354Z │ @scarf/scarf@1.4.0, @sentry/cli@2.58.5, @swc/core@1.15.21, │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4929220Z │ @tree-sitter-grammars/tree-sitter-yaml@0.7.1, core-js-pure@3.49.0, │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4930128Z │ esbuild@0.27.0, esbuild@0.27.4, esbuild@0.27.7, less@4.5.1, msw@2.12.14, │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4931465Z │ nx@22.6.3, protobufjs@7.5.4, puppeteer@23.11.1, sharp@0.34.5, │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4932338Z │ tree-sitter-json@0.24.8, tree-sitter@0.21.1, tree-sitter@0.22.4, │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4933186Z │ unrs-resolver@1.11.1. │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4934052Z │ Run "pnpm approve-builds" to pick which dependencies should be allowed │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4934962Z │ to run scripts. │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4935501Z │ │
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.4935982Z ╰──────────────────────────────────────────────────────────────────────────────╯
    Deploy Admin Install root dependencies 2026-04-17T21:50:16.5128324Z Done in 17.8s using pnpm v10.33.0
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5422878Z ##[group]Run actions/cache@v4
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423103Z with:
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423278Z path: .nx/cache
    Deploy Admin Setup Nx and Vercel Caching apps/admin/.next/cache
    Deploy Admin Setup Nx and Vercel Caching
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423605Z key: Linux-build-cache-ceb93242f18483b5d1227a8efb9f8bd607a42fd4
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423904Z restore-keys: Linux-build-cache-
    Deploy Admin Setup Nx and Vercel Caching
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424129Z enableCrossOsArchive: false
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424333Z fail-on-cache-miss: false
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424521Z lookup-only: false
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424691Z save-always: false
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424849Z env:
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5425047Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5425294Z ##[endgroup]
    Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.8307544Z Cache not found for input keys: Linux-build-cache-ceb93242f18483b5d1227a8efb9f8bd607a42fd4, Linux-build-cache-
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8371819Z ##[group]Run if [ -z "***" ]; then
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372147Z if [ -z "***" ]; then
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372394Z  echo "::error::Missing VERCEL_TOKEN secret"
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372638Z  exit 1
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372803Z fi
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372995Z echo "VERCEL_TOKEN is configured"
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8391384Z shell: /usr/bin/bash -e {0}
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8391582Z env:
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8391791Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8392043Z ##[endgroup]
    Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8429593Z VERCEL_TOKEN is configured
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8449104Z ##[group]Run curl -fsS -X PATCH \
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8449352Z curl -fsS -X PATCH \
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8449788Z  -H "Authorization: ***" \
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8450045Z  -H "Content-Type: application/json" \
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8450619Z  -d '{"buildCommand":"pnpm --filter admin build","outputDirectory":"apps/admin/.next"}' \
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8451100Z  "https://api.vercel.com/v9/projects/***"
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467187Z shell: /usr/bin/bash -e {0}
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467395Z env:
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467597Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467847Z ##[endgroup]
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2953978Z ##[group]Run npx vercel pull --yes --environment=production --token=***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2954473Z npx vercel pull --yes --environment=production --token=***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2954844Z npx vercel build --prod --token=***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2973268Z shell: /usr/bin/bash -e {0}
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2973479Z env:
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2973693Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974007Z VERCEL_ORG_ID: ***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974249Z VERCEL_PROJECT_ID: ***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974447Z NEXT_PUBLIC_APP_ENV: production
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974660Z NEXT_TELEMETRY_DISABLED: 1
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974958Z NEXT_PUBLIC_SUPABASE_URL: ***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2976020Z NEXT_PUBLIC_SUPABASE_ANON_KEY: ***
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2976253Z ##[endgroup]
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3366033Z > NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3366846Z > This information is used to shape the CLI roadmap and prioritize features.
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3367614Z > You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3368131Z > https://vercel.com/docs/cli/about-telemetry
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.5262325Z Retrieving project…
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.6645830Z > Downloading `production` Environment Variables for foushwares-projects/elzatona-admin
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.6646486Z Downloading
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7739457Z Created .vercel/.env.production.local file [109ms]
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7739852Z
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7740030Z > Downloading project settings
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7744116Z Downloaded project settings to ~/work/elzatona_web/elzatona_web/.vercel/project.json [0ms]
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.5798815Z WARNING! Build not running on Vercel. System environment variables will not be available.
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.8059668Z Warning: Detected "engines": { "node": ">=20.0.0" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: https://vercel.link/node-version
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.8843823Z Detected `pnpm-lock.yaml` version 9 generated by pnpm@10.x with package.json#packageManager pnpm@10.33.0
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.8876415Z Running "install" command: `npm install`...
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.6678258Z npm error Cannot read properties of null (reading 'package')
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.6679315Z npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2026-04-17T21_50_19_938Z-debug-0.log
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.6877573Z Error: Command "npm install" exited with 1
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9340059Z ╭──────────────────────────────────────────────────────────────────────────────╮
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9341461Z │ │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9342235Z │ Update available! v50.44.0 ≫ v51.6.1 │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9343060Z │ Changelog: https://github.com/vercel/vercel/releases/tag/vercel%4051.6.1 │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9343973Z │ Run `pnpm i vercel@latest` to update. │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9344724Z │ │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9345480Z │ The latest update may fix any errors that occurred. │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9346191Z │ │
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9346921Z ╰──────────────────────────────────────────────────────────────────────────────╯
    Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9570448Z ##[error]Process completed with exit code 1.
    Deploy Admin Post Install pnpm 2026-04-17T21:50:28.9829362Z Post job cleanup.
    Deploy Admin Post Install pnpm 2026-04-17T21:50:29.1060031Z Pruning is unnecessary.
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.1255154Z Post job cleanup.
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2100861Z [command]/usr/bin/git version
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2129638Z git version 2.53.0
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2169350Z Temporarily overriding HOME='/home/runner/work/_temp/f09a2c0b-7fea-43c6-acc3-2541c6adc2e6' before making global git config changes
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2170822Z Adding repository directory to the temporary git global config as a safe directory
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2174495Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/elzatona_web/elzatona_web
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2367036Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2400726Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2667384Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2690740Z http.https://github.com/.extraheader
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2702196Z [command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2949472Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.3161130Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
    Deploy Admin Post Checkout repository 2026-04-17T21:50:29.3186091Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
    Deploy Admin Complete job 2026-04-17T21:50:29.3532055Z Cleaning up orphan processes
    Deploy Admin Complete job 2026-04-17T21:50:29.3865757Z Terminate orphan process: pid (2261) (node)
    Deploy Admin Complete job 2026-04-17T21:50:29.3892557Z Terminate orphan process: pid (2324) (node)
    Deploy Admin Complete job 2026-04-17T21:50:29.3901920Z ##[warning]Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/cache@v4, actions/checkout@v4, actions/setup-node@v4, pnpm/action-setup@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟠 Major

    Remove raw CI run logs from the repository

    This file is a transient execution artifact, not source/config. Keeping full job logs in git adds high-churn noise and leaks unnecessary operational metadata (runner internals, workspace paths, dependency inventory, timestamps). Keep a short sanitized excerpt in docs instead, and attach full logs to PR/Actions artifacts.

    🧰 Tools
    🪛 LanguageTool

    [uncategorized] ~85-~85: The official name of this software platform is spelled with a capital “H”.
    Context: ...name-only --get-regexp http.https://github.com/.extraheader Deploy Admin Checko...

    (GITHUB)


    [uncategorized] ~86-~86: The official name of this software platform is spelled with a capital “H”.
    Context: ...ame-only --get-regexp 'http.https://github.com/.extraheader' && git config --lo...

    (GITHUB)


    [style] ~397-~397: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
    Context: ...:50:29.1060031Z Pruning is unnecessary. Deploy Admin Post Checkout repository 2026-04...

    (ENGLISH_WORD_REPEAT_BEGINNING_RULE)


    [style] ~398-~398: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
    Context: ...-17T21:50:29.1255154Z Post job cleanup. Deploy Admin Post Checkout repository 2026-04-...

    (ENGLISH_WORD_REPEAT_BEGINNING_RULE)


    [uncategorized] ~406-~406: The official name of this software platform is spelled with a capital “H”.
    Context: ...name-only --get-regexp http.https://github.com/.extraheader Deploy Admin Post C...

    (GITHUB)


    [uncategorized] ~409-~409: The official name of this software platform is spelled with a capital “H”.
    Context: ...ame-only --get-regexp 'http.https://github.com/.extraheader' && git config --lo...

    (GITHUB)

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@failed_job_log.txt` around lines 1 - 415, Remove the committed CI run log
    file (failed_job_log.txt) from the repository, commit the deletion (git rm
    failed_job_log.txt; git commit -m "chore: remove CI run log"), add a rule to
    .gitignore to prevent future checkins (e.g. failed_job_log.txt or a logs/
    pattern), include a short sanitized excerpt in docs if needed, and if the log is
    already in main history scrub it from the repo using a history-rewrite tool (git
    filter-repo or BFG) and force-push the cleaned branch; attach full raw logs to
    the GitHub Actions artifacts instead of committing them.
    

    Comment thread failed_job_log.txt
    Deploy Admin Complete job 2026-04-17T21:50:29.3532055Z Cleaning up orphan processes
    Deploy Admin Complete job 2026-04-17T21:50:29.3865757Z Terminate orphan process: pid (2261) (node)
    Deploy Admin Complete job 2026-04-17T21:50:29.3892557Z Terminate orphan process: pid (2324) (node)
    Deploy Admin Complete job 2026-04-17T21:50:29.3901920Z ##[warning]Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/cache@v4, actions/checkout@v4, actions/setup-node@v4, pnpm/action-setup@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🔴 Critical

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Inspect action pins and Node24 opt-in readiness in workflows.
    set -euo pipefail
    
    echo "== Actions currently pinned in workflows =="
    rg -n 'uses:\s*actions/(checkout|setup-node|cache)@|uses:\s*pnpm/action-setup@|FORCE_JAVASCRIPT_ACTIONS_TO_NODE24|ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION' .github/workflows
    
    echo
    echo "== List all action references (for upgrade audit) =="
    rg -n 'uses:\s*[[:alnum:]_.-]+/[[:alnum:]_.-]+@' .github/workflows

    Repository: FoushWare/elzatona_web

    Length of output: 8644


    🌐 Web query:

    actions/checkout v5 Node.js 24 support GitHub

    💡 Result:

    Yes, actions/checkout v5 supports Node.js 24. It was updated to use the node24 runtime as part of its v5.0.0 release in August 2025. This requires a minimum GitHub Actions runner version of v2.327.1. Self-hosted runners must be updated to this version or newer, and may need Node.js 24 pre-installed in some cases. The action's action.yml specifies 'using: node24', and the README confirms "Checkout v5 now supports Node.js 24". Examples in GitHub docs use actions/checkout@v5 with Node.js setups including 24.x.

    Citations:


    🌐 Web query:

    actions/setup-node v5 Node.js 24 support

    💡 Result:

    Yes, actions/setup-node v5 fully supports Node.js 24. It can install Node.js 24 (e.g., node-version: '24'), as shown in official documentation and examples. Additionally, v5 itself runs on Node.js 24 (upgraded from Node 20), requiring GitHub Actions runners version v2.327.1 or later for compatibility.

    Citations:


    🌐 Web query:

    pnpm/action-setup v5 Node.js 24 support

    💡 Result:

    Yes, pnpm/action-setup v5 supports Node.js 24. It runs on Node.js 24 runtime, as updated in v5.0.0 (released March 17, 2026). The action.yml specifies "using: node24", changing from "node20" in v4. This ensures compatibility with GitHub Actions runners using Node.js 24, which became standard around March 2026. Note that this action installs pnpm but does not set up Node.js itself; pair it with actions/setup-node for Node.js 24.

    Citations:


    Upgrade all deprecated actions to v5 before June 2, 2026

    All GitHub Actions pinned to v4 (actions/checkout, actions/setup-node, actions/cache, pnpm/action-setup) are running on deprecated Node.js 20. GitHub will force Node.js 24 as the default on June 2, 2026—just 6 weeks away. Node.js 20 runners are removed entirely on September 16, 2026.

    Bump affected workflows to v5:

    • actions/checkout@v5
    • actions/setup-node@v5
    • actions/cache@v5
    • pnpm/action-setup@v5

    All v5 releases are Node.js 24-ready and already published. No functional changes required; this is a straightforward version bump across .github/workflows/. Prioritize this to avoid CI breakage when the default flips.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@failed_job_log.txt` at line 415, Update all GitHub workflow files that
    reference actions pinned to v4 by replacing actions/checkout@v4,
    actions/setup-node@v4, actions/cache@v4, and pnpm/action-setup@v4 with their v5
    releases (actions/checkout@v5, actions/setup-node@v5, actions/cache@v5,
    pnpm/action-setup@v5); search for those exact strings in your
    .github/workflows/*.yml files and perform the version bumps, then run CI to
    verify; if you need a temporary opt-in instead of bumping, set the
    FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true runner/workflow environment variable,
    but prefer updating the action versions.
    

    Comment on lines +26 to +27
    <div className="absolute top-10 left-10 w-72 h-72 bg-indigo-300 dark:bg-indigo-600 rounded-full blur-[120px] animate-pulse" />
    <div className="absolute bottom-10 right-10 w-72 h-72 bg-purple-300 dark:bg-purple-600 rounded-full blur-[120px] animate-pulse delay-700" />
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Description: Check Tailwind version/config references for motion-safe/motion-reduce support.
    
    rg -n -C2 '"tailwindcss"\s*:' --iglob 'package.json'
    rg -n -C3 'motionSafe|motionReduce|motion-safe|motion-reduce|variants' --iglob 'tailwind*.{js,ts,cjs,mjs}'

    Repository: FoushWare/elzatona_web

    Length of output: 542


    🏁 Script executed:

    fd -t f 'HeroSection.tsx' --full-path

    Repository: FoushWare/elzatona_web

    Length of output: 121


    🏁 Script executed:

    #!/bin/bash
    # Find and check Tailwind config files for motion variant configuration
    fd -t f 'tailwind.config' -e js -e ts -e cjs -e mjs

    Repository: FoushWare/elzatona_web

    Length of output: 127


    🏁 Script executed:

    #!/bin/bash
    # Check if HeroSection component exists and read relevant sections
    if [ -f "libs/common-ui/src/components/molecules/HeroSection.tsx" ]; then
      head -n 100 "libs/common-ui/src/components/molecules/HeroSection.tsx" | cat -n
    fi

    Repository: FoushWare/elzatona_web

    Length of output: 4325


    🏁 Script executed:

    cat -n apps/admin/tailwind.config.ts

    Repository: FoushWare/elzatona_web

    Length of output: 4833


    🏁 Script executed:

    cat -n apps/website/tailwind.config.ts

    Repository: FoushWare/elzatona_web

    Length of output: 9668


    Add motion-safe/motion-reduce variants and aria-hidden to decorative blobs.

    Lines 26–27 use animate-pulse without respecting prefers-reduced-motion. Users who prefer reduced motion for accessibility reasons (vestibular disorders, etc.) still see the continuous animation. Additionally, the decorative container should be marked aria-hidden since it serves no semantic purpose.

    Proposed fix
    -      <div className="absolute top-0 left-1/2 -translate-x-1/2 w-full max-w-4xl h-full -z-10 opacity-30 pointer-events-none">
    -        <div className="absolute top-10 left-10 w-72 h-72 bg-indigo-300 dark:bg-indigo-600 rounded-full blur-[120px] animate-pulse" />
    -        <div className="absolute bottom-10 right-10 w-72 h-72 bg-purple-300 dark:bg-purple-600 rounded-full blur-[120px] animate-pulse delay-700" />
    +      <div
    +        aria-hidden="true"
    +        className="absolute top-0 left-1/2 -translate-x-1/2 w-full max-w-4xl h-full -z-10 opacity-30 pointer-events-none"
    +      >
    +        <div className="absolute top-10 left-10 w-72 h-72 bg-indigo-300 dark:bg-indigo-600 rounded-full blur-[120px] motion-safe:animate-pulse motion-reduce:animate-none" />
    +        <div className="absolute bottom-10 right-10 w-72 h-72 bg-purple-300 dark:bg-purple-600 rounded-full blur-[120px] motion-safe:animate-pulse motion-reduce:animate-none delay-700" />

    Tailwind 3.3+ includes built-in motion-safe and motion-reduce variants, so no config changes needed.

    📝 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
    <div className="absolute top-10 left-10 w-72 h-72 bg-indigo-300 dark:bg-indigo-600 rounded-full blur-[120px] animate-pulse" />
    <div className="absolute bottom-10 right-10 w-72 h-72 bg-purple-300 dark:bg-purple-600 rounded-full blur-[120px] animate-pulse delay-700" />
    <div
    aria-hidden="true"
    className="absolute top-0 left-1/2 -translate-x-1/2 w-full max-w-4xl h-full -z-10 opacity-30 pointer-events-none"
    >
    <div className="absolute top-10 left-10 w-72 h-72 bg-indigo-300 dark:bg-indigo-600 rounded-full blur-[120px] motion-safe:animate-pulse motion-reduce:animate-none" />
    <div className="absolute bottom-10 right-10 w-72 h-72 bg-purple-300 dark:bg-purple-600 rounded-full blur-[120px] motion-safe:animate-pulse motion-reduce:animate-none delay-700" />
    </div>
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/common-ui/src/components/molecules/HeroSection.tsx` around lines 26 -
    27, In HeroSection (the two decorative blob <div>s that currently include
    "animate-pulse"), make the animation respect user motion preferences by
    replacing "animate-pulse" with "motion-safe:animate-pulse
    motion-reduce:animate-none" and mark each decorative element as non-semantic by
    adding aria-hidden="true"; locate the elements by their existing class strings
    (e.g., "absolute top-10 left-10 w-72 h-72 bg-indigo-300..." and "absolute
    bottom-10 right-10 w-72 h-72 bg-purple-300...") and apply these changes to both
    blobs.
    

    …rage)
    
    - Centralize studyPlans data into libs/utilities
    - Implement unit tests for guided learning components and utilities
    - Enhance studyPlan data coverage to 100%
    - Fix lucide-react mock for Trophy icon
    Copy link
    Copy Markdown

    @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

    Caution

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

    ⚠️ Outside diff range comments (2)
    libs/utilities/src/lib/test-utils/mocks/lucide-react.tsx (1)

    65-114: ⚠️ Potential issue | 🟡 Minor

    Trophy missing from lucideMock default export 🏆

    The named export Trophy was added on line 23, but it was not added to the lucideMock object. Any test reaching for lucideMock.Trophy (or consuming the default import) will get undefined, while import { Trophy } works fine. This also contradicts the PR summary which states the default export was updated to include it.

    🔧 Proposed fix
       Award,
    +  Trophy,
       ExternalLink,
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/utilities/src/lib/test-utils/mocks/lucide-react.tsx` around lines 65 -
    114, The default export object lucideMock is missing the Trophy entry even
    though Trophy was added as a named export; update the lucideMock object to
    include Trophy (e.g., add "Trophy," alongside the other icon entries) so
    lucideMock.Trophy is defined and the default export matches the named exports.
    
    libs/utilities/src/lib/studyPlans.ts (1)

    171-287: ⚠️ Potential issue | 🟠 Major

    Keep milestone task estimates aligned with the advertised plan duration.

    The milestone task totals are much lower than estimatedTotalTime for the longer plans, especially three-months-comprehensive at 9h of tasks vs 36h advertised. Either add the missing task coverage or derive/display a separate “guided tasks estimate” so users are not promised a plan length the milestones do not represent.

    Also applies to: 412-522, 648-699

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/utilities/src/lib/studyPlans.ts` around lines 171 - 287, The plan's
    milestones (created via createMilestones and the milestones arrays) do not sum
    to the advertised estimatedTotalTime; update either the milestone entries or the
    metadata so they align: either increase/extend milestone estimatedTime values or
    add additional milestone objects to reach the advertised estimatedTotalTime for
    the affected plans (e.g., the three-months-comprehensive entry), or else compute
    and expose a derived "guidedTasksEstimate" from the sum of milestone. Ensure
    changes touch createMilestones input arrays and the estimatedTotalTime field
    consistently so that the sum of all milestone estimatedTime values equals (or is
    explicitly reflected by) the plan's estimatedTotalTime.
    
    ♻️ Duplicate comments (2)
    apps/website/src/app/features/guided-learning/utils/plan-helpers.ts (2)

    71-82: ⚠️ Potential issue | 🟡 Minor

    Replace any[] with unknown[] for milestone range input.

    This helper only reads .length, so unknown[] preserves strict typing without weakening the API.

    Proposed fix
    -export function getMilestoneRange(plans: { milestones?: any[] }[]): string {
    +export function getMilestoneRange(plans: { milestones?: unknown[] }[]): string {

    As per coding guidelines, No any types in TypeScript without explicit eslint-disable and justification comment.

    #!/bin/bash
    # Verify remaining explicit any usage in guided-learning helpers.
    rg -n -C2 '\bany\b' --iglob '*plan-helpers.ts'
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts` around
    lines 71 - 82, The function signature for getMilestoneRange uses a loose any[]
    for milestones; update the parameter type to use unknown[] (e.g., plans: {
    milestones?: unknown[] }[]) since the function only reads .length, preserving
    stricter typing without changing behavior; update the declaration of
    getMilestoneRange accordingly and ensure no other uses in this file require
    widening back to any.
    

    47-69: ⚠️ Potential issue | 🟡 Minor

    Separate question counts from hour estimates before rendering.

    getQuestionsRange now returns totalQuestions when present, otherwise duration.totalHours, but the returned value is still exposed as questionsRange by useGuidedLearningPlans. That can silently render hours as “questions”; return a discriminated result or split this into getQuestionsRange and getHoursRange.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts` around
    lines 47 - 69, getQuestionsRange currently returns either question counts or
    hour estimates and is surfaced as questionsRange by useGuidedLearningPlans,
    causing hours to be displayed as "questions"; change the API so the values are
    not conflated by either (A) split into two clear helpers getQuestionsRange and
    getHoursRange and have useGuidedLearningPlans expose both questionsRange and
    hoursRange, or (B) make getQuestionsRange return a discriminated union like {
    type: "questions" | "hours"; min: number; max: number } and update
    useGuidedLearningPlans to pass through the discriminant so callers can render
    appropriately; update references to getQuestionsRange and useGuidedLearningPlans
    to consume the new shape or the separate helper names.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Inline comments:
    In
    `@apps/website/src/app/features/guided-learning/components/__tests__/ActivePlanView.test.tsx`:
    - Around line 111-123: The component currently falls back to rendering "1/0"
    when milestones array is empty; change the rendering logic in ActivePlanView so
    that when milestones.length === 0 it displays "0/0" (or hides the counter) by
    computing displayIndex = 0 and total = milestones.length before formatting, and
    update the test expectation in ActivePlanView.test.tsx to assert "0/0" instead
    of "1/0"; adjust the code path that derives the counter (the logic using props
    milestones and currentMilestoneId inside ActivePlanView) and update the test
    assertion accordingly.
    
    In `@libs/common-ui/src/common/__tests__/LearningModeSwitcher.test.tsx`:
    - Around line 61-67: The test "applies scrolled styles when isScrolled is true"
    in LearningModeSwitcher.test.tsx destructures an unused container from render;
    remove the unused destructure by replacing "const { container } =
    render(<LearningModeSwitcher isScrolled={true} />);" with a simple
    "render(<LearningModeSwitcher isScrolled={true} />);" (keeping the existing
    screen.getByRole assertions intact for the group role) and run the tests to
    verify.
    
    In `@tests/config/playwright.config.ts`:
    - Around line 2-10: Update the Playwright config dotenv usage to fail loudly and
    perform CI-aware validation: stop calling dotenv.config() without checks;
    instead attempt dotenv.config({ path: path.resolve(__dirname,
    "../../.env.test.local") }) and if it returns an error/failed parse in local
    mode (when process.env.CI and process.env.GITHUB_ACTIONS are not set) throw an
    error so tests don’t silently load the wrong file, and in CI mode (when
    process.env.CI or process.env.GITHUB_ACTIONS is set) validate that required test
    env vars (list the keys your tests need) exist in process.env before exporting
    the config, throwing a clear error if any are missing; ensure you reference
    dotenv.config() return values and use process.env.CI /
    process.env.GITHUB_ACTIONS to branch behavior so there is no silent fallback to
    the default .env.
    
    ---
    
    Outside diff comments:
    In `@libs/utilities/src/lib/studyPlans.ts`:
    - Around line 171-287: The plan's milestones (created via createMilestones and
    the milestones arrays) do not sum to the advertised estimatedTotalTime; update
    either the milestone entries or the metadata so they align: either
    increase/extend milestone estimatedTime values or add additional milestone
    objects to reach the advertised estimatedTotalTime for the affected plans (e.g.,
    the three-months-comprehensive entry), or else compute and expose a derived
    "guidedTasksEstimate" from the sum of milestone. Ensure changes touch
    createMilestones input arrays and the estimatedTotalTime field consistently so
    that the sum of all milestone estimatedTime values equals (or is explicitly
    reflected by) the plan's estimatedTotalTime.
    
    In `@libs/utilities/src/lib/test-utils/mocks/lucide-react.tsx`:
    - Around line 65-114: The default export object lucideMock is missing the Trophy
    entry even though Trophy was added as a named export; update the lucideMock
    object to include Trophy (e.g., add "Trophy," alongside the other icon entries)
    so lucideMock.Trophy is defined and the default export matches the named
    exports.
    
    ---
    
    Duplicate comments:
    In `@apps/website/src/app/features/guided-learning/utils/plan-helpers.ts`:
    - Around line 71-82: The function signature for getMilestoneRange uses a loose
    any[] for milestones; update the parameter type to use unknown[] (e.g., plans: {
    milestones?: unknown[] }[]) since the function only reads .length, preserving
    stricter typing without changing behavior; update the declaration of
    getMilestoneRange accordingly and ensure no other uses in this file require
    widening back to any.
    - Around line 47-69: getQuestionsRange currently returns either question counts
    or hour estimates and is surfaced as questionsRange by useGuidedLearningPlans,
    causing hours to be displayed as "questions"; change the API so the values are
    not conflated by either (A) split into two clear helpers getQuestionsRange and
    getHoursRange and have useGuidedLearningPlans expose both questionsRange and
    hoursRange, or (B) make getQuestionsRange return a discriminated union like {
    type: "questions" | "hours"; min: number; max: number } and update
    useGuidedLearningPlans to pass through the discriminant so callers can render
    appropriately; update references to getQuestionsRange and useGuidedLearningPlans
    to consume the new shape or the separate helper names.
    
    🪄 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: f87d730f-424a-4b16-821e-4143edfcab37

    📥 Commits

    Reviewing files that changed from the base of the PR and between 30c087f and f6e9a16.

    ⛔ Files ignored due to path filters (1)
    • libs/common-ui/src/common/__snapshots__/ProgressTracker.test.tsx.snap is excluded by !**/*.snap
    📒 Files selected for processing (14)
    • apps/website/src/app/features/guided-learning/components/__tests__/ActivePlanView.test.tsx
    • apps/website/src/app/features/guided-learning/components/__tests__/LearningPlanCard.test.tsx
    • apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts
    • apps/website/src/app/features/guided-learning/utils/plan-helpers.ts
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • apps/website/src/app/lib/studyPlans.ts
    • libs/common-ui/src/common/LearningModeSwitcher.tsx
    • libs/common-ui/src/common/__tests__/LearningModeSwitcher.test.tsx
    • libs/common-ui/src/components/molecules/HeroSection.tsx
    • libs/utilities/src/index.ts
    • libs/utilities/src/lib/studyPlans.spec.ts
    • libs/utilities/src/lib/studyPlans.ts
    • libs/utilities/src/lib/test-utils/mocks/lucide-react.tsx
    • tests/config/playwright.config.ts
    💤 Files with no reviewable changes (1)
    • apps/website/src/app/lib/studyPlans.ts
    ✅ Files skipped from review due to trivial changes (2)
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • libs/common-ui/src/common/LearningModeSwitcher.tsx
    🚧 Files skipped from review as they are similar to previous changes (1)
    • libs/common-ui/src/components/molecules/HeroSection.tsx

    Comment on lines +111 to +123
    it("handles zero milestones gracefully", () => {
    render(
    <ActivePlanView
    plan={mockPlan}
    milestones={[]}
    currentMilestoneId={null}
    onResume={onResumeMock}
    onReset={onResetMock}
    />,
    );
    expect(screen.getByText("0%")).toBeInTheDocument();
    expect(screen.getByText("1/0")).toBeInTheDocument(); // index fallback to 0 + 1 / total 0
    });
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Verify how ActivePlanView computes and renders the current milestone counter.
    rg -n -C4 'currentMilestone|milestone.*length|/\\s*\\{?|No milestones|ActivePlanView' --iglob '*ActivePlanView.tsx'

    Repository: FoushWare/elzatona_web

    Length of output: 264


    🏁 Script executed:

    #!/bin/bash
    # Find the ActivePlanView component file
    fd -type f -name 'ActivePlanView.tsx' | head -5

    Repository: FoushWare/elzatona_web

    Length of output: 236


    🏁 Script executed:

    #!/bin/bash
    # Search for milestone counter rendering in the codebase
    rg -n "currentMilestone\|milestones\[" --type ts --type tsx | grep -i activePlan | head -20

    Repository: FoushWare/elzatona_web

    Length of output: 93


    🏁 Script executed:

    #!/bin/bash
    # Find ActivePlanView component
    find . -name 'ActivePlanView.tsx' -type f 2>/dev/null | grep -v node_modules

    Repository: FoushWare/elzatona_web

    Length of output: 143


    🏁 Script executed:

    #!/bin/bash
    # Read the ActivePlanView component (not the test)
    find . -name 'ActivePlanView.tsx' -type f 2>/dev/null | grep -v node_modules | grep -v test | head -1 | xargs cat -n

    Repository: FoushWare/elzatona_web

    Length of output: 6836


    The 1/0 milestone counter is confusing with zero milestones—update the component or test expectation.

    Lines 101 in the component and line 122 in the test lock in 1/0 as the fallback for an empty milestone list. This is not a graceful user-facing value. When milestones = [], the counter becomes mathematically nonsensical. Update the component to show 0/0, hide the counter entirely, or display a message like "No milestones" instead.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@apps/website/src/app/features/guided-learning/components/__tests__/ActivePlanView.test.tsx`
    around lines 111 - 123, The component currently falls back to rendering "1/0"
    when milestones array is empty; change the rendering logic in ActivePlanView so
    that when milestones.length === 0 it displays "0/0" (or hides the counter) by
    computing displayIndex = 0 and total = milestones.length before formatting, and
    update the test expectation in ActivePlanView.test.tsx to assert "0/0" instead
    of "1/0"; adjust the code path that derives the counter (the logic using props
    milestones and currentMilestoneId inside ActivePlanView) and update the test
    assertion accordingly.
    

    Comment thread libs/common-ui/src/common/__tests__/LearningModeSwitcher.test.tsx
    Comment on lines +2 to +10
    import path from "path";
    import dotenv from "dotenv";

    /**
    * Read environment variables from file.
    * https://github.com/motdotla/dotenv
    */
    dotenv.config({ path: path.resolve(__dirname, "../../.env.test.local") });
    dotenv.config();
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟠 Major

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    set -euo pipefail
    
    # Description: Inspect Playwright env-loading configs and validation helper.
    # Expected: root config should fail fast like the website config and invoke requireTestEnvironment().
    
    sed -n '1,90p' tests/config/playwright.config.ts
    
    printf '\n--- matching validation helpers ---\n'
    fd -a 'validate-test-env\.ts$' . | xargs -r sed -n '1,140p'
    
    printf '\n--- Playwright configs using requireTestEnvironment ---\n'
    rg -n -C3 'requireTestEnvironment|dotenv\.config|config\(\{' --glob 'playwright.config.ts'

    Repository: FoushWare/elzatona_web

    Length of output: 5191


    🏁 Script executed:

    # Check if validate-test-env.ts exists in root tests directory
    find . -path '*/tests/e2e/validate-test-env.ts' -o -path '*/tests/validate-test-env.ts' | head -20
    
    # Check the full structure of root tests directory
    echo "=== Root tests directory structure ===" 
    ls -la tests/ 2>/dev/null || echo "tests/ not found at root"
    ls -la tests/e2e/ 2>/dev/null || echo "tests/e2e/ not found at root"
    
    # Check if there's a validate-test-env anywhere at the root level
    find tests -name 'validate-test-env*' 2>/dev/null || echo "No validate-test-env found"
    
    # Verify what directories exist at root
    echo "=== Root level directories ===" 
    ls -d */ 2>/dev/null | head -20

    Repository: FoushWare/elzatona_web

    Length of output: 800


    🏁 Script executed:

    # Check if root playwright config is actually used or if apps have their own
    echo "=== Apps with Playwright configs ===" 
    find apps -name 'playwright.config.ts' | sort
    
    # Check if root playwright config is referenced in package.json or scripts
    echo -e "\n=== Root package.json playwright references ===" 
    cat package.json 2>/dev/null | grep -A5 -B5 'playwright\|e2e' || echo "No e2e script in root"
    
    # Check app-level package.json scripts
    echo -e "\n=== App-level e2e scripts ===" 
    for app in apps/*/; do
      if [ -f "$app/package.json" ]; then
        echo "=== $(basename $app) ===" 
        grep -A2 '"e2e"' "$app/package.json" || true
      fi
    done

    Repository: FoushWare/elzatona_web

    Length of output: 11150


    Add error handling and CI-aware env validation to root Playwright config.

    Lines 9–10 call dotenv.config() without checking for errors. If .env.test.local is missing or invalid, the second call on line 10 silently falls back to the default .env, which may contain non-test credentials. This violates graceful error handling and can cause E2E tests to run against the wrong environment.

    Add CI detection and explicit error checks similar to apps/website/tests/config/playwright.config.ts. At minimum:

    • Detect CI/test environment using process.env.CI, process.env.GITHUB_ACTIONS, or test-specific env vars
    • In local mode, require .env.test.local to exist and throw on load failure
    • In CI mode, validate required test variables are present before exporting the config
    • Avoid silent fallback to default .env
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@tests/config/playwright.config.ts` around lines 2 - 10, Update the Playwright
    config dotenv usage to fail loudly and perform CI-aware validation: stop calling
    dotenv.config() without checks; instead attempt dotenv.config({ path:
    path.resolve(__dirname, "../../.env.test.local") }) and if it returns an
    error/failed parse in local mode (when process.env.CI and
    process.env.GITHUB_ACTIONS are not set) throw an error so tests don’t silently
    load the wrong file, and in CI mode (when process.env.CI or
    process.env.GITHUB_ACTIONS is set) validate that required test env vars (list
    the keys your tests need) exist in process.env before exporting the config,
    throwing a clear error if any are missing; ensure you reference dotenv.config()
    return values and use process.env.CI / process.env.GITHUB_ACTIONS to branch
    behavior so there is no silent fallback to the default .env.
    

    - Fix 11 code smells (accessibility, complex logic, unused imports)
    - Add full test coverage for Guided Learning plans API route
    - Update component tests to match refactored logic
    Copy link
    Copy Markdown

    @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: 4

    🧹 Nitpick comments (3)
    libs/common-ui/src/common/LearningModeSwitcher.tsx (1)

    20-40: Minor: isSelfDirected = !isGuided misrepresents the null state, and the two color helpers are near-duplicates.

    UserType is "guided" | "self-directed" | null (see libs/contexts/src/lib/UserTypeContextSafe.tsx). When userType is null (brief window before the provider hydrates from localStorage), isGuided is false, so the Free Style button will render aria-pressed={true} and its pulse/pill — which is misleading. Consider const isSelfDirected = userType === "self-directed"; and let neither be "pressed" until hydration resolves.

    While you're here, the two getXxxTextColor helpers are identical except for the active flag — easy DRY win:

    ♻️ Suggested refactor
    -  const isGuided = userType === "guided";
    -  const isSelfDirected = !isGuided;
    -
    -  // Determine text colors based on state to avoid nested ternaries in JSX
    -  const getGuidedTextColor = () => {
    -    if (isGuided) {
    -      return isScrolled ? "text-white" : "text-indigo-900 dark:text-white";
    -    }
    -    return isScrolled
    -      ? "text-gray-500 hover:text-gray-700 dark:text-gray-400"
    -      : "text-white/70 hover:text-white";
    -  };
    -
    -  const getSelfDirectedTextColor = () => {
    -    if (isSelfDirected) {
    -      return isScrolled ? "text-white" : "text-indigo-900 dark:text-white";
    -    }
    -    return isScrolled
    -      ? "text-gray-500 hover:text-gray-700 dark:text-gray-400"
    -      : "text-white/70 hover:text-white";
    -  };
    +  const isGuided = userType === "guided";
    +  const isSelfDirected = userType === "self-directed";
    +
    +  // Shared text-color resolver (active vs idle, scrolled vs transparent nav)
    +  const getTextColor = (active: boolean) => {
    +    if (active) {
    +      return isScrolled ? "text-white" : "text-indigo-900 dark:text-white";
    +    }
    +    return isScrolled
    +      ? "text-gray-500 hover:text-gray-700 dark:text-gray-400"
    +      : "text-white/70 hover:text-white";
    +  };

    Then use getTextColor(isGuided) / getTextColor(isSelfDirected) in the JSX.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/common-ui/src/common/LearningModeSwitcher.tsx` around lines 20 - 40, The
    current logic sets isSelfDirected = !isGuided which treats userType === null as
    self-directed and causes incorrect aria-pressed state; change isSelfDirected to
    userType === "self-directed" (keep isGuided as userType === "guided") so neither
    button is active during hydration, and collapse the two near-duplicate helpers
    getGuidedTextColor and getSelfDirectedTextColor into a single DRY function
    (e.g., getTextColor(activeFlag)) that returns the same color strings based on
    the passed active flag and isScrolled, then call getTextColor(isGuided) /
    getTextColor(isSelfDirected) in the JSX.
    
    apps/website/src/app/features/guided-learning/types/guided-learning.types.ts (1)

    1-1: Use a type-only import for StudyPlan.

    StudyPlan is only used in a type position (interface inheritance), so import type avoids unnecessary runtime import emission and signals the intent clearly.

    ♻️ Proposed change
    -import { StudyPlan } from "@elzatona/types";
    +import type { StudyPlan } from "@elzatona/types";
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/types/guided-learning.types.ts`
    at line 1, Replace the runtime import with a type-only import: change the
    existing import of StudyPlan to a type-only import (i.e., use "import type {
    StudyPlan } from '...';") in the guided-learning types file so StudyPlan is only
    emitted for TypeScript type-checking; update the existing import statement that
    currently reads "import { StudyPlan } ..." to use "import type" referencing the
    StudyPlan symbol.
    
    apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts (1)

    80-93: Cover the id de-duplication contract in the merge test.

    The route filters DB rows that duplicate hardcoded plan IDs, but this test only covers unique DB rows.

    Proposed test strengthening
    -    it("merges hardcoded plans with DB plans", async () => {
    +    it("merges hardcoded plans with DB plans and deduplicates by id", async () => {
           mockSupabase.order.mockResolvedValue({
    -        data: [{ id: "db1", title: "DB Plan 1" }],
    +        data: [
    +          { id: "h1", title: "Duplicate DB Plan" },
    +          { id: "db1", title: "DB Plan 1" },
    +        ],
             error: null,
           });
     
    @@
    -      expect(json.data.length).toBe(2);
    +      expect(json.data).toHaveLength(2);
    +      expect(json.data).toContainEqual({ id: "h1", title: "Hardcoded Plan 1" });
    +      expect(json.data).toContainEqual({ id: "db1", title: "DB Plan 1" });
    +      expect(
    +        json.data.filter((plan: { id: string }) => plan.id === "h1"),
    +      ).toHaveLength(1);
         });
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts`
    around lines 80 - 93, The test "merges hardcoded plans with DB plans" should
    assert the route de-duplicates DB rows that share IDs with hardcoded plans:
    update the mocked DB response in the test (mockSupabase.order) to include both a
    unique DB row and a DB row whose id equals one of the hardcoded plan IDs, call
    GET as before, and then assert that the merged json.data contains only one entry
    for that duplicated id (e.g. expect(json.data.filter(p => p.id ===
    '<hardcoded-id>').length).toBe(1)) and that the total length equals the count of
    unique plan IDs after deduplication.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Inline comments:
    In
    `@apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts`:
    - Around line 146-156: The test "deletes a plan successfully" only checks a 200
    and success flag but doesn't assert the deletion was scoped to the requested id;
    update the test that calls DELETE(req) to also verify that only the plan with id
    "delete-me" was removed and other plans remain—e.g., after DELETE(req) assert
    the plan with id "delete-me" cannot be fetched (or is absent in a list) while
    another seeded plan (different id) still exists; reference the DELETE handler
    and the createMockRequest call to locate where to add these assertions.
    - Around line 25-30: The helper createMockRequest currently types body as any
    and uses a truthy check that drops valid falsy JSON bodies; update the signature
    to use body?: unknown and add an explicit return type of NextRequest, then
    change the conditional that sets the request body to check body !== undefined
    (not a truthy check) so values like false, 0, and null are correctly
    JSON.stringified when present; reference the createMockRequest function to make
    these edits.
    
    In `@libs/common-ui/src/common/LearningModeSwitcher.tsx`:
    - Around line 51-54: The wrapper div in LearningModeSwitcher currently uses
    aria-label="Learning Mode Selection" but lacks an explicit role, so screen
    readers may ignore it; update the outer container (the div using
    containerClasses inside the LearningModeSwitcher component) to include
    role="group" (or convert to a proper radiogroup if you prefer mutually-exclusive
    semantics) so the aria-label is announced—modify the JSX for the same div
    element to add role="group" while keeping the existing aria-label and
    classNames.
    - Around line 63-82: The two toggle buttons in LearningModeSwitcher.tsx (the
    elements that call setUserType("guided") and setUserType("self-directed") and
    use isGuided/isSelfDirected, getGuidedTextColor/getSelfDirectedTextColor) lack
    an explicit type and will default to type="submit" inside forms; update both
    button elements to include type="button" to prevent accidental form submission
    (leave existing onClick, aria-pressed and className logic unchanged).
    
    ---
    
    Nitpick comments:
    In
    `@apps/website/src/app/features/guided-learning/types/guided-learning.types.ts`:
    - Line 1: Replace the runtime import with a type-only import: change the
    existing import of StudyPlan to a type-only import (i.e., use "import type {
    StudyPlan } from '...';") in the guided-learning types file so StudyPlan is only
    emitted for TypeScript type-checking; update the existing import statement that
    currently reads "import { StudyPlan } ..." to use "import type" referencing the
    StudyPlan symbol.
    
    In
    `@apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts`:
    - Around line 80-93: The test "merges hardcoded plans with DB plans" should
    assert the route de-duplicates DB rows that share IDs with hardcoded plans:
    update the mocked DB response in the test (mockSupabase.order) to include both a
    unique DB row and a DB row whose id equals one of the hardcoded plan IDs, call
    GET as before, and then assert that the merged json.data contains only one entry
    for that duplicated id (e.g. expect(json.data.filter(p => p.id ===
    '<hardcoded-id>').length).toBe(1)) and that the total length equals the count of
    unique plan IDs after deduplication.
    
    In `@libs/common-ui/src/common/LearningModeSwitcher.tsx`:
    - Around line 20-40: The current logic sets isSelfDirected = !isGuided which
    treats userType === null as self-directed and causes incorrect aria-pressed
    state; change isSelfDirected to userType === "self-directed" (keep isGuided as
    userType === "guided") so neither button is active during hydration, and
    collapse the two near-duplicate helpers getGuidedTextColor and
    getSelfDirectedTextColor into a single DRY function (e.g.,
    getTextColor(activeFlag)) that returns the same color strings based on the
    passed active flag and isScrolled, then call getTextColor(isGuided) /
    getTextColor(isSelfDirected) in the JSX.
    
    🪄 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: fd9c0b4c-4c10-4c15-aa06-165ef8635218

    📥 Commits

    Reviewing files that changed from the base of the PR and between f6e9a16 and 77e183d.

    📒 Files selected for processing (6)
    • apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx
    • apps/website/src/app/features/guided-learning/types/guided-learning.types.ts
    • apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • libs/common-ui/src/common/LearningModeSwitcher.tsx
    • libs/common-ui/src/common/__tests__/LearningModeSwitcher.test.tsx
    🚧 Files skipped from review as they are similar to previous changes (3)
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • apps/website/src/app/features/guided-learning/components/LearningPlanCard.tsx
    • libs/common-ui/src/common/tests/LearningModeSwitcher.test.tsx

    Comment on lines +25 to +30
    const createMockRequest = (url: string, method = "GET", body?: any) => {
    return new NextRequest(url, {
    method,
    body: body ? JSON.stringify(body) : undefined,
    });
    };
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    rg -nP '\bany\b|eslint-disable.*no-explicit-any' apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts

    Repository: FoushWare/elzatona_web

    Length of output: 143


    Replace any with unknown and add explicit return type.

    Line 25 uses body?: any which violates the no-explicit-any rule. Additionally, the truthy check drops valid falsy JSON bodies like false, 0, or null.

    Fix
    -  const createMockRequest = (url: string, method = "GET", body?: any) => {
    +  const createMockRequest = (
    +    url: string,
    +    method = "GET",
    +    body?: unknown,
    +  ): NextRequest => {
         return new NextRequest(url, {
           method,
    -      body: body ? JSON.stringify(body) : undefined,
    +      body: body === undefined ? undefined : JSON.stringify(body),
         });
       };
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts`
    around lines 25 - 30, The helper createMockRequest currently types body as any
    and uses a truthy check that drops valid falsy JSON bodies; update the signature
    to use body?: unknown and add an explicit return type of NextRequest, then
    change the conditional that sets the request body to check body !== undefined
    (not a truthy check) so values like false, 0, and null are correctly
    JSON.stringified when present; reference the createMockRequest function to make
    these edits.
    

    Comment on lines +146 to +156
    it("deletes a plan successfully", async () => {
    const req = createMockRequest(
    "http://localhost/api/guided-learning/plans?id=delete-me",
    "DELETE",
    );
    const response = await DELETE(req);
    const json = await response.json();

    expect(response.status).toBe(200);
    expect(json.success).toBe(true);
    });
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟠 Major

    Assert the delete is scoped to the requested plan ID.

    This is the important safety property for the destructive path; the current test would still pass if the handler accidentally deleted without an id filter.

    Proposed assertion
           expect(response.status).toBe(200);
           expect(json.success).toBe(true);
    +      expect(mockSupabase.delete).toHaveBeenCalledTimes(1);
    +      expect(mockSupabase.eq).toHaveBeenCalledWith("id", "delete-me");
         });
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@apps/website/src/app/lib/network/routes/guided-learning/plans/__tests__/route.test.ts`
    around lines 146 - 156, The test "deletes a plan successfully" only checks a 200
    and success flag but doesn't assert the deletion was scoped to the requested id;
    update the test that calls DELETE(req) to also verify that only the plan with id
    "delete-me" was removed and other plans remain—e.g., after DELETE(req) assert
    the plan with id "delete-me" cannot be fetched (or is absent in a list) while
    another seeded plan (different id) still exists; reference the DELETE handler
    and the createMockRequest call to locate where to add these assertions.
    

    Comment on lines +51 to +54
    <div
    aria-label="Learning Mode Selection"
    className={`relative flex items-center p-1 rounded-xl transition-all duration-300 ${containerClasses}`}
    >
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    aria-label on a plain <div> needs a role to actually be announced.

    Most screen readers ignore aria-label on generic containers (no implicit role). To make "Learning Mode Selection" reach assistive tech, give the wrapper role="group" (or model it as a radiogroup with role="radio" buttons, since this is a mutually-exclusive selection).

    ♿ Suggested tweak
         <div
    +      role="group"
           aria-label="Learning Mode Selection"
           className={`relative flex items-center p-1 rounded-xl transition-all duration-300 ${containerClasses}`}
         >

    As per coding guidelines: "Follow WCAG guidelines for accessibility and implement proper ARIA labels and roles".

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/common-ui/src/common/LearningModeSwitcher.tsx` around lines 51 - 54, The
    wrapper div in LearningModeSwitcher currently uses aria-label="Learning Mode
    Selection" but lacks an explicit role, so screen readers may ignore it; update
    the outer container (the div using containerClasses inside the
    LearningModeSwitcher component) to include role="group" (or convert to a proper
    radiogroup if you prefer mutually-exclusive semantics) so the aria-label is
    announced—modify the JSX for the same div element to add role="group" while
    keeping the existing aria-label and classNames.
    

    Comment on lines +63 to +82
    <button
    onClick={() => setUserType("guided")}
    aria-pressed={isGuided}
    className={`relative z-10 flex items-center justify-center space-x-2 px-4 py-1.5 rounded-lg transition-colors duration-300 ${getGuidedTextColor()}`}
    >
    <Compass size={16} className={isGuided ? "animate-pulse" : ""} />
    <span className="text-sm font-semibold whitespace-nowrap">Guided</span>
    </button>

    {/* Free Style Option */}
    <button
    onClick={() => setUserType("self-directed")}
    aria-pressed={isSelfDirected}
    className={`relative z-10 flex items-center justify-center space-x-2 px-4 py-1.5 rounded-lg transition-colors duration-300 ${getSelfDirectedTextColor()}`}
    >
    <Map size={16} className={isSelfDirected ? "animate-pulse" : ""} />
    <span className="text-sm font-semibold whitespace-nowrap">
    Free Style
    </span>
    </button>
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    Add type="button" to both toggles.

    Without an explicit type, these default to type="submit". If this switcher ever gets dropped inside a <form> (navbar search, onboarding, etc.), clicking "Guided"/"Free Style" will submit the form. Cheap defensive fix:

    🛡️ Diff
           <button
    +        type="button"
             onClick={() => setUserType("guided")}
             aria-pressed={isGuided}
    ...
           <button
    +        type="button"
             onClick={() => setUserType("self-directed")}
             aria-pressed={isSelfDirected}
    📝 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
    onClick={() => setUserType("guided")}
    aria-pressed={isGuided}
    className={`relative z-10 flex items-center justify-center space-x-2 px-4 py-1.5 rounded-lg transition-colors duration-300 ${getGuidedTextColor()}`}
    >
    <Compass size={16} className={isGuided ? "animate-pulse" : ""} />
    <span className="text-sm font-semibold whitespace-nowrap">Guided</span>
    </button>
    {/* Free Style Option */}
    <button
    onClick={() => setUserType("self-directed")}
    aria-pressed={isSelfDirected}
    className={`relative z-10 flex items-center justify-center space-x-2 px-4 py-1.5 rounded-lg transition-colors duration-300 ${getSelfDirectedTextColor()}`}
    >
    <Map size={16} className={isSelfDirected ? "animate-pulse" : ""} />
    <span className="text-sm font-semibold whitespace-nowrap">
    Free Style
    </span>
    </button>
    <button
    type="button"
    onClick={() => setUserType("guided")}
    aria-pressed={isGuided}
    className={`relative z-10 flex items-center justify-center space-x-2 px-4 py-1.5 rounded-lg transition-colors duration-300 ${getGuidedTextColor()}`}
    >
    <Compass size={16} className={isGuided ? "animate-pulse" : ""} />
    <span className="text-sm font-semibold whitespace-nowrap">Guided</span>
    </button>
    {/* Free Style Option */}
    <button
    type="button"
    onClick={() => setUserType("self-directed")}
    aria-pressed={isSelfDirected}
    className={`relative z-10 flex items-center justify-center space-x-2 px-4 py-1.5 rounded-lg transition-colors duration-300 ${getSelfDirectedTextColor()}`}
    >
    <Map size={16} className={isSelfDirected ? "animate-pulse" : ""} />
    <span className="text-sm font-semibold whitespace-nowrap">
    Free Style
    </span>
    </button>
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@libs/common-ui/src/common/LearningModeSwitcher.tsx` around lines 63 - 82, The
    two toggle buttons in LearningModeSwitcher.tsx (the elements that call
    setUserType("guided") and setUserType("self-directed") and use
    isGuided/isSelfDirected, getGuidedTextColor/getSelfDirectedTextColor) lack an
    explicit type and will default to type="submit" inside forms; update both button
    elements to include type="button" to prevent accidental form submission (leave
    existing onClick, aria-pressed and className logic unchanged).
    

    - Fix empty catch block in guided learning plans route
    - Update website Jest config to collect coverage from all src files
    - Add unit tests for useActivePlan, useGuidedLearningPlans, and studyPlans to exceed 80% new code coverage
    Copy link
    Copy Markdown

    @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: 4

    ♻️ Duplicate comments (1)
    apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts (1)

    46-82: ⚠️ Potential issue | 🟠 Major

    Supabase client init still sits outside the DB try/catch — hardcoded fallback isn't actually guaranteed.

    If getSupabaseClient() throws and then getSupabaseClientWithAnonKey() also throws (e.g., missing NEXT_PUBLIC_SUPABASE_URL/anon key in a given environment), execution escapes to the outer catch at line 85 and the route returns a 500 — even though we already have perfectly good hardcoded plans ready to serve. The whole point of the refactor ("hardcoded-first") is defeated in exactly the failure mode it's meant to protect against.

    Additionally, dbPlans are merged at line 75 with no shape check. Any legacy learning_plans rows still using the old schedule structure will leak into the response and break useGuidedLearningPlans consumers that now expect milestones[] + estimatedTotalTime.

    Suggested direction (move client init inside the inner try, and validate rows before merging):

    🛠️ Proposed fix
    -    // Otherwise, get all learning plans
    -    // Use anon key for public read access (this is a public endpoint)
    -    let supabase;
    -    try {
    -      supabase = getSupabaseClient(); // Try service role first
    -    } catch (clientError) {
    -      console.warn(
    -        "Supabase service client initialization failed, falling back to anon key:",
    -        clientError,
    -      );
    -      supabase = getSupabaseClientWithAnonKey();
    -    }
    -
    -    // Import hardcoded featured plans
    -    const { studyPlans: hardcodedPlans } = await import("@elzatona/utilities");
    -
    -    // Convert hardcoded plans to match the expected API structure if needed
    -    // The new structure is already what we want
    -    let plans = [...hardcodedPlans];
    -
    -    try {
    -      // Try to fetch additional plans from Supabase if available
    -      const { data: dbPlans, error } = await supabase
    -        .from("learning_plans")
    -        .select("*")
    -        .order("created_at", { ascending: false });
    -
    -      if (!error && dbPlans && dbPlans.length > 0) {
    -        // Merge DB plans with hardcoded plans, avoiding duplicates by ID
    -        const hardcodedIds = new Set(plans.map((p) => p.id));
    -        const filteredDbPlans = dbPlans.filter((p) => !hardcodedIds.has(p.id));
    -        plans = [...plans, ...filteredDbPlans];
    -      }
    -    } catch (dbError) {
    +    // 📚 Hardcoded-first: guarantee a working response even if Supabase is down
    +    const { studyPlans: hardcodedPlans } = await import("@elzatona/utilities");
    +    let plans: StudyPlan[] = [...hardcodedPlans];
    +
    +    try {
    +      let supabase;
    +      try {
    +        supabase = getSupabaseClient();
    +      } catch (clientError) {
    +        console.warn(
    +          "⚠️ Supabase service client init failed, falling back to anon key:",
    +          clientError,
    +        );
    +        supabase = getSupabaseClientWithAnonKey();
    +      }
    +
    +      const { data: dbPlans, error } = await supabase
    +        .from("learning_plans")
    +        .select("*")
    +        .order("created_at", { ascending: false });
    +
    +      if (!error && Array.isArray(dbPlans) && dbPlans.length > 0) {
    +        const hardcodedIds = new Set(plans.map((plan) => plan.id));
    +        const filteredDbPlans = dbPlans.filter(
    +          (plan): plan is StudyPlan =>
    +            isMilestonePlan(plan) && !hardcodedIds.has(plan.id),
    +        );
    +        plans = [...plans, ...filteredDbPlans];
    +      }
    +    } catch (dbError) {
           console.warn(
             "⚠️ Database fetch failed for learning plans, using hardcoded only:",
             dbError,
           );
         }

    …with a small guard helper at module scope:

    import type { StudyPlan } from "@elzatona/types";
    
    const isMilestonePlan = (plan: unknown): plan is StudyPlan => {
      if (typeof plan !== "object" || plan === null) return false;
      const candidate = plan as Partial<StudyPlan>;
      return (
        typeof candidate.id === "string" &&
        Array.isArray(candidate.milestones) &&
        candidate.milestones.length > 0
      );
    };

    Worth adding a test case where both client factories throw, asserting the route still responds 200 with hardcoded plans — the current __tests__/route.test.ts only covers the post-client failure path.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts`
    around lines 46 - 82, The Supabase clients are initialized outside the DB
    try/catch so if both getSupabaseClient() and getSupabaseClientWithAnonKey()
    throw the route will bubble a 500 instead of returning the hardcoded plans; move
    the Supabase initialization into the inner try block (call getSupabaseClient(),
    fall back to getSupabaseClientWithAnonKey() there, and if both throw just log
    and continue without throwing) and before merging dbPlans into plans validate
    each row with a module-scoped type guard (e.g., isMilestonePlan) that ensures id
    is string and milestones is a non-empty array (and optionally
    estimatedTotalTime) then filter dbPlans with that guard before merging; also add
    a test where both client factories throw asserting the route returns 200 with
    only hardcoded plans.
    
    🧹 Nitpick comments (2)
    apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts (1)

    58-58: Minor: dynamic import() inside the hot path.

    await import("@elzatona/utilities") on every GET buys nothing here (no code-splitting benefit on a server route, and the module is tiny static data). A top-level import { studyPlans } from "@elzatona/utilities"; is simpler, avoids an await on the happy path, and makes the dependency visible to bundler tree-shaking analysis. Non-blocking.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts` at
    line 58, Replace the dynamic import with a top-level static import: remove the
    line using `await import("@elzatona/utilities")` and instead add a top-level
    `import { studyPlans as hardcodedPlans } from "@elzatona/utilities";` so that
    the GET handler (in this route module) no longer awaits a module at runtime, the
    dependency is visible to the bundler, and the `hardcodedPlans` symbol is
    available directly to any function (e.g., the route handler) that uses it.
    
    apps/website/src/app/features/guided-learning/__tests__/page.test.tsx (1)

    94-110: Assert the milestone prop rewiring, not just the title.

    This PR changes GuidedLearningPage to pass milestones and currentMilestoneId into ActivePlanView, but the test would still pass if those props were omitted. Have the mock render those props and assert them here.

    Example assertion-focused adjustment
    -  ActivePlanView: ({ plan }: ActivePlanViewMockProps) => (
    -    <div data-testid="active-plan">{plan.title}</div>
    +  ActivePlanView: ({
    +    plan,
    +    milestones,
    +    currentMilestoneId,
    +  }: ActivePlanViewMockProps & {
    +    milestones: Array<{ id: string }>;
    +    currentMilestoneId: string | null;
    +  }) => (
    +    <div data-testid="active-plan">
    +      <span>{plan.title}</span>
    +      <span data-testid="milestone-count">{milestones.length}</span>
    +      <span data-testid="current-milestone">{currentMilestoneId}</span>
    +    </div>
       ),
    @@
         (useActivePlan as jest.Mock).mockReturnValue({
           currentPlan: { id: "p1", title: "Active Plan" },
    -      milestones: [],
    +      milestones: [{ id: "m1" }],
           currentMilestoneId: "m1",
           resumePlan: jest.fn(),
           resetPlan: jest.fn(),
           selectPlan: jest.fn(),
    @@
         render(<GuidedLearningPage />);
         expect(screen.getByTestId("active-plan")).toBeDefined();
         expect(screen.getByText("Active Plan")).toBeDefined();
    +    expect(screen.getByTestId("milestone-count")).toHaveTextContent("1");
    +    expect(screen.getByTestId("current-milestone")).toHaveTextContent("m1");
       });

    Based on learnings, “Applies to **/*.test.{ts,tsx} : Update affected unit tests (*.test.tsx, *.test.ts) when code changes - update existing tests, add new tests for new functionality, remove obsolete tests.”

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/__tests__/page.test.tsx` around
    lines 94 - 110, The test currently only asserts the Active Plan title but not
    that GuidedLearningPage forwards milestones/currentMilestoneId; update the
    useActivePlan mock to include an explicit milestones array and
    currentMilestoneId (e.g., milestones: [{id: "m1", title: "Milestone 1"}],
    currentMilestoneId: "m1") and then mock the ActivePlanView component
    (ActivePlanView as jest.Mock) and assert it was rendered with those props
    (expect(ActivePlanView).toHaveBeenCalledWith(expect.objectContaining({milestones:
    expect.any(Array), currentMilestoneId: "m1"}), expect.anything())). Ensure you
    reference useActivePlan, GuidedLearningPage, and ActivePlanView in the change.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Inline comments:
    In `@apps/website/jest.config.js`:
    - Around line 16-23: The collectCoverageFrom glob currently pulls non-source
    files and co-located tests; update the collectCoverageFrom array (in
    jest.config.js) to target only source under common-ui by changing
    "../../libs/common-ui/**/*.{js,jsx,ts,tsx}" to
    "../../libs/common-ui/src/**/*.{js,jsx,ts,tsx}" and add explicit exclusions for
    co-located test files (e.g., exclude patterns like "!**/*.test.{ts,tsx,js,jsx}",
    "!**/*.integration.test.{ts,tsx,js,jsx}" or a general "!**/*.test*" entry) so
    test files such as *.test.tsx and *.integration.test.tsx are not counted in
    coverage.
    
    In `@apps/website/src/app/features/guided-learning/__tests__/page.test.tsx`:
    - Around line 23-45: Replace all uses of `any` in the test mocks by declaring
    explicit prop interfaces and typing the router: add interfaces
    SignInCTABannerMockProps (with onSignIn: () => void), ActivePlanViewMockProps
    (with plan: { title: string }), and PlanSelectionViewMockProps (with
    onSelectPlan: (plan: { id: string }) => void) and use those in the jest.mock
    component signatures instead of `any`; type `mockRouter` as Partial<NextRouter>
    (import NextRouter from 'next/router') or the appropriate router type used in
    the app, so the test accurately reflects prop shapes and router methods without
    `any`.
    
    In
    `@apps/website/src/app/features/guided-learning/hooks/__tests__/useActivePlan.test.ts`:
    - Around line 46-70: Remove the `as any` cast by typing `mockPlan` as
    `LearningPlan`, then update the two tests around useActivePlan to assert
    milestone progress persistence: in the select test (where
    result.current.selectPlan is called) assert
    localStorage.getItem("plan-progress-test-plan") === "m1" in addition to the
    existing checks; in the reset test (where result.current.resetPlan is called)
    assert localStorage.getItem("plan-progress-test-plan") is null; keep references
    to the hook and methods (`useActivePlan`, `selectPlan`, `resetPlan`, and
    `mockPlan`) so the assertions target the same behavior.
    
    In `@apps/website/src/app/features/guided-learning/page.tsx`:
    - Around line 63-66: The loading spinner (the div with
    data-testid="loading-spinner") is visual-only; add accessibility attributes so
    screen readers announce it: give that element role="status" and either
    aria-live="polite" or an aria-label like "Loading…" and/or include a
    visually-hidden text node (e.g., hidden span with "Loading…" for assistive tech)
    inside the same component to convey progress to screen-reader users; update the
    JSX in page.tsx where the spinner is rendered to include these attributes and/or
    the hidden label.
    
    ---
    
    Duplicate comments:
    In `@apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts`:
    - Around line 46-82: The Supabase clients are initialized outside the DB
    try/catch so if both getSupabaseClient() and getSupabaseClientWithAnonKey()
    throw the route will bubble a 500 instead of returning the hardcoded plans; move
    the Supabase initialization into the inner try block (call getSupabaseClient(),
    fall back to getSupabaseClientWithAnonKey() there, and if both throw just log
    and continue without throwing) and before merging dbPlans into plans validate
    each row with a module-scoped type guard (e.g., isMilestonePlan) that ensures id
    is string and milestones is a non-empty array (and optionally
    estimatedTotalTime) then filter dbPlans with that guard before merging; also add
    a test where both client factories throw asserting the route returns 200 with
    only hardcoded plans.
    
    ---
    
    Nitpick comments:
    In `@apps/website/src/app/features/guided-learning/__tests__/page.test.tsx`:
    - Around line 94-110: The test currently only asserts the Active Plan title but
    not that GuidedLearningPage forwards milestones/currentMilestoneId; update the
    useActivePlan mock to include an explicit milestones array and
    currentMilestoneId (e.g., milestones: [{id: "m1", title: "Milestone 1"}],
    currentMilestoneId: "m1") and then mock the ActivePlanView component
    (ActivePlanView as jest.Mock) and assert it was rendered with those props
    (expect(ActivePlanView).toHaveBeenCalledWith(expect.objectContaining({milestones:
    expect.any(Array), currentMilestoneId: "m1"}), expect.anything())). Ensure you
    reference useActivePlan, GuidedLearningPage, and ActivePlanView in the change.
    
    In `@apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts`:
    - Line 58: Replace the dynamic import with a top-level static import: remove the
    line using `await import("@elzatona/utilities")` and instead add a top-level
    `import { studyPlans as hardcodedPlans } from "@elzatona/utilities";` so that
    the GET handler (in this route module) no longer awaits a module at runtime, the
    dependency is visible to the bundler, and the `hardcodedPlans` symbol is
    available directly to any function (e.g., the route handler) that uses it.
    
    🪄 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: 6c0d2ca1-1b07-415e-85b3-036e8b297c55

    📥 Commits

    Reviewing files that changed from the base of the PR and between 77e183d and 27dc103.

    📒 Files selected for processing (7)
    • apps/website/jest.config.js
    • apps/website/src/app/features/guided-learning/__tests__/page.test.tsx
    • apps/website/src/app/features/guided-learning/hooks/__tests__/useActivePlan.test.ts
    • apps/website/src/app/features/guided-learning/hooks/__tests__/useGuidedLearningPlans.test.ts
    • apps/website/src/app/features/guided-learning/page.tsx
    • apps/website/src/app/lib/network/routes/guided-learning/plans/route.ts
    • libs/utilities/src/lib/studyPlans.test.ts

    Comment on lines +16 to +23
    collectCoverageFrom: [
    "src/**/*.{js,jsx,ts,tsx}",
    "../../libs/common-ui/**/*.{js,jsx,ts,tsx}",
    "!**/*.d.ts",
    "!**/node_modules/**",
    "!**/__tests__/**",
    "!**/test-utils/**",
    ],
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Description: Inspect common-ui JS/TS files that the broad coverage glob may include unintentionally.
    # Expected: Ideally no "outside src" entries, and co-located test/spec files should be explicitly excluded.
    
    fd -e js -e jsx -e ts -e tsx . libs/common-ui \
      | sed 's#^\./##' \
      | grep -Ev '(^|/)(node_modules|__tests__|test-utils)/|\.d\.ts$' \
      | awk '
          /\/src\/.*\.(test|spec)\.[jt]sx?$/ { print "co-located test/spec: " $0 }
          !/\/src\// { print "outside src: " $0 }
        ' \
      | head -200

    Repository: FoushWare/elzatona_web

    Length of output: 3278


    Exclude co-located test files from coverage and narrow the common-ui glob to source only.

    The current ../../libs/common-ui/**/*.{js,jsx,ts,tsx} glob pulls in files outside src/ (e.g., vite.config.ts) and includes all co-located test files (36 instances: *.test.tsx, *.integration.test.tsx). The existing exclusion list doesn't cover them, causing coverage metrics to inflate. Tighten to src/ and add an explicit test-file exclusion.

    Suggested fix
       collectCoverageFrom: [
         "src/**/*.{js,jsx,ts,tsx}",
    -    "../../libs/common-ui/**/*.{js,jsx,ts,tsx}",
    +    "../../libs/common-ui/src/**/*.{js,jsx,ts,tsx}",
         "!**/*.d.ts",
    +    "!**/*.{test,spec}.{js,jsx,ts,tsx}",
         "!**/node_modules/**",
         "!**/__tests__/**",
         "!**/test-utils/**",
       ],
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/jest.config.js` around lines 16 - 23, The collectCoverageFrom
    glob currently pulls non-source files and co-located tests; update the
    collectCoverageFrom array (in jest.config.js) to target only source under
    common-ui by changing "../../libs/common-ui/**/*.{js,jsx,ts,tsx}" to
    "../../libs/common-ui/src/**/*.{js,jsx,ts,tsx}" and add explicit exclusions for
    co-located test files (e.g., exclude patterns like "!**/*.test.{ts,tsx,js,jsx}",
    "!**/*.integration.test.{ts,tsx,js,jsx}" or a general "!**/*.test*" entry) so
    test files such as *.test.tsx and *.integration.test.tsx are not counted in
    coverage.
    

    Comment on lines +23 to +45
    // Mock the components to keep tests focused
    jest.mock("../components", () => ({
    GuidedLearningHeader: () => <div data-testid="header" />,
    SignInCTABanner: ({ onSignIn }: any) => (
    <button data-testid="signin-banner" onClick={onSignIn}>
    Sign In CTA
    </button>
    ),
    ActivePlanView: ({ plan }: any) => (
    <div data-testid="active-plan">{plan.title}</div>
    ),
    PlanSelectionView: ({ onSelectPlan }: any) => (
    <button
    data-testid="select-plan"
    onClick={() => onSelectPlan({ id: "p1" })}
    >
    Select Plan
    </button>
    ),
    }));

    describe("GuidedLearningPage", () => {
    let mockRouter: any;
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Description: Verify explicit `any` is removed from the page test.
    rg -nP '\bas\s+any\b|:\s*any\b' apps/website/src/app/features/guided-learning/__tests__/page.test.tsx

    Repository: FoushWare/elzatona_web

    Length of output: 233


    🏁 Script executed:

    sed -n '20,50p' apps/website/src/app/features/guided-learning/__tests__/page.test.tsx | cat -n

    Repository: FoushWare/elzatona_web

    Length of output: 1076


    Replace any types with explicit prop and variable type definitions.

    The mock component props and mockRouter use explicit any, violating the no-any rule. Define proper types for SignInCTABannerMockProps, ActivePlanViewMockProps, PlanSelectionViewMockProps, and the mockRouter variable to maintain type safety.

    Suggested typing cleanup
    +type SignInCTABannerMockProps = {
    +  onSignIn: () => void;
    +};
    +
    +type ActivePlanViewMockProps = {
    +  plan: { title: string };
    +};
    +
    +type PlanSelectionViewMockProps = {
    +  onSelectPlan: (plan: { id: string }) => void;
    +};
    +
     // Mock the components to keep tests focused
     jest.mock("../components", () => ({
       GuidedLearningHeader: () => <div data-testid="header" />,
    -  SignInCTABanner: ({ onSignIn }: any) => (
    +  SignInCTABanner: ({ onSignIn }: SignInCTABannerMockProps) => (
         <button data-testid="signin-banner" onClick={onSignIn}>
           Sign In CTA
         </button>
       ),
    -  ActivePlanView: ({ plan }: any) => (
    +  ActivePlanView: ({ plan }: ActivePlanViewMockProps) => (
         <div data-testid="active-plan">{plan.title}</div>
       ),
    -  PlanSelectionView: ({ onSelectPlan }: any) => (
    +  PlanSelectionView: ({ onSelectPlan }: PlanSelectionViewMockProps) => (
         <button
           data-testid="select-plan"
           onClick={() => onSelectPlan({ id: "p1" })}
           >
             Select Plan
           </button>
         ),
     }));
     
     describe("GuidedLearningPage", () => {
    -  let mockRouter: any;
    +  let mockRouter: { refresh: jest.Mock };
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/__tests__/page.test.tsx` around
    lines 23 - 45, Replace all uses of `any` in the test mocks by declaring explicit
    prop interfaces and typing the router: add interfaces SignInCTABannerMockProps
    (with onSignIn: () => void), ActivePlanViewMockProps (with plan: { title: string
    }), and PlanSelectionViewMockProps (with onSelectPlan: (plan: { id: string }) =>
    void) and use those in the jest.mock component signatures instead of `any`; type
    `mockRouter` as Partial<NextRouter> (import NextRouter from 'next/router') or
    the appropriate router type used in the app, so the test accurately reflects
    prop shapes and router methods without `any`.
    

    Comment on lines +46 to +70
    it("should select a plan and update localStorage", () => {
    const { result } = renderHook(() => useActivePlan(true));

    act(() => {
    result.current.selectPlan(mockPlan as any);
    });

    expect(result.current.currentPlan).toEqual(mockPlan);
    expect(localStorage.getItem("active-guided-plan")).toBe(
    JSON.stringify(mockPlan),
    );
    expect(mockPush).toHaveBeenCalledWith("/guided-practice?plan=test-plan");
    });

    it("should reset plan and clear localStorage", () => {
    localStorage.setItem("active-guided-plan", JSON.stringify(mockPlan));
    const { result } = renderHook(() => useActivePlan(true));

    act(() => {
    result.current.resetPlan();
    });

    expect(result.current.currentPlan).toBeNull();
    expect(localStorage.getItem("active-guided-plan")).toBeNull();
    });
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Description: Verify explicit `any` is removed from the useActivePlan test.
    rg -nP '\bas\s+any\b|:\s*any\b' apps/website/src/app/features/guided-learning/hooks/__tests__/useActivePlan.test.ts

    Repository: FoushWare/elzatona_web

    Length of output: 118


    🏁 Script executed:

    cat -n apps/website/src/app/features/guided-learning/hooks/__tests__/useActivePlan.test.ts

    Repository: FoushWare/elzatona_web

    Length of output: 3539


    🏁 Script executed:

    fd -t f "useActivePlan" --exclude "*.test.ts" apps/website/src/app/features/guided-learning

    Repository: FoushWare/elzatona_web

    Length of output: 134


    🏁 Script executed:

    cat -n apps/website/src/app/features/guided-learning/hooks/useActivePlan.ts

    Repository: FoushWare/elzatona_web

    Length of output: 3725


    Remove the as any cast and verify milestone progress storage in tests.

    Line 50 uses as any which violates TypeScript strict typing guidelines. Additionally, the hook actively persists plan-progress-${plan.id} on plan selection (set to the first milestone) and clears it on reset, but these assertions are missing from the tests.

    Type mockPlan as LearningPlan and add assertions for the milestone progress key in both the select and reset tests:

    • Verify localStorage.getItem("plan-progress-test-plan") equals "m1" after selection
    • Verify it's removed (null) after reset
    Suggested fix
     describe("useActivePlan", () => {
       let mockPush: jest.Mock;
       const mockPlan = {
    +    id: "test-plan",
    +    title: "Test Plan",
    +    duration: { weeks: 1, hoursPerWeek: 10, totalHours: 10 },
    +    difficulty: "beginner",
    +    milestones: [{ id: "m1", title: "Milestone 1", tasks: [] }],
       };
    
       it("should select a plan and update localStorage", () => {
         const { result } = renderHook(() => useActivePlan(true));
    
         act(() => {
    -      result.current.selectPlan(mockPlan as any);
    +      result.current.selectPlan(mockPlan);
         });
    
         expect(result.current.currentPlan).toEqual(mockPlan);
         expect(localStorage.getItem("active-guided-plan")).toBe(
           JSON.stringify(mockPlan),
         );
    +    expect(localStorage.getItem("plan-progress-test-plan")).toBe("m1");
         expect(mockPush).toHaveBeenCalledWith("/guided-practice?plan=test-plan");
       });
    
       it("should reset plan and clear localStorage", () => {
         localStorage.setItem("active-guided-plan", JSON.stringify(mockPlan));
    +    localStorage.setItem("plan-progress-test-plan", "m1");
         const { result } = renderHook(() => useActivePlan(true));
    
         act(() => {
           result.current.resetPlan();
         });
    
         expect(result.current.currentPlan).toBeNull();
         expect(localStorage.getItem("active-guided-plan")).toBeNull();
    +    expect(localStorage.getItem("plan-progress-test-plan")).toBeNull();
       });
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@apps/website/src/app/features/guided-learning/hooks/__tests__/useActivePlan.test.ts`
    around lines 46 - 70, Remove the `as any` cast by typing `mockPlan` as
    `LearningPlan`, then update the two tests around useActivePlan to assert
    milestone progress persistence: in the select test (where
    result.current.selectPlan is called) assert
    localStorage.getItem("plan-progress-test-plan") === "m1" in addition to the
    existing checks; in the reset test (where result.current.resetPlan is called)
    assert localStorage.getItem("plan-progress-test-plan") is null; keep references
    to the hook and methods (`useActivePlan`, `selectPlan`, `resetPlan`, and
    `mockPlan`) so the assertions target the same behavior.
    

    Comment on lines +63 to +66
    <div
    data-testid="loading-spinner"
    className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
    ></div>
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    Expose the loading state to assistive technologies.

    The spinner is currently visual-only. Add a status role and accessible label so screen-reader users receive the loading feedback too.

    Accessibility fix
             <div
               data-testid="loading-spinner"
    +          role="status"
    +          aria-label="Loading guided learning"
               className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
    -        ></div>
    +        >
    +          <span className="sr-only">Loading guided learning...</span>
    +        </div>

    As per coding guidelines, “Ensure responsive and accessible design in UI components.”

    📝 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
    <div
    data-testid="loading-spinner"
    className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
    ></div>
    <div
    data-testid="loading-spinner"
    role="status"
    aria-label="Loading guided learning"
    className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
    >
    <span className="sr-only">Loading guided learning...</span>
    </div>
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@apps/website/src/app/features/guided-learning/page.tsx` around lines 63 - 66,
    The loading spinner (the div with data-testid="loading-spinner") is visual-only;
    add accessibility attributes so screen readers announce it: give that element
    role="status" and either aria-live="polite" or an aria-label like "Loading…"
    and/or include a visually-hidden text node (e.g., hidden span with "Loading…"
    for assistive tech) inside the same component to convey progress to
    screen-reader users; update the JSX in page.tsx where the spinner is rendered to
    include these attributes and/or the hidden label.
    

    Dev CI added 12 commits April 18, 2026 23:31
    …nt errors
    
    - Consolidated Admin Questions API handler into shared utility library
    - Decomposed high-complexity routes (Plan Hierarchy, Progress Sync, Topics, Check Project)
    - Refactored Frontend Task Validator to use React 19 createRoot and modular evaluation
    - Cleaned up Environment Loaders and Learning Resources Service
    - Resolved lint errors by adding displayNames to memoized dashboard components
    - Verified fix for constant return value blocker in auth-config.ts
    - Resolved critical ReactDOM.render blocker in frontend validator
    - Fixed ternary operator code smells in content management hooks
    - Improved type safety for guided learning utilities (RangeInfo refactor)
    - Consolidated and restored missing shared utility functions
    - Added null-safety guards and fixed JSX syntax in dashboard
    - Performed global formatting sweep to resolve Prettier CI blockers
    - Verified local build and lint successfully
    - Refactor TopicFormModal for accessibility (semantic buttons)
    - Add aria-hidden to decorative checkbox icons
    - Implement 50+ unit tests for utility libraries (81.4% coverage)
    - Update SonarCloud workflow to include new coverage files
    - Apply Prettier formatting
    - Refactored 'authorize' in auth-config.ts to resolve S3516 (always returns same value)
    - Refactored 'EnhancedDashboard.tsx' into 'useDashboardData' hook to reduce complexity
    - Fixed 'ReactDOM.createRoot' mock in website utility tests
    - Aligned error messages and logging in FrontendTaskValidator
    - Improved validation utility modularity
    - Completed refactor of EnhancedDashboard for complexity
    - Fixed Prettier formatting in newly added tests
    - Verified all quality checks pass locally
    Dev CI added 22 commits April 20, 2026 00:05
    …ties, and plan hierarchy to resolve SonarQube new code coverage gate
    …overage
    
    - Resolved regex warnings in markdown-question-parser.ts
    - Removed unused imports and variables in dashboard and editor components
    - Fixed deprecated ZodIssue and global parseInt usage
    - Extracted complex ternary operations for better readability
    - Added 35+ unit tests for utilities, hooks, and API routes to exceed 80% coverage on new code
    …s and API routes, and increase test coverage
    @sonarqubecloud
    Copy link
    Copy Markdown

    sonarqubecloud Bot commented May 6, 2026

    Quality Gate Failed Quality Gate failed

    Failed conditions
    23.1% Coverage on New Code (required ≥ 80%)
    5.3% Duplication on New Code (required ≤ 3%)

    See analysis details on SonarQube Cloud

    @FoushWare FoushWare merged commit f668f27 into main May 6, 2026
    20 of 21 checks passed
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants