Skip to content

refactor: remove meilisearch and all dual write logic#27

Merged
Shunseii merged 8 commits intomainfrom
chore/remove-meili-dual-write
Dec 26, 2025
Merged

refactor: remove meilisearch and all dual write logic#27
Shunseii merged 8 commits intomainfrom
chore/remove-meili-dual-write

Conversation

@Shunseii
Copy link
Copy Markdown
Owner

@Shunseii Shunseii commented Dec 25, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced flashcard UI with animated feedback, per-option due display, and a new learning_steps field for scheduling.
  • Removed Features

    • Meilisearch-powered search, indexing, import/export, and related API routes; remote sync and deployment workflows removed.
    • Global decks/settings endpoints and shared schema package removed.
  • Bug Fixes

    • Improved local review reliability and visual feedback.
  • Chores

    • Dependency upgrades and local development docs updated; container/deploy configs removed.

✏️ Tip: You can customize this high-level summary in your review settings.

@Shunseii Shunseii self-assigned this Dec 25, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 25, 2025

Warning

Rate limit exceeded

@Shunseii has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e3f01de and 3ac7f20.

📒 Files selected for processing (3)
  • apps/api/src/auth.ts
  • apps/web/src/lib/error.ts
  • apps/web/src/lib/i18n.ts
📝 Walkthrough

Walkthrough

Removed Meilisearch and related deployment/config, deleted TRPC routers and many schemas, migrated schema imports to a local dictionary module, upgraded Zod to v4, added flashcard learning_steps, and replaced several TRPC mutations with React Query/local table mutations.

Changes

Cohort / File(s) Summary
Search & Meilisearch removal
.github/workflows/deploy-search.yml, .github/workflows/update-indexes.yml, apps/search/*, apps/api/src/clients/meilisearch.ts, scripts/update-indexes.sh, apps/api/scripts/migrate-meilisearch-to-user-db.ts
Removed Meilisearch client, deployment workflows, config, Docker/Fly artifacts, index update script, and Meilisearch→user-db migration script.
API routers & schemas removed
apps/api/src/routers/{dictionary,flashcard,tags,settings,decks}.ts, apps/api/src/schemas/*, apps/api/src/index.ts, apps/api/src/db/schema/{decks,settings}.ts, apps/api/src/utils/error.ts, apps/api/src/utils/index.ts
Deleted TRPC routers, many Zod schemas and schema exports, error utility module, and schema-bundling helper; API surface reduced and related routes removed.
Central schema package removed / localized
packages/schemas/*, apps/web/src/lib/schemas/dictionary.ts, apps/mobile/src/**
Removed @bahar/schemas package and barrel files; added local FormSchema/Inflection in web and updated imports across web/mobile to use local schema.
TRPC → React Query / local mutations
apps/web/src/**, apps/mobile/src/** (multiple files)
Replaced many TRPC mutations with React Query/table mutations and local-first flows; removed remote dual-write/trpc client calls in places (deck, settings, dictionary, flashcards).
Docker / Compose / Deployment removal
docker-compose*.yaml, docker-compose.yaml, apps/web/Dockerfile, apps/search/Dockerfile, apps/search/fly.toml, apps/search/config.toml, apps/search/example.env
Removed Dockerfiles, Compose services, Fly.io config and search Docker config/example envs.
Env / config / docs changes
apps/api/.example.env, apps/mobile/.example.env, apps/web/.example.env, apps/web/env.ts, apps/web/src/env.d.ts, apps/api/src/utils/config.ts, Makefile, CLAUDE.md, README.md, .gitignore, turbo.json
Dropped Meilisearch env vars, switched SENDGRID→RESEND, added SENTRY_ENV, removed web env validation/types, adjusted Makefile targets/docs, added .gitignore pattern, and set turbo UI stream.
Drizzle / DB migrations & schema updates
apps/api/drizzle/{0011_*.sql,0012_*.sql}, apps/api/drizzle/meta/*, packages/drizzle-user-db-schemas/drizzle/*, packages/drizzle-user-db-schemas/src/flashcards.ts, apps/api/src/db/schema/auth.ts
Added/updated migrations and snapshots: migrated auth tables to millisecond timestamps, added/updated indexes/relations, and added user-db flashcards learning_steps column and schema changes.
FSRS / flashcard scheduling changes
packages/fsrs/src/index.ts, apps/web/src/lib/db/operations/flashcards.ts
Propagated new learning_steps field through FSRS conversions, grading, and DB operations; exposed updates and returned learning_steps in queries.
Flashcard UI refactor & utilities
apps/web/src/components/features/flashcards/FlashcardDrawer/*
Extracted GradeFeedback, GradeOption, TagBadgesList, formatInterval util; restructured FlashcardDrawer imports and removed inline grading utilities/types.
Zod v3 → v4 migration
apps/web/src/lib/{zod.ts,error.ts}, apps/mobile/src/utils/zod.ts, packages/*/package.json, apps/api/package.json
Upgraded to zod v4 APIs: error map/type changes, z.config({ customError }) usage, updated types and error-handling code, and bumped zod deps across packages.
Misc cleanups & removals
docker-compose.yaml, apps/search/README.md, packages/schemas/tsconfig.json, assorted README/script docs`
Removed deprecated search README, package tsconfig/manifest, and references to removed migration scripts and tooling.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I nudged the indexes from cloud to ground,
Tables learned new steps without a sound,
Schemas moved home, imports found their nest,
Mutations now local — the rabbit hops, impressed,
A little refactor, and the code can rest.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: removing Meilisearch integration and all dual-write patterns throughout the codebase.

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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 25, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bahar-marketing 3ac7f20 Commit Preview URL Dec 26 2025, 12:00 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 25, 2025

Deploying bahar with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3ac7f20
Status: ✅  Deploy successful!
Preview URL: https://9411403d.bahar-5xu.pages.dev
Branch Preview URL: https://chore-remove-meili-dual-writ.bahar-5xu.pages.dev

View logs

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: 2

Caution

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

⚠️ Outside diff range comments (3)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (1)

199-211: Add error handling for the delete mutation.

The delete operation lacks error handling—if deleteDeck fails, the success toast still displays and the error goes unhandled. Wrap the mutation call in a try-catch block and show appropriate feedback to the user.

As per coding guidelines, use try/catch blocks and structured error types for error handling.

🔎 Proposed fix to add error handling
 <DropdownMenuItem
   onClick={async () => {
-    await deleteDeck({ id: deck.id });
-
-    toast({
-      title: t`Deck successfully deleted!`,
-      description: t`The deck "${deck.name}" has been deleted.`,
-    });
+    try {
+      await deleteDeck({ id: deck.id });
+
+      toast({
+        title: t`Deck successfully deleted!`,
+        description: t`The deck "${deck.name}" has been deleted.`,
+      });
+    } catch (error) {
+      toast({
+        title: t`Failed to delete deck`,
+        description: t`An error occurred while deleting "${deck.name}". Please try again.`,
+        variant: "destructive",
+      });
+    }
   }}
   className="cursor-pointer"
 >
apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx (2)

48-65: Add cache invalidation after flashcard reset.

The mutation successfully resets flashcards but doesn't invalidate any related queries. If there are any UI components displaying flashcard state on this page or elsewhere, they may show stale data after the reset.

🔎 Add cache invalidation
 const { mutateAsync: resetFlashcard } = useMutation({
   mutationFn: async ({
     dictionary_entry_id,
   }: {
     dictionary_entry_id: string;
   }) => {
     await Promise.all([
       flashcardsTable.reset.mutation({
         dictionary_entry_id,
         direction: "forward",
       }),
       flashcardsTable.reset.mutation({
         dictionary_entry_id,
         direction: "reverse",
       }),
     ]);
   },
+  onSuccess: async () => {
+    await queryClient.invalidateQueries({
+      queryKey: flashcardsTable.today.cacheOptions.queryKey,
+    });
+  },
 });

280-336: Add cache invalidation after dictionary entry update.

After successfully updating the dictionary entry, the query cache is not invalidated. This could lead to stale data being displayed if the user navigates to other pages that show this entry.

🔎 Add cache invalidation after successful edit
 const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (data) => {
   try {
     const root = data?.root
       ?.trim()
       ?.replace(/[\s,]+/g, "")
       ?.split("");

     const tags = data?.tags?.map((tag) => tag.name) ?? [];

     const id = wordId;

     const input = (() => {
       if (data.type === "ism") {
         return {
           ...data,
           id,
           root,
           tags,
           morphology: { ism: data?.morphology?.ism },
         };
       } else if (data.type === "fi'l") {
         return {
           ...data,
           id,
           root,
           tags,
           morphology: { verb: data?.morphology?.verb },
         };
       } else {
         return {
           ...data,
           id,
           root,
           tags,
           morphology: undefined,
         };
       }
     })();

     await editDictionaryEntry({ id: input.id, updates: input });

+    await queryClient.invalidateQueries({
+      queryKey: [...dictionaryEntriesTable.entry.cacheOptions.queryKey, wordId],
+    });
+
     toast({
       title: t`Successfully updated the word!`,
       description: t`The word has been updated.`,
     });
   } catch (err) {
     if (err instanceof Error) {
       console.error(err.message);
     }

     toast({
       title: t`Failed to update the word!`,
       description: t`There was an error updating your word. Please try again.`,
       variant: "destructive",
     });
   }
 };
🧹 Nitpick comments (4)
turbo.json (1)

4-4: Consider moving this unrelated change to a separate PR.

The addition of "ui": "stream" enables real-time log streaming in Turborepo, which is a developer experience improvement. However, this change is unrelated to the PR's objective of removing Meilisearch infrastructure and dual write logic. Consider either:

  • Moving this to a separate PR for better change tracking
  • Adding a note in the PR description explaining this DX improvement
apps/api/README.md (1)

106-112: Consider automating the manual migration seeding process.

Steps 6–7 require users to manually copy SQL files, strip markers, and insert records into the migrations table. This is error-prone and difficult to maintain. Consider one of the following:

  • Create a helper script (e.g., seed-user-migrations.ts) to automate this process.
  • Provide a migration runner that handles this on first user login.
  • If this remains manual, add a clear script or SQL template users can copy and execute directly.

This will reduce friction during local setup and minimize setup failures.

Makefile (1)

20-20: Consider adding conventional Makefile targets (optional).

While not required, Makefiles conventionally include all, clean, and test phony targets for standardization. Given this appears to be a project-specific utility Makefile, these may not be necessary. However, consider:

  • all: Default target that could build everything needed for local development
  • clean: Could replace or wrap delete-local-data for consistency
  • test: Could trigger the test suite if you want Make-based testing workflows

Based on static analysis hints.

apps/web/src/lib/schemas/dictionary.ts (1)

32-78: Consider type-specific morphology validation.

The morphology structure allows both ism and verb fields to be present simultaneously, regardless of the type field value. While this provides flexibility, it could lead to inconsistent data (e.g., type: "ism" with populated verb morphology).

Consider adding a refinement to validate that morphology matches the word type, or document that this flexibility is intentional.

🔎 Optional refinement for type-specific validation
 export const FormSchema = z.object({
   word: z.string().min(1),
   translation: z.string().min(1),
   definition: z.string().optional(),
   root: z.string().optional(),
   tags: z.array(z.object({ name: z.string() })).optional(),
   antonyms: z
     .array(
       z.object({
         word: z.string(),
       }),
     )
     .optional(),
   examples: z
     .array(
       z.object({
         sentence: z.string(),
         context: z.string().optional(),
         translation: z.string().optional(),
       }),
     )
     .optional(),
   type: z.enum(["ism", "fi'l", "harf", "expression"]).optional(),
   morphology: z
     .object({
       ism: z
         .object({
           singular: z.string().optional(),
           dual: z.string().optional(),
           plurals: z
             .array(
               z.object({ word: z.string(), details: z.string().optional() }),
             )
             .optional(),
           gender: z.enum(["masculine", "feminine"]).optional(),
           inflection: z
             .enum(["indeclinable", "diptote", "triptote"])
             .optional(),
         })
         .optional(),
       verb: z
         .object({
           huroof: z
             .array(
               z.object({
                 harf: z.string(),
                 meaning: z.string().optional(),
               }),
             )
             .optional(),
           past_tense: z.string().optional(),
           present_tense: z.string().optional(),
           active_participle: z.string().optional(),
           passive_participle: z.string().optional(),
           imperative: z.string().optional(),
           masadir: z
             .array(
               z.object({
                 word: z.string(),
                 details: z.string().optional(),
               }),
             )
             .optional(),
           form: z.string().optional(),
           form_arabic: z.string().optional(),
         })
         .optional(),
     })
     .optional(),
-});
+}).refine(
+  (data) => {
+    if (data.type === "ism" && data.morphology?.verb) {
+      return false;
+    }
+    if (data.type === "fi'l" && data.morphology?.ism) {
+      return false;
+    }
+    if ((data.type === "harf" || data.type === "expression") && data.morphology) {
+      return false;
+    }
+    return true;
+  },
+  {
+    message: "Morphology must match the word type",
+  }
+);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 69ed37c and d49f612.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (75)
  • .github/workflows/deploy-search.yml
  • .github/workflows/update-indexes.yml
  • .gitignore
  • CLAUDE.md
  • Makefile
  • apps/api/.example.env
  • apps/api/README.md
  • apps/api/drizzle/0011_dazzling_jackal.sql
  • apps/api/drizzle/meta/0011_snapshot.json
  • apps/api/drizzle/meta/_journal.json
  • apps/api/package.json
  • apps/api/scripts/README.md
  • apps/api/scripts/migrate-meilisearch-to-user-db.ts
  • apps/api/scripts/migrate-settings-decks-to-user-db.ts
  • apps/api/src/auth.ts
  • apps/api/src/clients/meilisearch.ts
  • apps/api/src/clients/turso.ts
  • apps/api/src/db/schema/decks.ts
  • apps/api/src/db/schema/settings.ts
  • apps/api/src/dictionary.json
  • apps/api/src/flashcard.json
  • apps/api/src/index.ts
  • apps/api/src/routers/decks.ts
  • apps/api/src/routers/dictionary.ts
  • apps/api/src/routers/flashcard.ts
  • apps/api/src/routers/settings.ts
  • apps/api/src/routers/tags.ts
  • apps/api/src/schema.json
  • apps/api/src/schemas/deck.schema.ts
  • apps/api/src/schemas/dictionary.schema.ts
  • apps/api/src/schemas/flashcard.schema.ts
  • apps/api/src/schemas/index.ts
  • apps/api/src/schemas/words.schema.ts
  • apps/api/src/utils/config.ts
  • apps/api/src/utils/error.ts
  • apps/api/src/utils/index.ts
  • apps/mobile/.example.env
  • apps/mobile/package.json
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/mobile/src/components/flashcards/FlashcardReview.tsx
  • apps/mobile/src/lib/schemas/dictionary.ts
  • apps/search/Dockerfile
  • apps/search/README.md
  • apps/search/config.toml
  • apps/search/example.env
  • apps/search/fly.toml
  • apps/web/.example.env
  • apps/web/Dockerfile
  • apps/web/env.ts
  • apps/web/package.json
  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx
  • apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx
  • apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx
  • apps/web/src/components/features/dictionary/add/TagsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/lib/error.ts
  • apps/web/src/lib/schemas/dictionary.ts
  • apps/web/src/router.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/settings/route.lazy.tsx
  • docker-compose.prod.yaml
  • docker-compose.yaml
  • packages/schemas/package.json
  • packages/schemas/src/index.ts
  • packages/schemas/tsconfig.json
  • scripts/update-indexes.sh
  • turbo.json
💤 Files with no reviewable changes (45)
  • apps/api/src/clients/turso.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/settings/route.lazy.tsx
  • apps/web/.example.env
  • apps/search/README.md
  • apps/api/src/schemas/index.ts
  • apps/web/src/router.ts
  • apps/mobile/.example.env
  • apps/web/env.ts
  • apps/api/src/schemas/dictionary.schema.ts
  • apps/api/src/utils/error.ts
  • apps/api/src/routers/settings.ts
  • apps/api/src/schemas/flashcard.schema.ts
  • apps/api/src/clients/meilisearch.ts
  • apps/api/src/auth.ts
  • packages/schemas/package.json
  • packages/schemas/tsconfig.json
  • apps/mobile/package.json
  • apps/api/src/routers/dictionary.ts
  • apps/api/src/utils/index.ts
  • apps/api/scripts/migrate-meilisearch-to-user-db.ts
  • apps/search/example.env
  • apps/api/scripts/migrate-settings-decks-to-user-db.ts
  • apps/api/src/routers/tags.ts
  • apps/mobile/src/components/flashcards/FlashcardReview.tsx
  • apps/api/src/routers/decks.ts
  • apps/api/src/db/schema/settings.ts
  • docker-compose.prod.yaml
  • apps/api/scripts/README.md
  • apps/search/config.toml
  • apps/api/src/flashcard.json
  • apps/api/src/schemas/words.schema.ts
  • apps/web/Dockerfile
  • docker-compose.yaml
  • apps/api/src/db/schema/decks.ts
  • apps/search/fly.toml
  • apps/api/src/schema.json
  • apps/search/Dockerfile
  • .github/workflows/deploy-search.yml
  • scripts/update-indexes.sh
  • apps/api/src/utils/config.ts
  • apps/api/src/routers/flashcard.ts
  • packages/schemas/src/index.ts
  • apps/api/src/schemas/deck.schema.ts
  • apps/api/src/dictionary.json
  • .github/workflows/update-indexes.yml
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use TypeScript with strict typing across entire codebase
Write self-documenting code and avoid overuse of comments
Error handling with try/catch blocks and structured error types
Component naming: PascalCase for components, camelCase for functions/variables

Files:

  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx
  • apps/mobile/src/lib/schemas/dictionary.ts
  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/dictionary/add/TagsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/lib/schemas/dictionary.ts
  • apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
  • apps/web/src/lib/error.ts
  • apps/api/src/index.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Do not use any type unless absolutely necessary in TypeScript
Use DisplayError class for user-friendly error messages and Result<T, E> type for explicit error handling

Files:

  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx
  • apps/mobile/src/lib/schemas/dictionary.ts
  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/dictionary/add/TagsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/lib/schemas/dictionary.ts
  • apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
  • apps/web/src/lib/error.ts
  • apps/api/src/index.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,jsx}: React components use functional style with hooks
Prefer using jotai atoms over React Context for state management

Files:

  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx
  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/dictionary/add/TagsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
apps/mobile/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/mobile/**/*.{tsx,jsx}: Mobile app uses UniWind (Tailwind for React Native) with Tailwind CSS v4 for styling
Mobile app uses Expo with file-based routing (Expo Router)

Files:

  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
apps/web/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the cn() utility function for combining and conditionally applying Tailwind classes

Files:

  • apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx
  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/dictionary/add/TagsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
apps/web/**/*.{tsx,jsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Tanstack Router for client-side routing

Files:

  • apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx
  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/dictionary/add/TagsFormSection.tsx
  • apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/lib/schemas/dictionary.ts
  • apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx
  • apps/web/src/lib/error.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
**/*.{ts,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for database operations

Files:

  • apps/mobile/src/lib/schemas/dictionary.ts
  • apps/web/src/lib/schemas/dictionary.ts
  • apps/web/src/lib/error.ts
  • apps/api/src/index.ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.
📚 Learning: 2025-11-27T06:02:25.941Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.

Applied to files:

  • apps/web/src/components/features/decks/DeckDialogContent.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/api/src/index.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx,ts} : Web app uses Tanstack Router for client-side routing

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/api/src/index.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{tsx,jsx} : React components use functional style with hooks

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/mobile/**/*.{tsx,jsx} : Mobile app uses Expo with file-based routing (Expo Router)

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
📚 Learning: 2025-11-30T06:57:48.510Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/lib/db/operations/settings.ts:15-21
Timestamp: 2025-11-30T06:57:48.510Z
Learning: In apps/web/src/lib/db/operations/settings.ts, the settings table is intentionally implemented as a de-facto singleton without schema-level enforcement. Multiple rows can exist due to initialization races, but all operations (SELECT without WHERE, UPDATE without WHERE) treat rows as synchronized and interchangeable. This is a conscious design decision where code clarity could be improved but functional correctness is maintained.

Applied to files:

  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the `cn()` utility function for combining and conditionally applying Tailwind classes

Applied to files:

  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{ts,js} : Use Drizzle ORM for database operations

Applied to files:

  • CLAUDE.md
  • apps/api/README.md
📚 Learning: 2025-11-27T23:01:26.752Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: packages/drizzle-user-db-schemas/package.json:1-20
Timestamp: 2025-11-27T23:01:26.752Z
Learning: drizzle-zod0.5.1 has peer dependencies of "zod": "*" (accepts any version) and "drizzle-orm": ">=0.23.13". The zod wildcard peer dependency means any zod version is compatible with drizzle-zod0.5.1.

Applied to files:

  • apps/api/package.json
🧬 Code graph analysis (4)
apps/web/src/components/features/decks/DeckDialogContent.tsx (1)
apps/web/src/lib/db/operations/decks.ts (1)
  • deck (47-126)
apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx (3)
apps/mobile/src/app/(search)/settings.tsx (2)
  • updates (107-109)
  • SettingsScreen (88-284)
apps/web/src/lib/db/operations/settings.ts (1)
  • updates (41-87)
apps/web/src/components/features/settings/AdminSettingsCardSection.tsx (1)
  • data (64-66)
apps/web/src/lib/error.ts (2)
apps/api/src/routers/dictionary.ts (2)
  • req (147-215)
  • word (444-466)
packages/schemas/src/dictionary.ts (1)
  • Inflection (3-7)
apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx (1)
packages/schemas/src/dictionary.ts (1)
  • Inflection (3-7)
🪛 checkmake (0.2.2)
Makefile

[warning] 20-20: Missing required phony target "all"

(minphony)


[warning] 20-20: Missing required phony target "clean"

(minphony)


[warning] 20-20: Missing required phony target "test"

(minphony)

🪛 LanguageTool
apps/api/README.md

[grammar] ~99-~99: Use a hyphen to join words.
Context: ...ate a new user through the web app (sign up flow) 5. Manually convert the user t...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: bahar-marketing
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (39)
.gitignore (1)

11-12: Verify this change is intentional for the Meilisearch removal scope.

The addition of vite.config.ts.timestamp-* to ignore Vite build artifacts appears tangential to the main PR objective of removing Meilisearch and dual write logic. While the pattern itself is valid and won't cause issues, please confirm this is intentional cleanup discovered during refactoring or if it should be deferred to a separate chore PR.

apps/api/README.md (2)

81-91: Clarify and verify the instruction sequence.

The sequence starts dev servers before the local database. Typically the database should be running first to avoid connection errors when the API initializes. Verify that this order works as documented, or clarify whether users should start make local-db in a separate terminal before step 1.


1-143: Comprehensive update to local development documentation.

The README effectively documents the removal of Meilisearch and the shift to local-first development. The addition of the "Setting Up a New User" section is helpful and aligns well with the PR's objective to simplify local setup. The updates to the dev commands and removal of the deprecated migration script reference are clean and correct.

apps/api/.example.env (2)

29-29: SENTRY_ENV is already properly integrated into the configuration schema and actively consumed in the codebase (defined with enum validation in apps/api/src/utils/config.ts and used in apps/api/instrument.mjs for Sentry initialization). No action required.

Likely an incorrect or invalid review comment.


15-15: Resend API integration is properly implemented. Email sending is fully migrated to use Resend (mail.ts), configuration is validated (config.ts), and no orphaned SendGrid references remain. Additionally, SENTRY_ENV is correctly configured across the API and web applications.

apps/web/src/components/features/decks/DeckDialogContent.tsx (2)

114-130: LGTM! Clean migration from TRPC to React Query.

The deck mutations have been successfully migrated from TRPC to direct database operations via React Query. Both createDeck and updateDeck properly invalidate the decks list cache on success, ensuring UI consistency.

Based on learnings, the TRPC backend API calls were temporary and this migration completes the planned architecture change.


154-194: LGTM! Solid error handling and user feedback.

The form submission logic properly handles both create and update flows with:

  • Correct mutation call signatures
  • Comprehensive error handling with try/catch
  • User-friendly toast notifications in both success and error cases
  • Appropriate form reset behavior (only on create)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (2)

321-334: LGTM! Successful migration to server-first architecture.

The flashcard update mutation has been simplified from dual-write/optimistic updates to a single server-side mutation with comprehensive cache invalidation. The strategy correctly invalidates:

  • flashcardsTable.today - refreshes the current review queue
  • flashcardsTable.counts - updates queue counts
  • decksTable.list - refreshes deck statistics

This architectural change prioritizes data consistency over immediate UI feedback, which is appropriate for this use case.


361-407: LGTM! Well-structured flashcard grading logic.

The executeGrade function properly:

  • Calculates FSRS scheduling data from the current card state
  • Constructs the update payload with all necessary timestamp and state fields
  • Provides immediate UI feedback by updating local state before the mutation
  • Correctly includes updateFlashcard in the dependency array

The balance between immediate UI updates (lines 398-399) and server mutation ensures a responsive user experience while maintaining data consistency.

apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx (2)

47-54: Successfully migrated from TRPC to react-query mutations.

The migration correctly replaces the dual-write pattern with a single mutation that updates local state and invalidates queries. The pattern aligns with the broader refactoring objective.


119-135: LGTM — submission logic correctly simplified.

The form submission now uses a single mutation call instead of parallel remote/local updates, reducing complexity while maintaining functionality. The dependency array is correctly updated.

apps/api/drizzle/meta/_journal.json (1)

82-88: LGTM!

The journal entry is correctly formatted and consistent with existing migration entries.

apps/api/drizzle/meta/0011_snapshot.json (1)

1-488: LGTM!

The snapshot correctly represents the database schema after dropping the decks and settings tables. All remaining tables (accounts, sessions, users, verifications, databases, migrations) have proper:

  • Column definitions
  • Foreign key constraints with appropriate cascade behavior
  • Unique indexes where needed
apps/api/drizzle/0011_dazzling_jackal.sql (1)

1-2: Do not apply this migration. The decks and settings tables are still actively used throughout the application and will cause runtime failures.

The decks table is heavily integrated into both web and mobile apps with active CRUD operations in:

  • apps/web/src/lib/db/operations/decks.ts
  • apps/mobile/src/lib/db/operations/decks.ts

The settings table stores flashcard user preferences and is referenced in:

  • apps/web/src/components/features/flashcards/QuestionSide.tsx
  • apps/web/src/components/features/flashcards/AnswerSide.tsx
  • apps/web/src/components/features/settings/FlashcardSettingsCardSection.tsx

Both tables have active schema definitions in packages/drizzle-user-db-schemas/src/ and are still exported for client use. Applying this migration will break flashcard functionality across the application. If this is intended as part of a larger refactoring, ensure the migration is coordinated with corresponding code changes and a proper data migration strategy.

Also, add a trailing newline at the end of the file for POSIX compliance.

⛔ Skipped due to learnings
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: Decks are not considered critical data in the application, so data consistency issues with decks during rollback scenarios are acceptable.
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/lib/db/operations/settings.ts:15-21
Timestamp: 2025-11-30T06:57:48.510Z
Learning: In apps/web/src/lib/db/operations/settings.ts, the settings table is intentionally implemented as a de-facto singleton without schema-level enforcement. Multiple rows can exist due to initialization races, but all operations (SELECT without WHERE, UPDATE without WHERE) treat rows as synchronized and interchangeable. This is a conscious design decision where code clarity could be improved but functional correctness is maintained.
apps/mobile/src/lib/schemas/dictionary.ts (1)

78-78: LGTM! Standard formatting improvement.

Adding a trailing newline at the end of the file aligns with common coding standards.

apps/web/src/lib/error.ts (1)

2-2: LGTM! Import migration aligns with schema consolidation.

The import path change from @bahar/schemas to the local @/lib/schemas/dictionary aligns with the PR objective to consolidate schemas locally. The aliasing as DictionarySchema maintains backward compatibility within this file.

apps/web/package.json (2)

13-72: LGTM! Dependency removals align with Meilisearch removal.

The removal of @meilisearch/instant-meilisearch, react-instantsearch, and @bahar/schemas dependencies aligns perfectly with the PR objectives to remove Meilisearch infrastructure and consolidate schemas locally.


100-100: Update is compatible—no breaking changes detected in this codebase.

The project uses wrangler minimally via the wrangler pages dev command. The wrangler.toml configuration contains no deprecated v3 features (no legacy_assets, node_compat, getBindingsProxy, etc.) and there are no programmatic wrangler API calls in the codebase. The upgrade to v4.54.0 is safe.

apps/api/package.json (2)

12-12: LGTM! Simplified dev script and removed schema generation.

The dev script simplification from the complex schema generation pipeline to tsx watch src aligns with the removal of Meilisearch-related schema tooling. This makes the development workflow cleaner.


56-56: LGTM! Correct placement of type definitions.

Moving @types/cookie-parser to devDependencies is the correct approach, as TypeScript type definitions are only needed at compile-time and should not be in production dependencies.

apps/web/src/lib/schemas/dictionary.ts (2)

1-7: LGTM! Clean enum definition.

The Inflection enum is well-defined with appropriate linguistic terminology for Arabic morphology.


9-31: LGTM! Well-structured basic fields.

The basic field definitions are comprehensive with appropriate required/optional markings and validation constraints. The type enum values correctly represent Arabic grammatical categories.

apps/web/src/routes/_authorized-layout/_app-layout/dictionary/edit/$wordId.tsx (2)

31-31: LGTM! Import migration aligns with schema consolidation.

The import path change to the local dictionary schema is consistent with the broader schema migration in this PR.


119-166: LGTM! Delete flow appropriately simplified.

The deletion flow correctly navigates away after the deletion, so no cache invalidation is needed. The home page will refetch fresh data on mount.

apps/web/src/components/features/dictionary/add/MorphologyFormSection.tsx (1)

16-16: LGTM! Consistent schema import migration.

The import path change aligns with the schema consolidation across the codebase.

apps/mobile/src/app/(search)/(home)/add-word.tsx (1)

26-26: LGTM! Schema import migration completed.

The import path change to the local dictionary schema completes the migration from the external @bahar/schemas package. The mobile app now uses its local schema definition parallel to the web app.

apps/web/src/components/features/dictionary/add/IsmMorphologyCardSection.tsx (1)

33-33: LGTM: Import path updated to local dictionary schema.

The import source change from @bahar/schemas to @/lib/schemas/dictionary is part of the broader refactor to centralize dictionary schemas locally. This aligns with the PR's objective to remove external dependencies and consolidate schema definitions.

apps/web/src/components/features/dictionary/add/VerbMorphologyCardSection.tsx (1)

21-21: LGTM: Import path updated consistently.

The FormSchema import has been correctly updated to reference the local dictionary schema module, consistent with the broader refactor across dictionary components.

apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx (1)

27-27: LGTM: Mobile app aligned with schema migration.

The import path update mirrors the web app changes, ensuring consistent schema usage across platforms.

apps/web/src/components/features/dictionary/add/CategoryFormSection.tsx (1)

30-30: LGTM: Schema import updated correctly.

The FormSchema import path change is consistent with the project-wide migration to local dictionary schemas.

apps/web/src/components/features/dictionary/add/AdditionalDetailsFormSection.tsx (1)

27-27: LGTM: Import path migrated successfully.

The update to reference @/lib/schemas/dictionary maintains consistency with other form sections in the dictionary feature.

apps/web/src/components/features/dictionary/add/TagsFormSection.tsx (1)

12-12: LGTM: Import source updated appropriately.

The FormSchema import now correctly references the local dictionary schema module.

apps/web/src/components/features/dictionary/add/BasicDetailsFormSection.tsx (1)

13-13: LGTM: Schema import path updated.

The FormSchema import change completes the migration pattern across all dictionary form sections.

apps/api/src/index.ts (2)

9-9: LGTM: Import streamlined after router removal.

The import has been simplified to only include getAllowedDomains, which is appropriate given the removal of the schema generation functionality.


31-34: All client-side references to removed routers have been properly updated.

The removal of dictionary, flashcard, tags, settings, and decks routers has been fully migrated in the client code. For example, DeckDialogContent.tsx now uses decksTable.create.mutation and decksTable.update.mutation (local database operations) instead of the removed trpc.decks endpoints. No remaining references to the removed routers were found in the codebase.

CLAUDE.md (2)

107-141: Documentation updates align with architectural simplification.

The removal of @bahar/schemas from the shared packages list and the elimination of the dual-write note from the Writing Data section correctly reflect the PR's architectural changes. The documentation now accurately describes the simplified local-first data flow without the previous dual-write complexity.


52-75: Clear local development instructions with accurate multi-terminal workflow.

The updated documentation provides a straightforward terminal-based development setup with correctly documented commands and ports. The API runs on port 3000 and web app on port 4000 as configured in their respective environment schemas. The local database (turso dev) and Drizzle Studio use their respective tool defaults (8080 and 4983), and all specified commands exist in the repository. The setup improves developer onboarding effectively.

apps/web/src/routes/_authorized-layout/_app-layout/dictionary/add/route.lazy.tsx (2)

30-30: The FormSchema import path change is correct and complete.

The schema exists at the new location and is properly exported with comprehensive Zod validation. No lingering imports from the old @bahar/schemas package remain in the codebase.


155-155: The addDictionaryEntry hook properly consolidates all necessary persistence operations.

The removal of the dual-write pattern is complete and safe. The hook now handles all required data consistency:

  • Local word storage via addWord mutation
  • Automatic flashcard creation (forward and reverse)
  • Search index updates via Orama
  • Search state cleanup

No downstream code in the web app depends on the removed backend calls, and all data persistence is properly bundled in the single async call.

Comment thread apps/api/README.md Outdated
Comment thread Makefile
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 (5)
apps/web/src/lib/db/operations/flashcards.ts (3)

586-649: Critical: learning_steps not persisted in clearBacklog UPDATE.

The learning_steps field is read from the database (Line 603) and included in the FSRS Card, but the UPDATE statement (lines 612-641) doesn't persist it back. If the FSRS scheduler modifies learning_steps during the repeat operation (Line 609), those changes will be lost.

🔎 Proposed fix to include learning_steps in UPDATE
           await db
             .prepare(
               `UPDATE flashcards SET
                 due = ?,
                 due_timestamp_ms = ?,
                 last_review = ?,
                 last_review_timestamp_ms = ?,
                 state = ?,
                 stability = ?,
                 difficulty = ?,
                 reps = ?,
                 lapses = ?,
                 elapsed_days = ?,
-                scheduled_days = ?
+                scheduled_days = ?,
+                learning_steps = ?
               WHERE id = ?`,
             )
             .run([
               newCard.due.toISOString(),
               newCard.due.getTime(),
               newCard.last_review?.toISOString() ?? null,
               newCard.last_review?.getTime() ?? null,
               newCard.state,
               newCard.stability,
               newCard.difficulty,
               newCard.reps,
               newCard.lapses,
               newCard.elapsed_days,
               newCard.scheduled_days,
+              newCard.learning_steps,
               rawCard.id,
             ]);

169-233: Major: learning_steps missing from create mutation.

The create mutation doesn't include learning_steps in the INSERT statement (lines 186-209), even though the schema defines it and the InsertFlashcard type includes it. While the database default of 0 prevents errors, callers cannot set an initial learning_steps value.

🔎 Proposed fix to add learning_steps to INSERT
         await db
           .prepare(
             `INSERT INTO flashcards (
-          id, dictionary_entry_id, difficulty, due, due_timestamp_ms, elapsed_days,
-          lapses, last_review, last_review_timestamp_ms, reps, scheduled_days, stability, state, direction, is_hidden
-        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+          id, dictionary_entry_id, difficulty, due, due_timestamp_ms, elapsed_days,
+          lapses, last_review, last_review_timestamp_ms, learning_steps, reps, scheduled_days, stability, state, direction, is_hidden
+        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
           )
           .run([
             id,
             flashcard.dictionary_entry_id,
             flashcard.difficulty,
             flashcard.due,
             dueDateMs,
             flashcard.elapsed_days,
             flashcard.lapses,
             flashcard.last_review,
             lastReviewDateMs,
+            flashcard.learning_steps ?? 0,
             flashcard.reps,
             flashcard.scheduled_days,
             flashcard.stability,
             flashcard.state,
             flashcard.direction,
             0,
           ]);

349-411: Major: learning_steps not reset in reset mutation.

The reset mutation resets all FSRS fields except learning_steps (lines 363-383). For a complete reset, learning_steps should also be set back to 0.

🔎 Proposed fix to reset learning_steps
         await db
           .prepare(
             `UPDATE flashcards
            SET state = ?, difficulty = ?, stability = ?, reps = ?, lapses = ?,
                elapsed_days = ?, scheduled_days = ?, last_review = NULL,
-               last_review_timestamp_ms = NULL, due = ?, due_timestamp_ms = ?
+               last_review_timestamp_ms = NULL, learning_steps = ?, due = ?, due_timestamp_ms = ?
            WHERE dictionary_entry_id = ? AND direction = ?;`,
           )
           .run([
             FlashcardState.NEW,
             0,
             0,
             0,
             0,
             0,
             0,
+            0,
             dueDate,
             dueDateMs,
             dictionary_entry_id,
             direction,
           ]);
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (2)

321-334: Add error handling to prevent data inconsistency.

The mutation lacks an onError handler. Since the card is removed from the local UI state (line 386) before the mutation completes, a failure would result in the card disappearing from the review queue without being updated in the database.

🔎 Recommended fix: Add error handler with rollback
 const { mutateAsync: updateFlashcard } = useMutation({
   mutationFn: flashcardsTable.update.mutation,
   onSuccess: () => {
     queryClient.invalidateQueries({
       queryKey: flashcardsTable.today.cacheOptions.queryKey,
     });
     queryClient.invalidateQueries({
       queryKey: flashcardsTable.counts.cacheOptions.queryKey,
     });
     queryClient.invalidateQueries({
       queryKey: decksTable.list.cacheOptions.queryKey,
     });
   },
+  onError: (error) => {
+    // Rollback: re-add the card to the queue on error
+    if (currentCard) {
+      setCards((prev) => [currentCard, ...prev]);
+    }
+    // Show error to user (consider using DisplayError class per coding guidelines)
+    console.error('Failed to update flashcard:', error);
+  },
 });

Note: Consider using the DisplayError class mentioned in the coding guidelines for user-friendly error messages.


371-391: Add learning_steps to the update payload — it's computed by FSRS and must be persisted.

The learning_steps field is part of the FSRS Card type and tracks the current step in the card's learning/relearning sequence. The f.repeat() algorithm computes an updated value for this field, but it's not included in localUpdates, so it won't be persisted to the database. This causes subsequent reviews to use stale learning_steps values, breaking the scheduler's short-term learning interval logic.

Add to line 383:

learning_steps: selectedCard.learning_steps,
🧹 Nitpick comments (2)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (1)

371-383: Consider adding explicit typing for localUpdates.

The localUpdates object could benefit from an explicit type annotation to ensure type safety and catch any schema mismatches at compile time, aligning with the coding guideline for strict typing.

Example with explicit typing
const localUpdates: Parameters<typeof flashcardsTable.update.mutation>[0]['updates'] = {
  due: selectedCard.due.toISOString(),
  due_timestamp_ms: dueTimestampMs,
  last_review: selectedCard?.last_review?.toISOString() ?? null,
  last_review_timestamp_ms: lastReviewTimestampMs,
  state: selectedCard.state,
  stability: selectedCard.stability,
  difficulty: selectedCard.difficulty,
  reps: selectedCard.reps,
  lapses: selectedCard.lapses,
  elapsed_days: selectedCard.elapsed_days,
  scheduled_days: selectedCard.scheduled_days,
};

This ensures compile-time verification that the updates match the expected schema.

As per coding guidelines for strict typing.

apps/mobile/src/utils/zod.ts (1)

1-46: Zod v4 migration implemented correctly.

The error map migration follows the Zod v4 API correctly:

  • Using z.core.$ZodErrorMap type
  • String-based issue codes ("invalid_type", "invalid_format", etc.)
  • z.config({ customError: errorMap }) for global configuration
  • Returning undefined for unhandled cases

This error map is nearly identical to apps/web/src/lib/zod.ts. Consider extracting to a shared package (e.g., @bahar/zod-utils) to avoid drift between mobile and web implementations.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e9aa1ab and 0bef205.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • apps/api/drizzle/0012_conscious_rhodey.sql
  • apps/api/drizzle/meta/0012_snapshot.json
  • apps/api/drizzle/meta/_journal.json
  • apps/api/package.json
  • apps/api/src/db/schema/auth.ts
  • apps/api/src/utils/config.ts
  • apps/mobile/package.json
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/mobile/src/utils/zod.ts
  • apps/web/env.ts
  • apps/web/package.json
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/env.d.ts
  • apps/web/src/lib/db/operations/flashcards.ts
  • apps/web/src/lib/error.ts
  • apps/web/src/lib/trpc.ts
  • apps/web/src/lib/zod.ts
  • package.json
  • packages/db-operations/package.json
  • packages/drizzle-user-db-schemas/drizzle/0001_even_kitty_pryde.sql
  • packages/drizzle-user-db-schemas/drizzle/meta/0001_snapshot.json
  • packages/drizzle-user-db-schemas/drizzle/meta/_journal.json
  • packages/drizzle-user-db-schemas/package.json
  • packages/drizzle-user-db-schemas/src/flashcards.ts
  • packages/fsrs/package.json
  • packages/fsrs/src/index.ts
💤 Files with no reviewable changes (3)
  • package.json
  • apps/web/env.ts
  • apps/web/src/env.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mobile/src/app/(search)/(home)/add-word.tsx
  • apps/api/src/utils/config.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use TypeScript with strict typing across entire codebase
Write self-documenting code and avoid overuse of comments
Error handling with try/catch blocks and structured error types
Component naming: PascalCase for components, camelCase for functions/variables

Files:

  • packages/drizzle-user-db-schemas/src/flashcards.ts
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/web/src/lib/trpc.ts
  • packages/fsrs/src/index.ts
  • apps/web/src/lib/zod.ts
  • apps/mobile/src/utils/zod.ts
  • apps/web/src/lib/db/operations/flashcards.ts
  • apps/api/src/db/schema/auth.ts
  • apps/web/src/lib/error.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Do not use any type unless absolutely necessary in TypeScript
Use DisplayError class for user-friendly error messages and Result<T, E> type for explicit error handling

Files:

  • packages/drizzle-user-db-schemas/src/flashcards.ts
  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
  • apps/web/src/lib/trpc.ts
  • packages/fsrs/src/index.ts
  • apps/web/src/lib/zod.ts
  • apps/mobile/src/utils/zod.ts
  • apps/web/src/lib/db/operations/flashcards.ts
  • apps/api/src/db/schema/auth.ts
  • apps/web/src/lib/error.ts
**/*.{ts,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for database operations

Files:

  • packages/drizzle-user-db-schemas/src/flashcards.ts
  • apps/web/src/lib/trpc.ts
  • packages/fsrs/src/index.ts
  • apps/web/src/lib/zod.ts
  • apps/mobile/src/utils/zod.ts
  • apps/web/src/lib/db/operations/flashcards.ts
  • apps/api/src/db/schema/auth.ts
  • apps/web/src/lib/error.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,jsx}: React components use functional style with hooks
Prefer using jotai atoms over React Context for state management

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
apps/web/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the cn() utility function for combining and conditionally applying Tailwind classes

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Tanstack Router for client-side routing

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/lib/trpc.ts
  • apps/web/src/lib/zod.ts
  • apps/web/src/lib/db/operations/flashcards.ts
  • apps/web/src/lib/error.ts
apps/mobile/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/mobile/**/*.{tsx,jsx}: Mobile app uses UniWind (Tailwind for React Native) with Tailwind CSS v4 for styling
Mobile app uses Expo with file-based routing (Expo Router)

Files:

  • apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx
🧠 Learnings (10)
📓 Common learnings
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.
📚 Learning: 2025-11-27T23:01:26.752Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: packages/drizzle-user-db-schemas/package.json:1-20
Timestamp: 2025-11-27T23:01:26.752Z
Learning: drizzle-zod0.5.1 has peer dependencies of "zod": "*" (accepts any version) and "drizzle-orm": ">=0.23.13". The zod wildcard peer dependency means any zod version is compatible with drizzle-zod0.5.1.

Applied to files:

  • packages/fsrs/package.json
  • packages/db-operations/package.json
  • apps/web/package.json
  • packages/drizzle-user-db-schemas/package.json
  • apps/api/package.json
  • apps/mobile/package.json
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{ts,js} : Use Drizzle ORM for database operations

Applied to files:

  • packages/fsrs/package.json
  • packages/db-operations/package.json
  • packages/drizzle-user-db-schemas/package.json
  • apps/api/src/db/schema/auth.ts
📚 Learning: 2025-11-27T06:02:25.941Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/web/src/lib/db/operations/flashcards.ts
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{tsx,jsx} : React components use functional style with hooks

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx,ts} : Web app uses Tanstack Router for client-side routing

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/mobile/**/*.{tsx,jsx} : Mobile app uses Expo with file-based routing (Expo Router)

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer.tsx
  • apps/mobile/package.json
📚 Learning: 2025-11-30T06:57:48.510Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/lib/db/operations/settings.ts:15-21
Timestamp: 2025-11-30T06:57:48.510Z
Learning: In apps/web/src/lib/db/operations/settings.ts, the settings table is intentionally implemented as a de-facto singleton without schema-level enforcement. Multiple rows can exist due to initialization races, but all operations (SELECT without WHERE, UPDATE without WHERE) treat rows as synchronized and interchangeable. This is a conscious design decision where code clarity could be improved but functional correctness is maintained.

Applied to files:

  • apps/api/src/db/schema/auth.ts
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/mobile/**/*.{tsx,jsx} : Mobile app uses UniWind (Tailwind for React Native) with Tailwind CSS v4 for styling

Applied to files:

  • apps/mobile/package.json
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the `cn()` utility function for combining and conditionally applying Tailwind classes

Applied to files:

  • apps/mobile/package.json
🧬 Code graph analysis (7)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (1)
apps/api/src/routers/flashcard.ts (2)
  • c (306-323)
  • ctx (144-171)
packages/fsrs/src/index.ts (1)
apps/api/src/routers/flashcard.ts (3)
  • FlashcardState (13-18)
  • c (306-323)
  • flashcard (75-105)
apps/web/src/lib/zod.ts (2)
apps/mobile/src/utils/zod.ts (1)
  • errorMap (4-44)
apps/marketing/src/i18n/index.ts (1)
  • t (11-13)
apps/mobile/src/utils/zod.ts (1)
apps/marketing/src/i18n/index.ts (1)
  • t (11-13)
apps/web/src/lib/db/operations/flashcards.ts (3)
apps/web/src/lib/db/operations/settings.ts (1)
  • updates (41-87)
apps/web/src/lib/db/import/v1/index.ts (1)
  • createFlashcardStatement (82-136)
apps/api/src/routers/flashcard.ts (1)
  • ctx (144-171)
apps/mobile/package.json (1)
apps/mobile/metro.config.js (1)
  • withMonorepoPaths (35-49)
apps/web/src/lib/error.ts (2)
apps/api/src/utils/error.ts (1)
  • ImportErrorCode (16-19)
apps/api/src/routers/dictionary.ts (1)
  • req (147-215)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: bahar-marketing
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (33)
apps/api/drizzle/meta/_journal.json (1)

81-97: LGTM!

The journal entries for migrations 0011 and 0012 are properly sequenced and formatted. This is standard Drizzle ORM migration tracking metadata.

apps/api/drizzle/meta/0012_snapshot.json (2)

450-464: Verify the intended onDelete behavior for the databases foreign key.

The databases.user_id FK uses onDelete: "no action" while similar FKs on accounts and sessions use onDelete: "cascade". If a user is deleted, their database record will become orphaned rather than being cleaned up.

If this is intentional (e.g., to preserve database resources for manual cleanup), this is fine. Otherwise, consider aligning with cascade.


1-519: Schema snapshot looks well-structured.

The snapshot properly reflects the schema state after migration 0012 with consistent timestamp handling, appropriate foreign keys, and sensible indexing. The removal of decks/settings tables aligns with the PR objective of removing Meilisearch dual-write logic.

apps/api/drizzle/0012_conscious_rhodey.sql (2)

1-13: Index drop/recreate pattern is correct for SQLite.

Dropping indexes before column alterations and recreating them afterward is the appropriate approach for SQLite, which has limited ALTER TABLE support. The indexes are recreated with the same definitions before subsequent alterations.


14-20: Column alterations look correct.

The timestamp columns are standardized to use millisecond-precision Unix timestamps, and the boolean-like columns (email_verified, banned) properly use SQLite's integer representation with appropriate nullability.

apps/api/src/db/schema/auth.ts (4)

1-2: LGTM!

Imports are well-organized and all are utilized in the schema definitions below.


4-23: LGTM!

The users table is well-structured with appropriate defaults for notNull columns and consistent use of timestamp_ms mode for date fields.


77-93: LGTM!

The verifications table is correctly structured with appropriate defaults for both createdAt and updatedAt, and an index on identifier for efficient lookups.


95-112: LGTM!

Relations are correctly wired with proper field references matching the foreign key constraints defined in the tables.

apps/mobile/src/app/(search)/(home)/edit-word/[id].tsx (2)

27-27: The schema migration from @bahar/schemas to the local schema is complete. FormSchema is properly exported from @/lib/schemas/dictionary, and no remaining references to the old import path exist in the codebase.


38-38: The Zod v4 API z.config({ customError: errorMap }) is correct. The parameter name, global application, and syntax align with Zod v4 documentation. No changes needed.

packages/drizzle-user-db-schemas/package.json (2)

13-14: Compatibility verified — drizzle-zod@0.8.3 officially supports zod@4.0.17.

All package versions exist and are stable. The peer dependency for drizzle-zod@0.8.3 explicitly supports zod: '^3.25.0 || ^4.0.0' and requires drizzle-orm: '>=0.36.0', both of which are satisfied by your versions (zod@4.0.17 and drizzle-orm@0.45.1).


18-18: No action needed. The drizzle-kit@0.31.8 update is compatible with drizzle-orm@0.45.1. Both packages are released in aligned cycles (early-mid December 2025) and designed to work together. drizzle-kit declares no conflicting peer dependencies.

apps/web/src/lib/db/operations/flashcards.ts (1)

272-278: LGTM! learning_steps field properly integrated.

The learning_steps field is correctly added to the update logic following the same pattern as other FSRS fields.

packages/drizzle-user-db-schemas/drizzle/0001_even_kitty_pryde.sql (1)

1-1: LGTM! Migration correctly adds learning_steps field.

The migration properly adds the learning_steps column with an integer type and default value of 0, consistent with the schema definition.

packages/drizzle-user-db-schemas/drizzle/meta/_journal.json (1)

11-18: LGTM! Journal entry correctly added.

The new migration journal entry is properly structured and tracks the 0001_even_kitty_pryde migration.

packages/drizzle-user-db-schemas/src/flashcards.ts (1)

33-33: LGTM! Schema correctly defines learning_steps field.

The learning_steps field is properly added to the flashcards schema with the correct type and default value, consistent with the migration.

packages/drizzle-user-db-schemas/drizzle/meta/0001_snapshot.json (1)

215-222: LGTM! Snapshot correctly includes learning_steps.

The snapshot metadata accurately reflects the learning_steps column definition with the correct type and default value.

packages/fsrs/src/index.ts (3)

56-80: LGTM! fromFsrsCard correctly includes learning_steps.

The learning_steps field is properly mapped from the FSRS Card back to the database format (Line 73), consistent with the schema changes.


118-159: LGTM! gradeFlashcard correctly propagates learning_steps.

The learning_steps field is properly included in both the return type (Line 136) and the returned object (Line 157), ensuring it's preserved through the grading flow.


33-51: No action required—learning_steps field is valid in ts-fsrs@5.2.3.

The Card type from ts-fsrs@5.2.3 includes the learning_steps field, which tracks the current step index during Learning or Relearning stages. The code correctly uses this field.

apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (2)

71-71: LGTM: learning_steps field addition.

The learning_steps field is properly added with a sensible default value, following the same pattern as other optional FSRS fields.


393-393: LGTM: Dependency array is correct.

The dependency array properly includes all values used within executeGrade, and updateFlashcard from useMutation is stable.

packages/fsrs/package.json (1)

12-16: LGTM!

The ts-fsrs v5 upgrade is consistent with the same change in apps/mobile and apps/web, ensuring version alignment across the monorepo.

apps/api/package.json (2)

12-14: Dev workflow simplified appropriately.

The removal of gen-schema from the dev script aligns with the PR objective of removing schema generation tooling tied to Meilisearch.


38-45: Jose v6 upgrade appears safe for this codebase.

The upgrade to jose v6 is a major version bump with breaking changes (ESM-only, Node 18.x dropped, API changes). However, the codebase is already ESM-based ("type": "module"), uses Node 20.11.1 (above v6's minimum), and only uses decodeJwt for simple JWT decoding in apps/api/src/utils/index.ts. The decodeJwt function remains compatible and the project doesn't use key generation, imports, or remote JWKS fetching—the areas most affected by jose v6 breaking changes.

apps/web/src/lib/zod.ts (1)

1-48: Zod v4 migration implemented correctly.

The implementation correctly follows Zod v4 conventions and exports the configured z instance for use throughout the web app.

As noted in the mobile review, this error map is nearly identical to apps/mobile/src/utils/zod.ts. Future consolidation into a shared package could reduce maintenance burden.

apps/web/src/lib/error.ts (2)

2-3: Import changes align with PR objectives.

The DictionarySchema import moved from @bahar/schemas to local path, consistent with the removal of the shared schemas package. The zod/v4 import aligns with the monorepo-wide migration.


105-136: Zod v4 error structure correctly implemented.

The changes correctly adapt to Zod v4's error codes and properties:

  • invalid_value handles enum/literal mismatches with err.values
  • invalid_format replaces invalid_string for string-format validation with err.format
  • err.input is used for type validation checks (replacing v3's err.received)
  • Standard properties (err.expected, err.minimum, err.maximum) map correctly to error types
packages/db-operations/package.json (1)

11-17: Zod v4 upgrade is consistent across the monorepo.

The pinned version (4.0.17) ensures consistency in both peerDependencies and devDependencies. All zod references in the monorepo are aligned to v4.0.17, including the consuming package apps/mobile.

apps/web/package.json (2)

60-71: drizzle-orm and ts-fsrs upgrades are safe and non-breaking.

The drizzle-orm upgrade to 0.45.1 contains only bug fixes and improvements with no breaking changes. The schemas throughout the codebase using drizzle-orm/sqlite-core and drizzle-zod remain fully compatible. The ts-fsrs upgrade to v5 is aligned with the broader refactoring in this PR.


99-99: Wrangler v4 upgrade is compatible with this project's deployment configuration.

The minimal wrangler.toml uses no deprecated features (legacy assets, Workers Sites, legacy_env, getBindingsProxy) that would be affected by the v4 upgrade. Node.js v20.11.1 satisfies the v4 requirement (v18+). The Cloudflare Pages deployment is configured correctly with pages_build_output_dir = "dist" and uses the correct local dev command wrangler pages dev.

apps/mobile/package.json (1)

71-73: Major version upgrade requires compatibility verification before merge.

The ts-fsrs v4→v5 upgrade is a significant version bump. The project doesn't publish a dedicated changelog, so breaking changes need to be verified by:

  • Reviewing fsrs-rs v5 release notes for algorithm/API changes
  • Checking actual usage of ts-fsrs in the codebase for parameter/signature compatibility
  • Verifying Node.js runtime requirements haven't changed in ways that conflict with the project's CI/deployment environment

The zod v3→v4 migration should also be verified for compatibility across the monorepo.

Comment thread apps/api/src/db/schema/auth.ts
Comment thread apps/api/src/db/schema/auth.ts
Comment thread apps/web/src/lib/trpc.ts
Comment thread packages/drizzle-user-db-schemas/package.json
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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (1)

199-211: Add error handling to prevent misleading success feedback.

The delete operation lacks error handling. If the mutation fails, the success toast will still display, misleading users into thinking the deletion succeeded.

🔎 Proposed fix
 <DropdownMenuItem
   onClick={async () => {
-    await deleteDeck({ id: deck.id });
-
-    toast({
-      title: t`Deck successfully deleted!`,
-      description: t`The deck "${deck.name}" has been deleted.`,
-    });
+    try {
+      await deleteDeck({ id: deck.id });
+      
+      toast({
+        title: t`Deck successfully deleted!`,
+        description: t`The deck "${deck.name}" has been deleted.`,
+      });
+    } catch (error) {
+      toast({
+        title: t`Failed to delete deck`,
+        description: t`An error occurred while deleting "${deck.name}". Please try again.`,
+        variant: "destructive",
+      });
+    }
   }}
   className="cursor-pointer"
 >
🧹 Nitpick comments (5)
apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx (1)

10-10: Import path is correct; consider using barrel export for better maintainability.

The FlashcardDrawer import has been properly updated and the new file structure exists with correct exports. However, both files importing FlashcardDrawer use the direct path @/components/features/flashcards/FlashcardDrawer/FlashcardDrawer when a barrel export is available. Consider importing from @/components/features/flashcards/FlashcardDrawer instead to align with typical module organization patterns and improve maintainability.

apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx (1)

8-19: Add exhaustive return handling for type safety.

The getTranslatedType function doesn't explicitly handle all possible paths. If entryType is null or doesn't match any case, the function returns undefined, which could cause issues when rendered in JSX at line 36.

🔎 Suggested improvements

Add an explicit return type and handle the null case:

-const getTranslatedType = (entryType: SelectDictionaryEntry["type"]) => {
+const getTranslatedType = (
+  entryType: SelectDictionaryEntry["type"]
+): string | null => {
+  if (!entryType) return null;
+  
   switch (entryType) {
     case "ism":
       return t`Noun`;
     case "fi'l":
       return t`Verb`;
     case "harf":
       return t`Preposition`;
     case "expression":
       return t`Expression`;
+    default:
+      return null;
   }
 };

Then update line 31 to check for null:

-      {!!currentCard.dictionary_entry.type && (
+      {!!currentCard.dictionary_entry.type && getTranslatedType(currentCard.dictionary_entry.type) && (
         <Badge
           variant="secondary"
           className="w-max bg-primary/10 text-primary border-primary/20 hover:bg-primary/15 transition-colors"
         >
           {getTranslatedType(currentCard.dictionary_entry.type)}
         </Badge>
       )}
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)

36-36: Fix typo in comment.

"represetnation" should be "representation".

🔎 Proposed fix
 /**
- * Converts the database represetnation of a flashcard to the format
+ * Converts the database representation of a flashcard to the format
  * expected by the fsrs library.
  */
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx (1)

25-84: Move feedbackConfig outside component to avoid recreation on every render.

The feedbackConfig object is currently defined inside the component body, causing it to be recreated on every render. Since this configuration is static, it should be moved outside the component or memoized.

🔎 Proposed fix

Move the configuration outside the component:

+const feedbackConfig = {
+  [Rating.Again]: {
+    icon: RotateCcw,
+    color: "text-muted-foreground",
+    bgColor: "bg-muted/20",
+    animation: {
+      initial: { scale: 0, rotate: 0 },
+      animate: {
+        scale: [0, 1.2, 1],
+        rotate: [0, -10, 10, -10, 0],
+      },
+      transition: { duration: 0.5 },
+    },
+  },
+  [Rating.Hard]: {
+    icon: Brain,
+    color: "text-orange-500",
+    bgColor: "bg-orange-500/10",
+    animation: {
+      initial: { scale: 0 },
+      animate: {
+        scale: [0, 1.3, 1],
+        opacity: [0, 1, 1],
+      },
+      transition: { duration: 0.5, ease: "easeOut" },
+    },
+  },
+  [Rating.Good]: {
+    icon: ThumbsUp,
+    color: "text-primary",
+    bgColor: "bg-primary/10",
+    animation: {
+      initial: { scale: 0, y: 10, opacity: 0 },
+      animate: {
+        scale: 1,
+        y: 0,
+        opacity: 1,
+      },
+      transition: {
+        duration: 0.4,
+        type: "spring",
+        stiffness: 200,
+        damping: 15,
+      },
+    },
+  },
+  [Rating.Easy]: {
+    icon: Zap,
+    color: "text-green-500",
+    bgColor: "bg-green-500/10",
+    animation: {
+      initial: { scale: 0, rotate: -20 },
+      animate: {
+        scale: [0, 1.4, 1],
+        rotate: [-20, 10, 0],
+      },
+      transition: { duration: 0.4, ease: "easeOut" },
+    },
+  },
+};
+
 export const GradeFeedback: FC<{
   grade: Grade | null;
   onComplete: () => void;
 }> = ({ grade, onComplete }) => {
   useEffect(() => {
     if (grade === null) return;

     const timer = setTimeout(onComplete, 600);
     return () => clearTimeout(timer);
   }, [grade, onComplete]);

   if (grade === null) return null;

-  const feedbackConfig = {
-    [Rating.Again]: {
-      icon: RotateCcw,
-      color: "text-muted-foreground",
-      bgColor: "bg-muted/20",
-      animation: {
-        initial: { scale: 0, rotate: 0 },
-        animate: {
-          scale: [0, 1.2, 1],
-          rotate: [0, -10, 10, -10, 0],
-        },
-        transition: { duration: 0.5 },
-      },
-    },
-    [Rating.Hard]: {
-      icon: Brain,
-      color: "text-orange-500",
-      bgColor: "bg-orange-500/10",
-      animation: {
-        initial: { scale: 0 },
-        animate: {
-          scale: [0, 1.3, 1],
-          opacity: [0, 1, 1],
-        },
-        transition: { duration: 0.5, ease: "easeOut" },
-      },
-    },
-    [Rating.Good]: {
-      icon: ThumbsUp,
-      color: "text-primary",
-      bgColor: "bg-primary/10",
-      animation: {
-        initial: { scale: 0, y: 10, opacity: 0 },
-        animate: {
-          scale: 1,
-          y: 0,
-          opacity: 1,
-        },
-        transition: {
-          duration: 0.4,
-          type: "spring",
-          stiffness: 200,
-          damping: 15,
-        },
-      },
-    },
-    [Rating.Easy]: {
-      icon: Zap,
-      color: "text-green-500",
-      bgColor: "bg-green-500/10",
-      animation: {
-        initial: { scale: 0, rotate: -20 },
-        animate: {
-          scale: [0, 1.4, 1],
-          rotate: [-20, 10, 0],
-        },
-        transition: { duration: 0.4, ease: "easeOut" },
-      },
-    },
-  };

   const config = feedbackConfig[grade];
   const Icon = config.icon;
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (1)

67-68: Consider using actual locale from i18n for better flexibility.

The locale is currently hardcoded based on text direction (rtl → "ar-u-nu-arab", ltr → "en"). While this works for the current use case, using the actual locale from i18n.locale would be more flexible and accurate for date formatting.

🔎 Proposed improvement
 const dir = useDir();
-const locale = dir === "rtl" ? "ar-u-nu-arab" : "en";
+const { i18n } = useLingui();
+const locale = i18n.locale;

This would require adding the import:

+import { useLingui } from "@lingui/react";

Note: You may need to verify that i18n.locale returns values compatible with Intl formatting functions used in formatInterval.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 008fe26 and 445680e.

📒 Files selected for processing (8)
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use TypeScript with strict typing across entire codebase
Write self-documenting code and avoid overuse of comments
Error handling with try/catch blocks and structured error types
Component naming: PascalCase for components, camelCase for functions/variables

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Do not use any type unless absolutely necessary in TypeScript
Use DisplayError class for user-friendly error messages and Result<T, E> type for explicit error handling

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,jsx}: React components use functional style with hooks
Prefer using jotai atoms over React Context for state management

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the cn() utility function for combining and conditionally applying Tailwind classes

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Tanstack Router for client-side routing

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
**/*.{ts,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for database operations

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.
📚 Learning: 2025-11-27T06:02:25.941Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx,ts} : Web app uses Tanstack Router for client-side routing

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{tsx,jsx} : React components use functional style with hooks

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the `cn()` utility function for combining and conditionally applying Tailwind classes

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/mobile/**/*.{tsx,jsx} : Mobile app uses Expo with file-based routing (Expo Router)

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/mobile/**/*.{tsx,jsx} : Mobile app uses UniWind (Tailwind for React Native) with Tailwind CSS v4 for styling

Applied to files:

  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
🧬 Code graph analysis (7)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (2)
apps/mobile/src/app/(search)/decks.tsx (2)
  • deleteMutation (247-247)
  • deck (241-250)
apps/web/src/components/features/decks/DeckDialogContent.tsx (4)
  • queryClient (119-123)
  • deck (111-382)
  • queryClient (135-139)
  • queryClient (126-130)
apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx (2)
apps/marketing/src/i18n/index.ts (1)
  • t (11-13)
apps/web/src/components/ui/badge.tsx (3)
  • Badge (36-36)
  • Badge (30-34)
  • BadgeProps (26-28)
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (4)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (2)
  • flashcard (59-76)
  • dir (268-872)
apps/api/src/routers/flashcard.ts (2)
  • c (306-323)
  • flashcard (75-105)
apps/web/src/lib/db/import/v1/index.ts (1)
  • createFlashcardStatement (82-136)
apps/web/src/lib/db/operations/flashcards.ts (2)
  • days (37-37)
  • flashcard (170-229)
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx (3)
packages/fsrs/src/index.ts (2)
  • Grade (28-28)
  • Rating (26-26)
packages/design-system/src/utils.ts (1)
  • cn (4-6)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (9)
  • useEffect (104-229)
  • gradeCard (723-723)
  • grade (456-464)
  • gradeCard (787-787)
  • grade (402-451)
  • gradeCard (755-755)
  • gradeCard (819-819)
  • grade (105-110)
  • fsrs (394-394)
apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx (1)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (5)
  • tag (249-263)
  • FlashcardDrawerProps (91-97)
  • flashcard (59-76)
  • motion (233-266)
  • data (386-390)
apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx (1)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (6)
  • fsrs (394-394)
  • FlashcardDrawerProps (91-97)
  • tag (249-263)
  • motion (233-266)
  • flashcard (59-76)
  • grade (402-451)
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (3)
packages/fsrs/src/index.ts (1)
  • Rating (26-26)
apps/web/src/hooks/useDir.ts (1)
  • useDir (4-10)
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)
  • formatInterval (14-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: bahar-marketing
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (12)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (1)

57-64: Clean migration from TRPC to React Query.

The mutation setup correctly uses React Query's useMutation and properly invalidates the deck list cache on success. The partial queryKey invalidation strategy will correctly clear all matching queries regardless of the show_reverse_flashcards parameter.

apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx (1)

21-56: LGTM!

The component properly handles optional fields with conditional rendering and optional chaining, and the staggered motion animations provide a nice UX touch.

apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)

8-33: LGTM!

The date formatting logic is clear and well-documented. The 60-day threshold for switching to exact dates is a reasonable UX choice.

apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx (1)

1-1: LGTM!

Clean barrel export pattern for the refactored component.

apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx (2)

16-21: LGTM!

The timer cleanup logic is properly implemented with useEffect cleanup function.


89-139: LGTM!

The animation overlay implementation is well-structured with proper motion animations and the sparkles effect for the Easy rating adds a nice touch.

apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (2)

34-65: LGTM!

Using useMemo with an empty dependency array for the static configuration is appropriate and prevents unnecessary recreations.


72-95: LGTM!

The component structure is clean with proper typing, motion animations, and good use of the cn() utility for conditional styling.

apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx (4)

41-44: LGTM! Clean component modularization.

The extraction of GradeOption, GradeFeedback, and TagBadgesList into separate components improves maintainability and follows good separation of concerns.


107-120: LGTM! Proper React Query mutation setup.

The migration from TRPC to React Query's useMutation is well-implemented with appropriate query invalidation in the onSuccess handler.


137-142: LGTM! Improved naming and conversion logic.

The renaming to schedulingCards (camelCase) improves consistency, and the use of the extracted convertFlashcardToFsrsCard utility reduces duplication.


428-439: LGTM! Cleaner grading UI with component mapping.

Replacing the inline button blocks with a map over GradeOption components significantly improves code readability and maintainability.

Comment thread apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts Outdated
@Shunseii Shunseii force-pushed the chore/remove-meili-dual-write branch from 445680e to 12c89dd Compare December 25, 2025 23:51
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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (1)

57-64: Add error handling for delete operation.

The delete mutation and its handler lack error handling. If the deletion fails, the user receives no feedback, creating a poor experience where clicking "Delete" appears to do nothing.

🔎 Recommended error handling implementation

Add an onError handler to the mutation:

 const { mutateAsync: deleteDeck } = useMutation({
   mutationFn: decksTable.delete.mutation,
   onSuccess: async () => {
     await queryClient.invalidateQueries({
       queryKey: decksTable.list.cacheOptions.queryKey,
     });
   },
+  onError: (error) => {
+    toast({
+      title: t`Failed to delete deck`,
+      description: t`An error occurred while deleting the deck. Please try again.`,
+      variant: "destructive",
+    });
+  },
 });

Alternatively, wrap the delete handler in a try-catch:

 <DropdownMenuItem
   onClick={async () => {
+    try {
       await deleteDeck({ id: deck.id });
 
       toast({
         title: t`Deck successfully deleted!`,
         description: t`The deck "${deck.name}" has been deleted.`,
       });
+    } catch (error) {
+      toast({
+        title: t`Failed to delete deck`,
+        description: t`An error occurred while deleting the deck. Please try again.`,
+        variant: "destructive",
+      });
+    }
   }}
   className="cursor-pointer"
 >

Also applies to: 199-211

♻️ Duplicate comments (1)
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)

29-46: Code duplication already flagged in previous review.

A previous review comment already identified that this conversion logic duplicates the toFsrsCard function from @bahar/fsrs. Please refer to that comment for the recommended fix.

🧹 Nitpick comments (3)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (1)

199-211: Consider adding loading state to prevent duplicate deletions.

The delete button doesn't disable during the mutation, allowing rapid clicks to trigger multiple deletion attempts. While the backend should handle this safely, adding a loading state improves UX and prevents user confusion.

💡 Suggested implementation

Extract isPending from the mutation and disable the button during deletion:

-const { mutateAsync: deleteDeck } = useMutation({
+const { mutateAsync: deleteDeck, isPending: isDeleting } = useMutation({
   mutationFn: decksTable.delete.mutation,
   onSuccess: async () => {
     await queryClient.invalidateQueries({
       queryKey: decksTable.list.cacheOptions.queryKey,
     });
   },
 });

Then disable the dropdown item:

 <DropdownMenuItem
+  disabled={isDeleting}
   onClick={async () => {
     await deleteDeck({ id: deck.id });
 
     toast({
       title: t`Deck successfully deleted!`,
       description: t`The deck "${deck.name}" has been deleted.`,
     });
   }}
   className="cursor-pointer"
 >
   <Trans>Delete</Trans>
 </DropdownMenuItem>

Note: Since multiple decks can be deleted independently, you may need to track pending state per deck ID using a Set or Map in state, rather than relying on the mutation's global isPending.

apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx (1)

8-19: Add explicit return type and consider a default case.

The function lacks an explicit return type and a default case in the switch statement. While TypeScript's exhaustiveness checking may catch missing cases if the type is a strict union, adding an explicit return type improves clarity and a default case makes the code more defensive.

🔎 Proposed fix
-const getTranslatedType = (entryType: SelectDictionaryEntry["type"]) => {
+const getTranslatedType = (
+  entryType: SelectDictionaryEntry["type"]
+): string => {
   switch (entryType) {
     case "ism":
       return t`Noun`;
     case "fi'l":
       return t`Verb`;
     case "harf":
       return t`Preposition`;
     case "expression":
       return t`Expression`;
+    default:
+      return entryType;
   }
 };
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx (1)

25-84: Consider moving feedbackConfig outside the component.

The configuration object is static and doesn't depend on props or state, so it's recreated unnecessarily on every render. Moving it outside the component would provide a minor performance improvement.

🔎 Proposed refactor

Move the configuration above the component definition:

const feedbackConfig = {
  [Rating.Again]: {
    icon: RotateCcw,
    color: "text-muted-foreground",
    bgColor: "bg-muted/20",
    animation: {
      initial: { scale: 0, rotate: 0 },
      animate: {
        scale: [0, 1.2, 1],
        rotate: [0, -10, 10, -10, 0],
      },
      transition: { duration: 0.5 },
    },
  },
  // ... rest of config
} as const;

export const GradeFeedback: FC<{
  grade: Grade | null;
  onComplete: () => void;
}> = ({ grade, onComplete }) => {
  // ... component body
};
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 445680e and 12c89dd.

📒 Files selected for processing (8)
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/routes/_authorized-layout/_search-layout/index.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/index.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use TypeScript with strict typing across entire codebase
Write self-documenting code and avoid overuse of comments
Error handling with try/catch blocks and structured error types
Component naming: PascalCase for components, camelCase for functions/variables

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Do not use any type unless absolutely necessary in TypeScript
Use DisplayError class for user-friendly error messages and Result<T, E> type for explicit error handling

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,jsx}: React components use functional style with hooks
Prefer using jotai atoms over React Context for state management

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the cn() utility function for combining and conditionally applying Tailwind classes

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Tanstack Router for client-side routing

Files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
**/*.{ts,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for database operations

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.
📚 Learning: 2025-11-27T06:02:25.941Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx,ts} : Web app uses Tanstack Router for client-side routing

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{tsx,jsx} : React components use functional style with hooks

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the `cn()` utility function for combining and conditionally applying Tailwind classes

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/mobile/**/*.{tsx,jsx} : Mobile app uses Expo with file-based routing (Expo Router)

Applied to files:

  • apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx
🧬 Code graph analysis (5)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (2)
apps/mobile/src/app/(search)/decks.tsx (2)
  • deleteMutation (247-247)
  • deck (241-250)
apps/web/src/components/features/decks/DeckDialogContent.tsx (4)
  • queryClient (119-123)
  • deck (111-382)
  • queryClient (126-130)
  • queryClient (135-139)
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx (3)
packages/fsrs/src/index.ts (2)
  • Grade (28-28)
  • Rating (26-26)
packages/design-system/src/utils.ts (1)
  • cn (4-6)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (5)
  • useEffect (104-229)
  • grade (456-464)
  • grade (402-451)
  • grade (105-110)
  • fsrs (394-394)
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (4)
packages/fsrs/src/index.ts (1)
  • Rating (26-26)
apps/web/src/hooks/useDir.ts (1)
  • useDir (4-10)
packages/design-system/src/utils.ts (1)
  • cn (4-6)
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)
  • formatInterval (11-23)
apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx (3)
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)
  • convertFlashcardToFsrsCard (29-47)
packages/fsrs/src/index.ts (2)
  • Grade (28-28)
  • Rating (26-26)
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (1)
  • GradeOption (27-96)
apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)
apps/web/src/components/features/flashcards/FlashcardDrawer.tsx (3)
  • flashcard (59-76)
  • fsrs (394-394)
  • grade (402-451)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Workers Builds: bahar-marketing
🔇 Additional comments (12)
apps/web/src/routes/_authorized-layout/_app-layout/decks/route.lazy.tsx (1)

31-31: LGTM: Import path updated correctly.

The FlashcardDrawer import path has been updated to reflect the new subdirectory structure, and the usage throughout the file remains consistent.

apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts (1)

11-23: LGTM! Clean interval formatting logic.

The function correctly handles same-day vs future date scenarios and properly uses locale-aware formatting.

apps/web/src/components/features/flashcards/FlashcardDrawer/TagBadgesList.tsx (1)

21-56: LGTM! Well-structured component with smooth animations.

The component properly handles conditional rendering and implements staggered animations for a polished user experience.

apps/web/src/components/features/flashcards/FlashcardDrawer/GradeFeedback.tsx (2)

16-21: LGTM! Proper timer cleanup.

The effect correctly manages the timer lifecycle and includes appropriate cleanup on unmount or dependency changes.


89-139: LGTM! Excellent animation implementation.

The rendering logic is well-structured with smooth animations and the sparkle effect for "Easy" ratings adds a nice touch. The math for positioning sparkles in a circle is correct.

apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx (4)

1-44: LGTM! Clean import organization.

The imports are well-organized and the new modular components are properly imported.


134-145: LGTM! Improved consistency with camelCase naming.

The rename from scheduling_cards to schedulingCards follows JavaScript naming conventions, and using the extracted convertFlashcardToFsrsCard utility improves code organization.


147-180: LGTM! Grade execution logic is sound.

The function correctly updates both local and remote state with proper scheduling data.


419-439: LGTM! Cleaner approach with mapped components.

Mapping over the rating values to render GradeOption components is more maintainable and DRY compared to manually coding each button. The refactoring improves code quality.

apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (3)

11-25: LGTM! Well-defined TypeScript types.

The type definitions are clear and provide good type safety for the component.


34-65: LGTM! Proper use of useMemo for static configuration.

The configuration is correctly memoized with an empty dependency array since it's static, preventing unnecessary recomputation.


67-96: LGTM! Clean component implementation with proper animations.

The component correctly determines locale based on text direction and renders a well-structured button with smooth animations and formatted interval display.

Comment thread apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts Outdated
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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx (1)

144-177: Add learning_steps to the localUpdates object.

The learning_steps field is missing from the localUpdates object. The reference implementation in packages/fsrs/src/index.ts (gradeFlashcard function, line 157) includes learning_steps: selectedCard.learning_steps in the returned updates. This field should be persisted when saving the graded card.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 12c89dd and e3f01de.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/web/package.json
  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/components/features/flashcards/FlashcardDrawer/utils.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use TypeScript with strict typing across entire codebase
Write self-documenting code and avoid overuse of comments
Error handling with try/catch blocks and structured error types
Component naming: PascalCase for components, camelCase for functions/variables

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Do not use any type unless absolutely necessary in TypeScript
Use DisplayError class for user-friendly error messages and Result<T, E> type for explicit error handling

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,jsx}: React components use functional style with hooks
Prefer using jotai atoms over React Context for state management

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the cn() utility function for combining and conditionally applying Tailwind classes

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
apps/web/**/*.{tsx,jsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Web app uses Tanstack Router for client-side routing

Files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
🧠 Learnings (6)
📓 Common learnings
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.
📚 Learning: 2025-11-27T23:01:26.752Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: packages/drizzle-user-db-schemas/package.json:1-20
Timestamp: 2025-11-27T23:01:26.752Z
Learning: drizzle-zod0.5.1 has peer dependencies of "zod": "*" (accepts any version) and "drizzle-orm": ">=0.23.13". The zod wildcard peer dependency means any zod version is compatible with drizzle-zod0.5.1.

Applied to files:

  • apps/web/package.json
📚 Learning: 2025-11-27T06:02:25.941Z
Learnt from: Shunseii
Repo: Shunseii/bahar PR: 24
File: apps/web/src/components/features/decks/DeckDialogContent.tsx:195-201
Timestamp: 2025-11-27T06:02:25.941Z
Learning: In apps/web/src/components/features/decks/DeckDialogContent.tsx, the backend API calls (trpc.decks.create and trpc.decks.update) are temporary and planned to be removed after migration is complete.

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Web app uses Shadcn/UI components and Tailwind CSS v4 for styling, using the `cn()` utility function for combining and conditionally applying Tailwind classes

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to **/*.{tsx,jsx} : React components use functional style with hooks

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
📚 Learning: 2025-12-24T04:04:31.679Z
Learnt from: CR
Repo: Shunseii/bahar PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T04:04:31.679Z
Learning: Applies to apps/web/**/*.{tsx,jsx,ts} : Web app uses Tanstack Router for client-side routing

Applied to files:

  • apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx
🧬 Code graph analysis (1)
apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx (2)
packages/fsrs/src/index.ts (3)
  • toFsrsCard (33-51)
  • Grade (28-28)
  • Rating (26-26)
apps/web/src/components/features/flashcards/FlashcardDrawer/GradeOption.tsx (1)
  • GradeOption (27-96)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: bahar-marketing
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (8)
apps/web/src/components/features/flashcards/FlashcardDrawer/FlashcardDrawer.tsx (6)

1-44: LGTM!

The import refactoring is well-organized. The separation of concerns is clean with UI components extracted to their own modules (GradeOption, GradeFeedback, TagBadgesList), and the FSRS conversion logic properly imported from @bahar/fsrs.


134-142: LGTM!

The rename from scheduling_cards to schedulingCards follows JavaScript/TypeScript camelCase conventions, and the integration with toFsrsCard from the external module is correct.


179-190: LGTM!

The animation-based grading flow is clean - storing the callback reference and deferring execution until animation completes is a good pattern for UX.


107-120: LGTM!

The migration from TRPC to local table mutations aligns with the PR objectives. Query invalidation is comprehensive, covering today's cards, counts, and deck lists. Based on learnings, this completes the planned removal of temporary backend API calls.


425-435: Clean refactor using array mapping.

Replacing the explicit four-button block with a map over Rating values improves maintainability. The as const assertion ensures type safety. This aligns with DRY principles.


219-227: LGTM!

The extracted GradeFeedback component integrates cleanly with the AnimatePresence wrapper, maintaining the same animation behavior while improving code organization.

apps/web/package.json (2)

14-14: The Zod v4 upgrade is compatible with the package versions selected. @hookform/resolvers v5.2.2 supports Zod v4 (added in v5.1.0), and drizzle-orm v0.45.1 works with Zod v4 via the separate drizzle-zod package (v0.8.3 in this repo, which supports Zod v4). The codebase has already been migrated to Zod v4 across all packages with no v3-specific patterns remaining.

Likely an incorrect or invalid review comment.


69-69: No action needed – ts-fsrs v5 upgrade is properly implemented.

The upgrade from ts-fsrs v4.5.1 to v5.2.3 has been correctly handled. The enable_fuzz parameter used in fsrs({ enable_fuzz: true }) is valid in v5, and the learning_steps field is properly mapped throughout the codebase (database schema, FSRS Card conversion, and grading logic). All breaking changes appear to be addressed.

Comment thread apps/web/package.json
@Shunseii Shunseii merged commit 18df778 into main Dec 26, 2025
3 checks passed
@Shunseii Shunseii deleted the chore/remove-meili-dual-write branch December 26, 2025 00:10
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.

1 participant