Skip to content

Allow multiple pdf upload and tagging#79

Merged
InfinityBowman merged 12 commits into
mainfrom
78-multiple-pdfs
Dec 18, 2025
Merged

Allow multiple pdf upload and tagging#79
InfinityBowman merged 12 commits into
mainfrom
78-multiple-pdfs

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Dec 18, 2025

Summary by CodeRabbit

  • New Features
    • Multi‑PDF support: dropdown selector, tagging (Primary/Protocol/Secondary), editable PDF citation metadata, and enhanced upload/download/delete flows
    • New "All Studies" view with expandable study cards and reviewer-assignment modal
    • Inline project header editing for name and description
    • UI library: new TypeScript declarations and a Menu option to hide the dropdown indicator
  • Bug Fixes
    • Cleans up orphaned files when PDF metadata updates or uploads fail
  • Removals
    • Study naming conventions UI, utilities and tests removed

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

@InfinityBowman InfinityBowman linked an issue Dec 18, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds multi-PDF management across UI and data layers: new PDF components and modals, tag and citation metadata, cache-aware upload/download/delete flows with orphan cleanup, Y.js sync updates (pdfId, tag, citation fields), All-Studies UI rewrite, and Zag machine initialization refactors.

Changes

Cohort / File(s) Summary
Configuration & Docs
\.github/copilot-instructions.md`, `packages/web/package.json``
Docs update (copilot instruction) and added runtime dependency nanoid ^5.1.6 to web package.
PDF UI Components
\packages/web/src/components/checklist-ui/pdf/*``
New PDF components and barrel: PdfList.jsx, PdfListItem.jsx, PdfSelector.jsx, PdfTagBadge.jsx, PdfTagSelect.jsx, index.js — list UI, item actions, tag UI, selector dropdown and exports.
Checklist Integration
\packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx`, `packages/web/src/components/checklist-ui/ChecklistsDashboard.jsx``
ChecklistYjsWrapper updated for multiple PDFs, selection state, default/primary selection, cache-aware load and switch logic, upload/delete/tag flows; minor formatting tweak in dashboard.
Project All-Studies UI
\packages/web/src/components/project-ui/all-studies/.jsx`, `.../study-card/`, `.../index.js``
New AllStudiesTab and study-related components (StudyCard, StudyCardHeader, StudyPdfSection, AssignReviewersModal, EditPdfMetadataModal) and barrels — per-study PDF lists, uploads, Google Drive import, metadata editing, reviewers, and form-state persistence.
Project View / Header
\packages/web/src/components/project-ui/ProjectView.jsx`, `packages/web/src/components/project-ui/ProjectHeader.jsx``
Added orphaned-upload cleanup on metadata failures (delete uploaded file on addPdf failure); ProjectHeader refactored to use Editable and a simplified description edit flow; ProjectView import paths updated.
PDF Handlers & Primitives
\packages/web/src/primitives/useProjectPdfHandlers.js`, `packages/web/src/primitives/useProject/pdfs.js`, `packages/web/src/primitives/useProject/index.js``
New handlers: handleDownloadPdf, handleDeletePdf, handleTagChange, handleUpdatePdfMetadata; upload/import apply tagging logic; cache integration; pdfs.js adds pdfId via nanoid, tag management, citation metadata, and mutation APIs; useProject exposes new pdf methods.
Sync & Store
\packages/web/src/primitives/useProject/sync.js`, `packages/web/src/stores/projectStore.js``
Sync maps PDFs by pdfId, adds id, tag, and citation fields (title, firstAuthor, publicationYear, journal, doi); projectStore adds selectors: getStudyPdfs, getPrimaryPdf, getProtocolPdf, getSecondaryPdfs, getPdf.
Study Handlers & Naming
\packages/web/src/primitives/useProjectStudyHandlers.js`, `packages/web/src/components/project-ui/tabs/OverviewTab.jsx`, `packages/web/src/components/project-ui/ProjectSettings.jsx`, `packages/web/src/lib/studyNaming.js`, `packages/web/src/lib/tests/studyNaming.test.js``
Removed legacy study naming utilities, tests, and ProjectSettings; study naming now derived from PDF filename; handlers updated to use filename and added orphaned-upload cleanup; legacy naming-application flows removed.
Zag machine init refactor
\packages/ui/src/zag/{Accordion,Clipboard,Combobox,Progress,RadioGroup,ToggleGroup,Tour,Menu}.jsx``
Replaced mergeProps-based context construction with inline useMachine initializers; Menu adds hideIndicator prop and placement handling.
UI Types & Packaging
\packages/ui/package.json`, `packages/ui/src/index.d.ts``
Added types entry and expanded package.exports to include TypeScript types; new index.d.ts provides public typings for UI primitives and utilities.
Misc refactors & tests
\packages/ui/src/zag/Editable.jsx`, `packages/ui/src/zag/*.jsx`, `packages/workers/src/config/validation.js`, `packages/workers/src/routes/projects.js``
Editable syncs external value only when not editing; validators preserve undefined vs null; projects PUT uses selective update payloads to avoid overwriting fields.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as UI (Checklist / AllStudies)
    participant Handlers as PDF Handlers
    participant API as Project API (pdfs.js)
    participant YDoc as Y.js Store
    participant Cache as Local Cache
    participant Server

    rect rgb(200,220,255)
    Note over User,Server: Upload + Add to Study
    User->>UI: selects/uploads PDF
    UI->>Handlers: handleUploadPdf(studyId, file)
    Handlers->>Server: uploadPdf(file)
    Server-->>Handlers: uploaded file metadata
    Handlers->>Cache: cachePdf(data)
    Handlers->>API: addPdfToStudy(studyId, pdfInfo, tag)
    API->>YDoc: create pdfId, store metadata (id,key,fileName,tag,citation...)
    YDoc-->>API: confirm
    API-->>Handlers: pdfId
    Handlers->>UI: update list/selection
    end

    rect rgb(220,255,220)
    Note over User,Server: View / Download (cache-first)
    User->>UI: view/download PDF
    UI->>Handlers: handleViewPdf / handleDownloadPdf
    alt cache hit
        Handlers->>Cache: getCachedPdf(key)
        Cache-->>Handlers: pdf data
    else cache miss
        Handlers->>Server: downloadPdf(key)
        Server-->>Handlers: pdf data
        Handlers->>Cache: cachePdf(data)
    end
    Handlers->>UI: render or trigger browser download
    end

    rect rgb(255,240,200)
    Note over User,Server: Tag / Metadata Update
    User->>UI: change tag or edit metadata
    UI->>Handlers: handleTagChange / handleUpdatePdfMetadata
    Handlers->>API: updatePdfTag / updatePdfMetadata
    API->>YDoc: clear conflicting tags (if needed) and update fields
    YDoc-->>API: confirm
    API-->>Handlers: success
    Handlers->>UI: refresh display
    end

    rect rgb(255,220,220)
    Note over User,Server: Delete + Cleanup
    User->>UI: confirm delete
    UI->>Handlers: handleDeletePdf(studyId,pdf)
    Handlers->>Server: deletePdf(key)
    Server-->>Handlers: confirm
    Handlers->>API: removePdfFromStudy(studyId,pdfId)
    API->>YDoc: delete pdf entry
    YDoc-->>API: confirm
    Handlers->>Cache: clear cached pdf
    Handlers->>UI: update selection/list
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

  • Focus review on:
    • packages/web/src/primitives/useProject/pdfs.js — pdfId generation, Y.Map usage, tag conflict clearing, metadata add/delete semantics.
    • packages/web/src/primitives/useProjectPdfHandlers.js — upload/import error handling, cache usage, orphaned-file cleanup.
    • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx — selection, cache interaction, race conditions when switching PDFs.
    • packages/web/src/components/project-ui/all-studies/* — new components interactions, OAuth form-state persistence, modal flows.
    • packages/ui/src/zag/* (Menu) — ensure useMachine initializers preserve previous behavior (placement/hideIndicator).

Possibly related PRs

Poem

🐰 I hopped through folders, PDFs in tow,

Tags and metadata all in a row.
Cached and tidy, no orphaned crumb,
Cards unfold and viewers hum.
A rabbit cheers: "Organize — let's go!"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.22% 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 'Allow multiple pdf upload and tagging' directly and accurately reflects the main objective of this pull request, which introduces multi-PDF support with tag management throughout the codebase.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db970c7 and bf26c69.

📒 Files selected for processing (5)
  • packages/ui/src/zag/Editable.jsx (3 hunks)
  • packages/web/src/components/project-ui/ProjectHeader.jsx (2 hunks)
  • packages/web/src/components/project-ui/ProjectView.jsx (5 hunks)
  • packages/workers/src/config/validation.js (1 hunks)
  • packages/workers/src/routes/projects.js (1 hunks)

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 18, 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
corates bf26c69 Commit Preview URL Dec 18 2025, 05:00 AM

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

🧹 Nitpick comments (13)
.github/copilot-instructions.md (1)

11-11: Minor grammar correction.

"client side" should be hyphenated as "client-side" when used as a compound adjective.

🔎 Apply this diff to fix the grammar:
-Do not worry about migrations either client side or backend unless specifically instructed to do so in the prompt. This project is not in production and has no users.
+Do not worry about migrations either client-side or backend unless specifically instructed to do so in the prompt. This project is not in production and has no users.
packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx (1)

22-27: Consider using createMemo for reactive derived values.

The disabledValues function computes values based on props. Per SolidJS best practices and coding guidelines, derived values from props should use createMemo to ensure proper reactive updates.

🔎 Apply this diff to use createMemo:
+import { createMemo } from 'solid-js';
 import { Select } from '@corates/ui';

 const TAG_OPTIONS = [
   { label: 'Primary Report', value: 'primary' },
   { label: 'Protocol', value: 'protocol' },
   { label: 'Secondary', value: 'secondary' },
 ];

 export default function PdfTagSelect(props) {
-  const disabledValues = () => {
+  const disabledValues = createMemo(() => {
     const disabled = [];
     if (props.disablePrimary) disabled.push('primary');
     if (props.disableProtocol) disabled.push('protocol');
     return disabled;
-  };
+  });

As per coding guidelines: "When you need to compute a value based on props or state, use createMemo to ensure it updates reactively."

packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (2)

42-74: Consider using createMemo for derived values.

The tagMenuItems function computes menu items based on props. Per SolidJS best practices, derived values from props should use createMemo to ensure proper reactive updates and avoid unnecessary recalculations.

🔎 Apply this diff to use createMemo:
-import { Show, createSignal } from 'solid-js';
+import { Show, createSignal, createMemo } from 'solid-js';
 import { FiTrash2, FiEye, FiDownload } from 'solid-icons/fi';
 import { CgFileDocument } from 'solid-icons/cg';
 import { Menu, Dialog } from '@corates/ui';
 import PdfTagBadge from './PdfTagBadge.jsx';

 export default function PdfListItem(props) {
   const [showDeleteConfirm, setShowDeleteConfirm] = createSignal(false);

-  const tagMenuItems = () => {
+  const tagMenuItems = createMemo(() => {
     const items = [];

     // Only show tag options that make sense
     if (props.pdf.tag !== 'primary') {
       items.push({
         id: 'set-primary',
         label:
           props.hasPrimary && props.pdf.tag !== 'primary' ?
             'Set as Primary (replaces current)'
           : 'Set as Primary',
       });
     }

     if (props.pdf.tag !== 'protocol') {
       items.push({
         id: 'set-protocol',
         label:
           props.hasProtocol && props.pdf.tag !== 'protocol' ?
             'Set as Protocol (replaces current)'
           : 'Set as Protocol',
       });
     }

     if (props.pdf.tag !== 'secondary') {
       items.push({
         id: 'set-secondary',
         label: 'Set as Secondary',
       });
     }

     return items;
-  };
+  });

As per coding guidelines: "When you need to compute a value based on props or state, use createMemo to ensure it updates reactively."


30-40: Optional: Consider extracting formatting utilities.

The formatFileSize and formatDate helper functions could be extracted to a shared utility file (e.g., @/utils/formatters.js) for reuse across components. However, since the component is still focused and manageable in size, this refactor can be deferred.

packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx (2)

25-34: Consider extracting the sorting logic to a shared utility.

This sorting logic is duplicated in PdfList.jsx (lines 26-36). To follow the DRY principle, consider extracting it to a shared utility function.

🔎 Suggested approach

Create a utility in a shared location:

// e.g., in utils/pdfUtils.js or primitives/
export const PDF_TAG_ORDER = { primary: 0, protocol: 1, secondary: 2 };

export function sortPdfsByTag(pdfs) {
  return [...pdfs].sort((a, b) => {
    const tagA = PDF_TAG_ORDER[a.tag] ?? 2;
    const tagB = PDF_TAG_ORDER[b.tag] ?? 2;
    return tagA - tagB;
  });
}

68-79: Consider reusing PdfTagBadge component for consistency.

The inline badge rendering duplicates the PdfTagBadge component's functionality. While the compact menu item styling may justify this, consider whether PdfTagBadge could accept a size or compact prop to enable reuse here.

packages/web/src/components/checklist-ui/pdf/PdfList.jsx (1)

16-25: Prop count exceeds guideline (8 props vs. 5 max).

Per coding guidelines, components should receive at most 1–5 props. Consider grouping related callbacks into a single handlers object prop or moving PDF operations into an external store/context that children can access directly.

🔎 Suggested approach

Option 1 - Group callbacks:

-// props.onView, onDownload, onDelete, onTagChange, onUpload
+// props.handlers: { onView, onDownload, onDelete, onTagChange, onUpload }

Option 2 - Use a PDF operations context/store that PdfList and PdfListItem can access directly, reducing prop drilling of handlers.

packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (1)

297-304: Redundant visibility check.

The Show when={studyPdfs().length > 1} duplicates PdfSelector's internal shouldShow logic. While harmless, you could simplify by always rendering PdfSelector and letting it handle its own visibility.

🔎 Suggested simplification
-      {/* PDF Selector - only shown when multiple PDFs exist */}
-      <Show when={studyPdfs().length > 1}>
-        <PdfSelector
-          pdfs={studyPdfs()}
-          selectedPdfId={selectedPdfId()}
-          onSelect={handlePdfSelect}
-        />
-      </Show>
+      <PdfSelector
+        pdfs={studyPdfs()}
+        selectedPdfId={selectedPdfId()}
+        onSelect={handlePdfSelect}
+      />
packages/web/src/primitives/useProject/pdfs.js (2)

63-91: Consider adding tag validation.

The function accepts any string as tag without validation. While callers likely pass valid values, adding validation could prevent subtle bugs.

🔎 Optional: Add tag validation
+const VALID_TAGS = ['primary', 'protocol', 'secondary'];
+
 function addPdfToStudy(studyId, pdfInfo, tag = 'secondary') {
   const ydoc = getYDoc();
   if (!ydoc || !isSynced()) return null;

   const studiesMap = ydoc.getMap('reviews');
   const studyYMap = studiesMap.get(studyId);
   if (!studyYMap) return null;

+  const validatedTag = VALID_TAGS.includes(tag) ? tag : 'secondary';
   const pdfsMap = getPdfsMap(studyId, true);

   // If setting as primary or protocol, clear existing tag first
-  if (tag === 'primary' || tag === 'protocol') {
-    clearTag(studyId, tag);
+  if (validatedTag === 'primary' || validatedTag === 'protocol') {
+    clearTag(studyId, validatedTag);
   }

137-159: Minor redundancy in ydoc checks.

The updatePdfTag function performs ydoc/isSynced() checks that are already done inside getPdfsMap. This is safe but slightly redundant.

🔎 Optional: Simplify by reusing getPdfsMap checks
 function updatePdfTag(studyId, pdfId, tag) {
-  const ydoc = getYDoc();
-  if (!ydoc || !isSynced()) return;
-
   const pdfsMap = getPdfsMap(studyId);
   if (!pdfsMap) return;

   const pdfYMap = pdfsMap.get(pdfId);
   if (!pdfYMap) return;

   // If setting as primary or protocol, clear existing tag first
   if (tag === 'primary' || tag === 'protocol') {
     clearTag(studyId, tag);
   }

   pdfYMap.set('tag', tag);

+  const ydoc = getYDoc();
   const studiesMap = ydoc.getMap('reviews');
   const studyYMap = studiesMap.get(studyId);
   if (studyYMap) {
     studyYMap.set('updatedAt', Date.now());
   }
 }
packages/web/src/primitives/useProjectPdfHandlers.js (3)

48-77: Consider caching downloaded PDFs.

When fetching from the server (line 60), the downloaded data isn't cached for future use. This differs from handleUploadPdf which caches uploaded files. Consider caching to improve subsequent access performance.

🔎 Optional: Cache downloaded PDFs
     if (!data) {
       // Fetch from server
       data = await downloadPdf(projectId, studyId, pdf.fileName);
+      // Cache for future access
+      cachePdf(projectId, studyId, pdf.fileName, data).catch(err =>
+        console.warn('Failed to cache downloaded PDF:', err),
+      );
     }

149-172: Minor DRY opportunity.

The effectiveTag determination logic (lines 155-158) is duplicated from handleUploadPdf (lines 88-91). Consider extracting to a helper if this pattern grows.

The duplication is minor (4 lines) and may be acceptable given the localized scope.


181-184: Redundant arrow function wrappers.

The convenience methods are already functions from projectActions, so wrapping them in additional arrow functions is unnecessary.

🔎 Simplify convenience method exports
   return {
     handleViewPdf,
     handleDownloadPdf,
     handleUploadPdf,
     handleDeletePdf,
     handleTagChange,
     handleGoogleDriveImportSuccess,
     // Convenience methods
-    setPdfAsPrimary: (studyId, pdfId) => setPdfAsPrimary(studyId, pdfId),
-    setPdfAsProtocol: (studyId, pdfId) => setPdfAsProtocol(studyId, pdfId),
+    setPdfAsPrimary,
+    setPdfAsProtocol,
   };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b4bdee and 954d756.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • .github/copilot-instructions.md (1 hunks)
  • docs/plans/all-studies-tab-redesign.plan.md (1 hunks)
  • docs/plans/multiple-pdfs-per-study.plan.md (1 hunks)
  • packages/web/package.json (1 hunks)
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (6 hunks)
  • packages/web/src/components/checklist-ui/ChecklistsDashboard.jsx (0 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfList.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/pdf/index.js (1 hunks)
  • packages/web/src/primitives/useProject/index.js (1 hunks)
  • packages/web/src/primitives/useProject/pdfs.js (3 hunks)
  • packages/web/src/primitives/useProject/sync.js (1 hunks)
  • packages/web/src/primitives/useProjectPdfHandlers.js (2 hunks)
  • packages/web/src/stores/projectStore.js (2 hunks)
💤 Files with no reviewable changes (1)
  • packages/web/src/components/checklist-ui/ChecklistsDashboard.jsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx
  • packages/web/src/stores/projectStore.js
  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/components/checklist-ui/pdf/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfList.jsx
  • packages/web/src/primitives/useProject/sync.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Import stores directly where needed instead of passing values through multiple components
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

packages/web/src/**/*.{js,jsx,ts,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx
  • packages/web/src/stores/projectStore.js
  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/components/checklist-ui/pdf/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfList.jsx
  • packages/web/src/primitives/useProject/sync.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Use responsive design principles for UI components
Use Zag.js for UI components and design system. Zag components exist in packages/web/src/components/zag/* and should be reused

Files:

  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/components/checklist-ui/pdf/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfList.jsx
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx
  • packages/web/src/stores/projectStore.js
  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/components/checklist-ui/pdf/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx
  • packages/web/package.json
  • packages/web/src/components/checklist-ui/pdf/PdfList.jsx
  • packages/web/src/primitives/useProject/sync.js
**/{index,index.js,index.jsx,index.ts,index.tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Group related components in subdirectories with an index.js barrel export

Files:

  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/checklist-ui/pdf/index.js
docs/plans/**/*.{md,txt}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place plans in the docs/plans/ directory

Files:

  • docs/plans/multiple-pdfs-per-study.plan.md
  • docs/plans/all-studies-tab-redesign.plan.md
🧠 Learnings (9)
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: This project is split into multiple packages under the packages/ directory: landing/marketing site, main app (web), and backend services (workers). Each package may have its own dependencies and configurations

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: This project uses Cloudflare Workers only for backend services and frontend deployments. Cloudflare Pages is not used

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/components/**/*.{js,jsx,ts,tsx} : For UI icons, use the `solid-icons` library or SVGs only. Do not use emojis

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Cloudflare Pages is not used in this project; only Cloudflare Workers is used for backend services and frontend deployments

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Do not use emojis in code, comments, or documentation

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/workers/migrations/*.sql : All migrations should go in a single file: packages/workers/migrations/0001_init.sql. Do NOT create separate migration files. Edit the existing 0001_init.sql file directly when adding new tables or schema changes

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/components/**/*.{js,jsx,ts,tsx} : Use Zag.js for UI components and design system. Zag components exist in `packages/web/src/components/zag/*` and should be reused

Applied to files:

  • packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx
📚 Learning: 2025-12-16T18:27:52.769Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.769Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Group related components in subdirectories with an index.js barrel export

Applied to files:

  • packages/web/src/components/checklist-ui/pdf/index.js
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to **/{index,index.js,index.jsx,index.ts,index.tsx} : Group related components in subdirectories with an `index.js` barrel export

Applied to files:

  • packages/web/src/components/checklist-ui/pdf/index.js
🧬 Code graph analysis (6)
packages/web/src/components/checklist-ui/pdf/PdfTagSelect.jsx (1)
packages/ui/src/zag/Select.jsx (1)
  • Select (22-152)
packages/web/src/stores/projectStore.js (2)
packages/web/src/primitives/useProject/sync.js (1)
  • study (59-88)
packages/workers/src/routes/pdfs.js (1)
  • pdfs (76-81)
packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (2)
packages/web/src/api/pdf-api.js (2)
  • uploadPdf (40-70)
  • deletePdf (113-127)
packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx (1)
  • PdfSelector (15-99)
packages/web/src/primitives/useProjectPdfHandlers.js (3)
packages/web/src/primitives/useProject/index.js (1)
  • studies (43-43)
packages/web/src/primitives/pdfCache.js (3)
  • getCachedPdf (82-102)
  • cachePdf (111-138)
  • removeCachedPdf (146-163)
packages/web/src/api/pdf-api.js (3)
  • downloadPdf (79-93)
  • uploadPdf (40-70)
  • deletePdf (113-127)
packages/web/src/components/checklist-ui/pdf/PdfList.jsx (2)
packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx (1)
  • sortedPdfs (25-34)
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1)
  • PdfListItem (18-184)
packages/web/src/primitives/useProject/sync.js (2)
packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (1)
  • pdfData (20-20)
packages/web/src/components/checklist-ui/compare/ReconciliationWrapper.jsx (1)
  • pdfData (44-44)
🪛 LanguageTool
.github/copilot-instructions.md

[grammar] ~11-~11: Use a hyphen to join words.
Context: ...not worry about migrations either client side or backend unless specifically inst...

(QB_NEW_EN_HYPHEN)

docs/plans/all-studies-tab-redesign.plan.md

[style] ~183-~183: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...k actions (select multiple studies)? 3. Should PDF upload have a tag selector in the f...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~184-~184: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...e a tag selector in the file picker? 4. Should we show a PDF preview thumbnail?

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.18.1)
docs/plans/multiple-pdfs-per-study.plan.md

26-26: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


53-53: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


182-182: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/plans/all-studies-tab-redesign.plan.md

17-17: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


43-43: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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: corates
🔇 Additional comments (29)
packages/web/src/primitives/useProject/sync.js (1)

111-122: LGTM - Good backward compatibility handling.

The updated PDF iteration logic properly handles both the new id-based structure and legacy fileName-based structure with sensible fallbacks. The default tag value of 'secondary' aligns well with the tagging system introduced across the PR.

packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx (1)

14-35: LGTM - Clean presentation component.

The component correctly follows SolidJS best practices:

  • Props are accessed directly without destructuring
  • Uses Show for conditional rendering
  • Uses solid-icons library as per guidelines
  • Lean and focused on presentation only
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1)

91-184: LGTM - Well-structured component with good UX patterns.

The component demonstrates several good practices:

  • Uses Zag.js components (Menu, Dialog) as per guidelines
  • Uses solid-icons library for icons (no emojis)
  • Props accessed directly without destructuring
  • Local state managed with createSignal
  • Delete confirmation dialog provides good UX
  • Conditional menu items with clear labeling for tag replacements
packages/web/src/stores/projectStore.js (2)

224-265: LGTM - Well-designed PDF selectors with backward compatibility.

The new PDF selectors are well-structured:

  • Consistent with existing store patterns
  • Clear naming conventions
  • getSecondaryPdfs correctly handles backward compatibility by including PDFs without tags
  • Proper use of optional chaining and safe defaults
  • Each selector has a focused, single responsibility

483-489: LGTM - Proper export of new selectors.

The new PDF selectors are correctly exported and follow the existing organization pattern in the store.

packages/web/src/primitives/useProject/index.js (1)

191-194: LGTM - Consistent integration of new PDF operations.

The new PDF operations are properly integrated following established patterns:

  • Consistent delegation to pdfOps with optional chaining
  • Spread args pattern matches existing operations
  • Clear naming that indicates their purpose
  • removePdfByFileName provides useful backward compatibility
packages/web/package.json (1)

22-22: Verify that the nanoid package version is current and secure.

nanoid version 5.1.6 is the latest stable release, and Snyk confirms that 5.1.6 is the latest non-vulnerable version. The package demonstrates a positive release cadence with community engagement. No security advisories apply to this version.

docs/plans/multiple-pdfs-per-study.plan.md (1)

1-353: LGTM - Well-structured implementation plan.

The plan is comprehensive with clear phases, file mappings, and implementation progress tracking. It follows the guideline to place plans in docs/plans/.

Minor: The static analysis tool flagged three fenced code blocks (lines 26, 53, 182) that could specify a language (e.g., yaml or text for the Y.js structure diagrams, text for the file structure). This is optional for documentation.

packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx (1)

15-98: LGTM - Clean, focused component.

The component follows SolidJS best practices:

  • Props accessed directly without destructuring
  • createMemo used appropriately for derived values
  • Uses solid-icons library as per guidelines
  • Reasonable prop count (3 props)
packages/web/src/components/checklist-ui/pdf/PdfList.jsx (2)

81-94: LGTM - Correct hasPrimary/hasProtocol logic.

The conditional logic hasPrimary={hasPrimary() && pdf.tag !== 'primary'} correctly conveys to PdfListItem whether setting this PDF as primary would replace an existing primary. Same for protocol.


26-36: Good secondary sort by uploadedAt.

The additional descending sort by uploadedAt within tag groups provides stable, user-friendly ordering. Note: the tag-based sorting logic is also in PdfSelector.jsx - consider the shared utility suggestion from earlier.

docs/plans/all-studies-tab-redesign.plan.md (2)

1-184: Well-organized redesign plan.

The plan clearly outlines the component structure, implementation steps, and UI/UX considerations. The expandable card pattern and modal separation are sensible architectural choices.


17-39: Emojis in documentation violate coding guidelines.

Per the coding guidelines: "Do not use emojis in code, comments, or documentation." Lines 30, 33, and 36 use emojis (📄, ⭐, 📋) in the ASCII diagram. Consider using text labels or icon descriptions instead.

🔎 Suggested fix
-│ │ 📄 smith-2024-main.pdf          ⭐ Primary    [View] [⋮]   │ │
+│ │ [doc] smith-2024-main.pdf       [*] Primary   [View] [⋮]   │ │
-│ │ 📄 smith-2024-protocol.pdf      📋 Protocol   [View] [⋮]   │ │
+│ │ [doc] smith-2024-protocol.pdf   [P] Protocol  [View] [⋮]   │ │
-│ │ 📄 supplementary.pdf                          [View] [⋮]   │ │
+│ │ [doc] supplementary.pdf                       [View] [⋮]   │ │
⛔ Skipped due to learnings
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Do not use emojis in code, comments, or documentation
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.769Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Do not use emojis in code, comments, or documentation
packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (3)

173-192: LGTM - Clean deletion flow with proper state reset.

The deletion handler correctly:

  1. Deletes from cloud storage
  2. Removes from Y.js metadata
  3. Clears local cache
  4. Resets selection state

The auto-select effect will then pick the new default PDF automatically.


15-361: LGTM - Well-integrated multi-PDF support.

The component cleanly integrates multi-PDF functionality:

  • Proper use of createMemo for derived PDF state
  • Appropriate loading/caching flow
  • Correct tag assignment on upload (primary for first, secondary for subsequent)
  • Clean integration with PdfSelector

77-83: Remove the infinite loop concern—the guard condition prevents re-triggering.

The effect on lines 77-83 is safe: once selectedPdfId is set, the condition !selectedPdfId() evaluates to false on subsequent runs, preventing the setter from being called again. There is no infinite loop risk. If defaultPdf() changes later, the effect reruns but the guard still prevents unexpected re-selection.

If you prefer explicit dependency tracking for code clarity, consider importing and using on() from solid-js to track only defaultPdf(), avoiding reactivity to selectedPdfId():

createEffect(on(defaultPdf, (pdf) => {
  if (pdf && !selectedPdfId()) {
    setSelectedPdfId(pdf.id);
  }
}));

This is optional for clarity only.

Likely an incorrect or invalid review comment.

packages/web/src/primitives/useProject/pdfs.js (7)

1-11: LGTM!

Good documentation of PDF tags and proper import of dependencies.


21-39: LGTM!

The getPdfsMap helper properly handles all edge cases with appropriate null checks and conditional creation.


41-54: LGTM!

The clearTag function correctly enforces the single primary/protocol constraint by resetting conflicting tags to 'secondary'.


93-112: LGTM!

The removePdfFromStudy function correctly handles removal by pdfId with appropriate null checks and timestamp updates.


114-129: LGTM!

Good backward compatibility method. The early return after first match is appropriate behavior for filename-based removal.


161-177: LGTM!

Clean convenience methods that provide a clear API for common operations.


179-186: LGTM!

Complete export list matching the documented public API.

packages/web/src/primitives/useProjectPdfHandlers.js (5)

1-27: LGTM!

Clear documentation of supported tags and proper imports. The destructuring of projectActions is acceptable here since it's a plain object parameter, not SolidJS props.


29-46: LGTM!

Good cache-first approach for viewing PDFs. The 60-second delay before revoking the blob URL gives adequate time for the browser tab to load the PDF.


86-117: LGTM!

Good implementation with auto-primary tagging for the first PDF. The optional chaining on study?.pdfs?.length safely handles edge cases, and caching the upload improves subsequent access.


119-137: LGTM!

Correct order of operations: server delete first, then local state update. This ensures consistency if the server call fails.


139-147: LGTM!

Clean wrapper that delegates to the underlying operation.

packages/web/src/components/checklist-ui/pdf/index.js (1)

1-13: LGTM!

Clean barrel export following the coding guidelines. This properly groups related PDF components with an index.js export for simplified imports. Based on learnings, this aligns with the project's convention for organizing related components.

Comment on lines +150 to +160
const pdfId = addPdfToStudy(
params.studyId,
{
key: result.key,
fileName: result.fileName,
size: result.size,
uploadedBy: user()?.id,
uploadedAt: Date.now(),
},
tag,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for addPdfToStudy function definition
rg "addPdfToStudy" --type js --type jsx --type ts --type tsx -A 15

Repository: InfinityBowman/corates

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Search for addPdfToStudy function definition across all files
rg "addPdfToStudy" -A 15 --type-add 'web:*.{js,jsx,ts,tsx}'

Repository: InfinityBowman/corates

Length of output: 22704


🏁 Script executed:

#!/bin/bash
# Get the complete addPdfToStudy function implementation
sed -n '/function addPdfToStudy/,/^  }/p' packages/web/src/primitives/useProject/pdfs.js | head -50

Repository: InfinityBowman/corates

Length of output: 991


🏁 Script executed:

#!/bin/bash
# Check how pdfId is used after assignment in ChecklistYjsWrapper.jsx
sed -n '150,175p' packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx

Repository: InfinityBowman/corates

Length of output: 781


Add null validation for addPdfToStudy return value before setting state.

The function addPdfToStudy returns the PDF id on success, but can return null if the document is not synced or the study doesn't exist. Line 163 calls setSelectedPdfId(pdfId) without validating that pdfId is not null, which could silently set the state to null and cause the UI to behave unexpectedly. Add a check to ensure pdfId is valid before updating state:

if (pdfId) {
  setSelectedPdfId(pdfId);
}
🤖 Prompt for AI Agents
In packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx around lines
150 to 160, the code assigns the return value of addPdfToStudy to pdfId and
immediately calls setSelectedPdfId(pdfId) without checking for null;
addPdfToStudy can return null when the document isn't synced or the study is
missing, so validate the return before updating state by checking that pdfId is
truthy (e.g., if (pdfId) { setSelectedPdfId(pdfId); }) and only set state when a
valid id is returned.

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

🧹 Nitpick comments (9)
packages/ui/src/zag/Menu.jsx (1)

99-99: Consider extracting button styles to a config.

The button has hardcoded Tailwind classes for padding, colors, and hover states. As per coding guidelines, consider extracting these to a configuration object if this button style needs to be reused or customized across different Menu instances.

Example refactor to make button styling configurable:
+const defaultButtonClass = 'inline-flex items-center gap-1 p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded transition-colors';
+
 export function Menu(props) {
   const [local, machineProps] = splitProps(props, [
     'trigger',
     'items',
     'inDialog',
     'hideIndicator',
     'class',
+    'buttonClass',
   ]);

Then use: class={local.buttonClass || defaultButtonClass}

packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (2)

159-182: Remove redundant padding wrapper.

The Dialog component already wraps its children in a <div class='p-4'>. The additional <div class='p-4'> here creates double padding. Consider removing the wrapper or using a different spacing approach.

🔎 Suggested fix:
       {/* Delete confirmation dialog */}
       <Dialog open={showDeleteConfirm()} onOpenChange={setShowDeleteConfirm} title='Delete PDF'>
-        <div class='p-4'>
-          <p class='text-gray-600 mb-4'>
+        <div>
+          <p class='text-gray-600 mb-6'>
             Are you sure you want to delete <strong>{props.pdf.fileName}</strong>? This action
             cannot be undone.
           </p>
           <div class='flex justify-end gap-3'>

30-40: Consider extracting utility functions.

formatFileSize and formatDate are common formatting utilities that could be reused across components. Consider moving them to a shared utility file if similar functions exist elsewhere in the codebase.

packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (2)

13-27: Consider reducing the number of props via context or a primitive.

This component accepts 14 props, which exceeds the guideline of 1-5 props for local configuration. The component appears to be a coordination layer that passes many callbacks through to child components.

According to the coding guidelines: "If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context."

Consider refactoring to:

  • Move PDF-related handlers (onViewPdf, onDownloadPdf, onUploadPdf, onDeletePdf, onTagChange, onOpenGoogleDrive) into a context or primitive
  • Move study-related handlers (onEditMetadata, onAssignReviewers, onDeleteStudy) into a context or primitive
  • Use the existing ProjectContext or create a StudyCardContext to provide these handlers

This would reduce prop drilling and make the component more maintainable.

Based on coding guidelines requiring components to receive at most 1-5 props for local configuration and to avoid prop drilling of application state.


36-40: Simplify the Collapsible onOpenChange handler.

The condition if (open !== expanded()) before calling onToggleExpanded may be redundant. The Collapsible component's onOpenChange should only fire when the state actually changes, making this check unnecessary.

🔎 Simplify to:
       <Collapsible
         open={expanded()}
-        onOpenChange={open => {
-          if (open !== expanded()) {
-            props.onToggleExpanded?.();
-          }
-        }}
+        onOpenChange={() => props.onToggleExpanded?.()}
         trigger={api => (
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (1)

15-23: Consider consolidating PDF handlers into a single prop.

This component accepts 8 props, which exceeds the guideline of 1-5 props. While this is a leaf component (less critical than a coordinator component), consider consolidating the PDF-related callbacks into a single handlers object or using a context/primitive.

For example:

// Instead of multiple callback props:
props.onViewPdf, props.onDownloadPdf, props.onUploadPdf, etc.

// Use a single handlers object:
props.pdfHandlers = {
  onView, onDownload, onUpload, onDelete, onTagChange, onOpenGoogleDrive
}

This would reduce the prop count and align better with the coding guidelines.

Based on coding guidelines requiring components to receive at most 1-5 props.

packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1)

17-24: Consider reducing props via context.

This component accepts 8 props, exceeding the guideline of 1-5 props. Similar to StudyCard and StudyPdfSection, consider consolidating the action callbacks (onEditMetadata, onAssignReviewers, onDelete) into a single handlers object or providing them via context.

Based on coding guidelines requiring components to receive at most 1-5 props.

packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (2)

107-125: Inconsistent optional chaining on PDF handlers.

The PDF handlers use optional chaining inconsistently:

  • Lines 108, 116: No optional chaining (handlers.pdfHandlers.handleViewPdf, handlers.pdfHandlers.handleUploadPdf)
  • Lines 112, 120, 124: With optional chaining (handlers.pdfHandlers.handleDownloadPdf?., handlers.pdfHandlers.handleDeletePdf?., handlers.pdfHandlers.handleTagChange?.)

If all these handlers are expected to exist in pdfHandlers, use consistent non-optional access. If some are optional, add comments explaining why.

🔎 Make consistent by removing optional chaining (if all handlers are guaranteed):
   const handleDownloadPdf = (studyId, pdf) => {
-    handlers.pdfHandlers.handleDownloadPdf?.(studyId, pdf);
+    handlers.pdfHandlers.handleDownloadPdf(studyId, pdf);
   };

   const handleUploadPdf = async (studyId, file) => {
     await handlers.pdfHandlers.handleUploadPdf(studyId, file);
   };

   const handleDeletePdf = (studyId, pdf) => {
-    handlers.pdfHandlers.handleDeletePdf?.(studyId, pdf);
+    handlers.pdfHandlers.handleDeletePdf(studyId, pdf);
   };

   const handleTagChange = (studyId, pdfId, newTag) => {
-    handlers.pdfHandlers.handleTagChange?.(studyId, pdfId, newTag);
+    handlers.pdfHandlers.handleTagChange(studyId, pdfId, newTag);
   };

183-197: StudyCard receives many props - consider context pattern.

Similar to the comment on StudyCard.jsx itself, the StudyCard component receives 13+ props here. While this works, it creates tight coupling between AllStudiesTab and StudyCard.

Consider creating a StudyCardContext that provides the handlers, reducing the number of props passed at each level and making the component more maintainable. This would align with the coding guideline to avoid passing shared state through props.

Based on coding guidelines requiring use of stores/context instead of passing shared state through multiple component props.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 954d756 and 755e239.

📒 Files selected for processing (12)
  • packages/ui/src/zag/Menu.jsx (3 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1 hunks)
  • packages/web/src/components/project-ui/ProjectView.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx (6 hunks)
  • packages/web/src/components/project-ui/all-studies/index.js (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/index.js (1 hunks)
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/ProjectView.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/ui/src/zag/Menu.jsx
  • packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Import stores directly where needed instead of passing values through multiple components
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

packages/web/src/**/*.{js,jsx,ts,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/ProjectView.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Use responsive design principles for UI components
Use Zag.js for UI components and design system. Zag components exist in packages/web/src/components/zag/* and should be reused

Files:

  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/ProjectView.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/ProjectView.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/ui/src/zag/Menu.jsx
  • packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
**/{index,index.js,index.jsx,index.ts,index.tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Group related components in subdirectories with an index.js barrel export

Files:

  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
🧠 Learnings (4)
📚 Learning: 2025-12-16T18:27:52.769Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.769Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Group related components in subdirectories with an index.js barrel export

Applied to files:

  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to **/{index,index.js,index.jsx,index.ts,index.tsx} : Group related components in subdirectories with an `index.js` barrel export

Applied to files:

  • packages/web/src/components/project-ui/all-studies/index.js
  • packages/web/src/components/project-ui/all-studies/study-card/index.js
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use Solid's createStore for complex state or state objects for better performance and reactivity

Applied to files:

  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Import stores directly where needed instead of passing values through multiple components

Applied to files:

  • packages/web/src/components/project-ui/ProjectView.jsx
🧬 Code graph analysis (7)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (4)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1)
  • study (29-29)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1)
  • study (26-26)
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (2)
  • handleDelete (86-89)
  • PdfListItem (18-185)
packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (1)
  • handleTagChange (123-125)
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (3)
packages/web/src/components/checklist-ui/pdf/PdfTagBadge.jsx (1)
  • PdfTagBadge (14-35)
packages/ui/src/zag/Menu.jsx (1)
  • Menu (31-125)
packages/ui/src/zag/Dialog.jsx (1)
  • Dialog (18-92)
packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx (3)
packages/web/src/components/project-ui/ReviewerAssignment.jsx (1)
  • getMemberName (44-48)
packages/ui/src/zag/Dialog.jsx (1)
  • Dialog (18-92)
packages/ui/src/zag/Select.jsx (1)
  • Select (22-152)
packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (1)
packages/web/src/lib/formStatePersistence.js (5)
  • getRestoreParamsFromUrl (204-214)
  • getFormState (90-132)
  • clearFormState (140-165)
  • clearRestoreParamsFromUrl (219-225)
  • saveFormState (49-82)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (4)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1)
  • study (29-29)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (1)
  • study (28-28)
packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx (1)
  • name (24-24)
packages/ui/src/zag/Menu.jsx (1)
  • Menu (31-125)
packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx (2)
packages/ui/src/zag/Dialog.jsx (1)
  • Dialog (18-92)
packages/ui/src/zag/Collapsible.jsx (1)
  • Collapsible (15-70)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (4)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (2)
  • study (26-26)
  • StudyCardHeader (17-139)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)
  • study (28-28)
  • StudyPdfSection (15-170)
packages/ui/src/zag/Collapsible.jsx (1)
  • Collapsible (15-70)
packages/ui/src/zag/Menu.jsx (1)
  • api (48-48)
⏰ 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: corates
🔇 Additional comments (21)
packages/ui/src/zag/Menu.jsx (3)

19-19: Documentation looks good.

The hideIndicator prop is documented clearly and follows the existing documentation pattern.


32-38: Props destructuring is correct.

The hideIndicator prop is properly included in the local props array, which is appropriate since it's used directly in the component's JSX.


97-117: Conditional indicator rendering implemented correctly; styling is appropriate and follows best practices.

The hideIndicator feature is cleanly implemented using SolidJS's Show component with the when prop, which properly hides the chevron when the prop is true. The button styling uses Tailwind's data-[state=open] modifier for state-based styling, which is the recommended approach for Zag components and aligns with current Tailwind best practices.

Verified that existing Menu usages (PdfListItem.jsx and PdfSelector.jsx) work correctly with the new styling—the button styles are applied consistently across all instances without regressions. The SVG chevron implementation complies with the coding guidelines (no emoji usage).

packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1)

91-109: LGTM!

The component structure is clean with proper separation of file info, tag badge, and action buttons. Props are accessed reactively without destructuring, which is correct for SolidJS.

packages/web/src/components/project-ui/all-studies/AssignReviewersModal.jsx (3)

25-39: LGTM - Good store usage pattern.

The component correctly imports projectStore directly and accesses members via projectStore.getMembers() rather than prop-drilling. The memberItems memo properly includes an "Unassigned" option and handles member name fallbacks. Based on learnings, this follows the preferred pattern of importing stores directly.


42-48: LGTM!

The form reset effect correctly tracks both props.study and props.open as dependencies, ensuring the form is reinitialized when the modal opens with a different study.


85-104: Good UX: Cross-validation prevents duplicate reviewer selection.

The disabledValues prop on each Select prevents selecting the same person as both Reviewer 1 and Reviewer 2, which is appropriate validation for this use case.

packages/web/src/components/project-ui/all-studies/EditStudyMetadataModal.jsx (3)

52-63: LGTM - Robust publication year validation.

The validation handles edge cases well: trims whitespace, checks for finite numbers, truncates decimals, and enforces a reasonable year range (1900-2100). This prevents invalid data from being saved.


65-73: Minor: Consider preserving empty string vs undefined semantics.

Currently, empty trimmed strings become undefined. If the backend distinguishes between "field was cleared" (empty string) and "field was never set" (undefined/null), this could cause issues. Verify this aligns with backend expectations.


85-91: LGTM!

Clean Dialog setup with appropriate props forwarding. The size='lg' correctly maps to max-w-lg in the Dialog component.

packages/web/src/components/project-ui/all-studies/study-card/index.js (1)

1-4: LGTM - Clean barrel export.

This follows the coding guidelines for grouping related components in subdirectories with an index.js barrel export. The named exports provide a clear public API for the study-card module.

packages/web/src/components/project-ui/ProjectView.jsx (1)

31-31: LGTM - Module reorganization import update.

The import path correctly reflects the restructuring of AllStudiesTab from tabs/ to the all-studies/ directory, with no remaining references to the old import path in the codebase.

packages/web/src/components/project-ui/all-studies/index.js (1)

1-5: LGTM! Clean barrel export structure.

This index file follows the coding guideline to "Group related components in subdirectories with an index.js barrel export." The re-export pattern is clean and appropriate.

Based on learnings: applies to **/{index,index.js,index.jsx,index.ts,index.tsx} - Group related components in subdirectories with an index.js barrel export.

packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (3)

25-62: LGTM! Solid state management and error handling.

The upload handling is well-implemented:

  • Proper use of createSignal for the uploading state
  • MIME type validation before upload
  • Comprehensive error handling with user-friendly toast notifications
  • Cleanup in the finally block to reset the file input

32-43: LGTM! Excellent use of createMemo for derived state.

The computed values (sortedPdfs, hasPrimary, hasProtocol) properly use createMemo to ensure reactive updates. The sort logic correctly prioritizes PDFs by tag (primary → protocol → secondary) and then by upload timestamp.

As per coding guidelines: "Use createMemo for derived values based on props or state to ensure reactive updates."


160-161: Prop logic is correct and well-implemented.

The expressions hasPrimary() && pdf.tag !== 'primary' and hasProtocol() && pdf.tag !== 'protocol' correctly pass to PdfListItem a boolean indicating whether another PDF already occupies that tag. PdfListItem uses these props to display appropriate "(replaces current)" messages, which aligns with the documented behavior. No issues found.

packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (2)

29-55: LGTM! Clean helper functions with proper fallbacks.

The helper functions (assignedReviewers, hasReviewers, displayName, citationLine) are well-structured with appropriate fallback logic. The displayName function provides a good user experience by falling back to "Author (Year)" or "Untitled Study" when the study name is not available.


100-102: Verify chevron rotation synchronization.

The chevron uses props.expanded to determine rotation state. Ensure this stays in sync with the Collapsible's internal state. From the StudyCard implementation, props.expanded receives api.open from the Collapsible, which should keep it synchronized.

However, there's a potential race condition: if onToggle is called (line 96) and props.expanded hasn't updated yet, the chevron might show the wrong state momentarily.

Consider testing the expand/collapse interaction to confirm smooth visual transitions without flickering.

packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (3)

22-23: LGTM! Proper use of context to avoid prop drilling.

Using useProjectContext to access shared state (projectId, handlers, getAssigneeName) is the correct approach and aligns with the coding guideline: "Do NOT prop-drill application state. Import stores directly where needed instead of passing values through multiple components."

Based on coding guidelines requiring use of external stores or context rather than prop drilling.


39-53: LGTM! Solid OAuth redirect state restoration.

The mount-time form state restoration is well-implemented:

  • Checks for matching restore params
  • Properly handles errors
  • Cleans up both IndexedDB state and URL params after restoration
  • Uses try-catch for robust error handling

This enables a smooth user experience when returning from OAuth flows.


31-31: LGTM! Appropriate use of Set for expanded studies.

Using a Set to track expanded study IDs is an efficient choice for this use case, allowing O(1) lookups and easy add/delete operations.

As per coding guidelines: "Use Solid's createStore for complex state or state objects for better performance and reactivity." In this case, a Set within a signal is simpler and appropriate for tracking a collection of IDs.

Comment thread packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
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: 10

Caution

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

⚠️ Outside diff range comments (1)
packages/ui/src/zag/Combobox.jsx (1)

52-69: Fix property override: openOnClick prop is being ignored.

The hardcoded openOnClick: true on line 55 overrides any user-provided openOnClick value from machineProps because it comes after the spread. According to the documentation (line 25), openOnClick should be a configurable prop.

However, the getter pattern for collection (lines 56-58) is correctly implemented to ensure reactive access.

🔎 Apply this diff to respect the user's `openOnClick` prop:
   const service = useMachine(combobox.machine, () => ({
-    ...machineProps,
     id: createUniqueId(),
     openOnClick: true,
+    ...machineProps,
     get collection() {
       return collection();
     },

This places defaults first so machineProps can override them.

♻️ Duplicate comments (1)
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1)

147-155: Menu placement prop issue already flagged.

This is the same issue identified in the past review comment - the Menu component's placement prop is not functional due to hardcoded positioning.

🧹 Nitpick comments (12)
packages/ui/src/zag/RadioGroup.jsx (1)

30-30: Prefer reading orientation from the machine API for consistency.

The isVertical function reads directly from local.orientation (props), while the relevant code snippet from ToggleGroup.jsx shows the pattern of reading from api().orientation (machine state). Since orientation is passed to the machine on line 25, the UI should read it back from the machine API to maintain a single source of truth, consistent with the Zag.js pattern used in other components.

🔎 Apply this diff for consistency with ToggleGroup pattern:
-  const isVertical = () => (local.orientation || 'vertical') === 'vertical';
+  const isVertical = () => api().orientation === 'vertical';
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (2)

17-24: Consider reducing prop count or moving shared state to context/store.

This component receives 6 props, which exceeds the recommended 1-5 props per coding guidelines. Consider:

  • Moving getAssigneeName to context (it's a project-level utility)
  • Grouping callbacks into a single actions object prop

Based on coding guidelines, components should receive at most 1-5 props for local configuration.


69-81: Use createMemo for derived values to avoid redundant computation.

citationLine() is invoked twice in the JSX (lines 125-126), recomputing on each call. Wrap it with createMemo for reactive caching.

Suggested fix
+import { createMemo } from 'solid-js';
...
-  const citationLine = () => {
+  const citationLine = createMemo(() => {
     const pdf = primaryPdf();
     const parts = [];
     const author = pdf?.firstAuthor || study().firstAuthor;
     const year = pdf?.publicationYear || study().publicationYear;
     const journal = pdf?.journal || study().journal;
     if (author) parts.push(author);
     if (year) parts.push(`(${year})`);
     if (journal) parts.push(`- ${journal}`);
     return parts.join(' ');
-  };
+  });
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (2)

18-28: Prop count (9) significantly exceeds guideline limit.

This component receives 9 props, well beyond the 1-5 prop limit. Consider refactoring:

  • Extract hasPrimary/hasProtocol into a context or derive them from a studyPdfs prop
  • Group action callbacks (onView, onDownload, onDelete, onTagChange, onEditMetadata) into a single actions object
  • Pass readOnly via context if it's consistent across the PDF list

Based on coding guidelines and retrieved learnings.


31-41: Extract utility functions for reuse.

formatFileSize and formatDate are generic helpers that could be reused across the codebase. Consider moving them to a shared utility file (e.g., @lib/formatters.js).

packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)

15-25: Prop count (9) exceeds guideline limit.

Similar to PdfListItem, this component receives 9 props. Consider:

  • Passing handlers as a single pdfActions object
  • Using context for readOnly state
  • Deriving study ID from a context rather than accessing study().id repeatedly

Based on coding guidelines.


46-51: Consider adding file extension check as fallback validation.

MIME type detection can be unreliable across browsers. Adding a file extension check provides a more robust validation.

Suggested enhancement
   const handleFileSelect = async e => {
     const file = e.target.files?.[0];
-    if (!file || file.type !== 'application/pdf') {
+    const isPdf = file?.type === 'application/pdf' || file?.name?.toLowerCase().endsWith('.pdf');
+    if (!file || !isPdf) {
       showToast.error('Invalid File', 'Please select a PDF file');
       return;
     }
packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (2)

34-40: Consider using createStore for grouped modal state.

Multiple related signals for modal state could be consolidated using createStore for cleaner state management.

Suggested approach
import { createStore } from 'solid-js/store';

const [modalState, setModalState] = createStore({
  metadata: { open: false, study: null },
  reviewers: { open: false, study: null },
  pdfMetadata: { open: false, pdf: null, studyId: null },
});

// Usage:
setModalState('metadata', { open: true, study: someStudy });

Based on retrieved learnings: "Use Solid's createStore for complex state or state objects for better performance and reactivity."


206-221: StudyCard receives 15 props - consider refactoring to reduce coupling.

This prop-drilling pattern violates the 1-5 prop guideline. Consider:

  • Passing a single handlers object containing all callbacks
  • Using context for PDF and study handlers that are consistent across all cards
  • Moving getAssigneeName to context (project-level utility)
Suggested approach
// In AllStudiesTab or ProjectContext:
const studyCardActions = {
  onToggleExpanded: toggleStudyExpanded,
  onEditMetadata: handleOpenMetadataModal,
  onAssignReviewers: handleOpenReviewersModal,
  onDeleteStudy: handlers.studyHandlers.handleDeleteStudy,
  pdf: {
    onView: handleViewPdf,
    onDownload: handleDownloadPdf,
    onUpload: handleUploadPdf,
    onDelete: handleDeletePdf,
    onTagChange: handleTagChange,
    onEditMetadata: handleOpenPdfMetadataModal,
    onOpenGoogleDrive: handleOpenGoogleDrive,
  },
};

// Then in JSX:
<StudyCard
  study={study}
  expanded={isStudyExpanded(study.id)}
  actions={studyCardActions}
/>

Based on coding guidelines.

packages/web/src/primitives/useProject/pdfs.js (1)

145-167: Consider validating tag values.

The function accepts any string as tag without validating it against the allowed values ('primary', 'protocol', 'secondary'). Invalid tags could be set, potentially causing issues in UI components that expect specific tag values.

Suggested validation:
+const VALID_TAGS = ['primary', 'protocol', 'secondary'];
+
 function updatePdfTag(studyId, pdfId, tag) {
   const ydoc = getYDoc();
   if (!ydoc || !isSynced()) return;
+  
+  if (!VALID_TAGS.includes(tag)) {
+    console.warn(`Invalid PDF tag: ${tag}`);
+    return;
+  }

   const pdfsMap = getPdfsMap(studyId);
packages/web/src/primitives/useProjectPdfHandlers.js (2)

57-83: Consider caching downloaded PDFs.

When fetching from the server (line 66), the downloaded data isn't cached for future use. This means subsequent downloads of the same PDF will always hit the server. handleUploadPdf caches after upload, so similar caching here would be consistent.

Suggested improvement:
       if (!data) {
         // Fetch from server
         data = await downloadPdf(projectId, studyId, pdf.fileName);
+        // Cache for future use
+        cachePdf(projectId, studyId, pdf.fileName, data).catch(err =>
+          console.warn('Failed to cache downloaded PDF:', err),
+        );
       }

168-188: Consider returning pdfId for consistency.

Unlike handleUploadPdf which returns the new pdfId, this function doesn't return it. If callers need to reference the newly added PDF immediately after import, they'd have no way to identify it.

Suggested change:
   const handleGoogleDriveImportSuccess = (studyId, file, tag = 'secondary') => {
     if (!studyId || !file) return;
     // ...
-    addPdfToStudy(
+    return addPdfToStudy(
       studyId,
       // ...
     );
   };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 755e239 and 35a9c0f.

📒 Files selected for processing (18)
  • packages/ui/src/zag/Accordion.jsx (2 hunks)
  • packages/ui/src/zag/Clipboard.jsx (3 hunks)
  • packages/ui/src/zag/Combobox.jsx (3 hunks)
  • packages/ui/src/zag/Menu.jsx (4 hunks)
  • packages/ui/src/zag/Progress.jsx (2 hunks)
  • packages/ui/src/zag/RadioGroup.jsx (2 hunks)
  • packages/ui/src/zag/ToggleGroup.jsx (3 hunks)
  • packages/ui/src/zag/Tour.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (1 hunks)
  • packages/web/src/primitives/useProject/index.js (1 hunks)
  • packages/web/src/primitives/useProject/pdfs.js (3 hunks)
  • packages/web/src/primitives/useProject/sync.js (1 hunks)
  • packages/web/src/primitives/useProjectPdfHandlers.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/primitives/useProject/sync.js
  • packages/ui/src/zag/Menu.jsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/ui/src/zag/Progress.jsx
  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Clipboard.jsx
  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/ui/src/zag/Combobox.jsx
  • packages/ui/src/zag/Tour.jsx
  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/ui/src/zag/RadioGroup.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using config files rather than hardcoding values

Files:

  • packages/ui/src/zag/Progress.jsx
  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Clipboard.jsx
  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/ui/src/zag/Combobox.jsx
  • packages/ui/src/zag/Tour.jsx
  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/ui/src/zag/RadioGroup.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Import stores directly where needed instead of passing values through multiple components
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

packages/web/src/**/*.{js,jsx,ts,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Use responsive design principles for UI components
Use Zag.js for UI components and design system. Zag components exist in packages/web/src/components/zag/* and should be reused

Files:

  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
**/{index,index.js,index.jsx,index.ts,index.tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Group related components in subdirectories with an index.js barrel export

Files:

  • packages/web/src/primitives/useProject/index.js
🧠 Learnings (9)
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date

Applied to files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date

Applied to files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/components/**/*.{js,jsx,ts,tsx} : Use Zag.js for UI components and design system. Zag components exist in `packages/web/src/components/zag/*` and should be reused

Applied to files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context

Applied to files:

  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)

Applied to files:

  • packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx
  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : When you have complex state or state objects, use Solid's `createStore` for better performance and reactivity

Applied to files:

  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use Solid's createStore for complex state or state objects for better performance and reactivity

Applied to files:

  • packages/ui/src/zag/Tour.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering

Applied to files:

  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering

Applied to files:

  • packages/ui/src/zag/Tour.jsx
🧬 Code graph analysis (9)
packages/ui/src/zag/Progress.jsx (13)
packages/ui/src/zag/Accordion.jsx (1)
  • service (22-26)
packages/ui/src/zag/Clipboard.jsx (2)
  • service (23-27)
  • service (90-94)
packages/ui/src/zag/Menu.jsx (1)
  • service (41-46)
packages/ui/src/zag/RadioGroup.jsx (1)
  • service (22-26)
packages/ui/src/zag/ToggleGroup.jsx (1)
  • service (25-32)
packages/ui/src/zag/Tour.jsx (1)
  • service (59-65)
packages/ui/src/zag/Editable.jsx (1)
  • service (106-128)
packages/ui/src/zag/Avatar.jsx (1)
  • service (11-14)
packages/ui/src/zag/Collapsible.jsx (1)
  • service (22-30)
packages/ui/src/zag/FloatingPanel.jsx (1)
  • service (39-58)
packages/ui/src/zag/NumberInput.jsx (1)
  • service (48-48)
packages/ui/src/zag/Dialog.jsx (2)
  • service (25-31)
  • service (117-130)
packages/ui/src/zag/Toast.jsx (1)
  • machineProps (22-26)
packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx (1)
packages/ui/src/zag/Dialog.jsx (1)
  • Dialog (18-92)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (5)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1)
  • study (30-30)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1)
  • study (25-25)
packages/web/src/primitives/useProject/sync.js (1)
  • study (59-88)
packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (2)
  • handleDelete (87-90)
  • PdfListItem (18-198)
packages/web/src/primitives/useProjectPdfHandlers.js (1)
  • handleTagChange (151-153)
packages/web/src/primitives/useProjectPdfHandlers.js (2)
packages/web/src/primitives/pdfCache.js (3)
  • getCachedPdf (82-102)
  • cachePdf (111-138)
  • removeCachedPdf (146-163)
packages/web/src/api/pdf-api.js (3)
  • downloadPdf (79-93)
  • uploadPdf (40-70)
  • deletePdf (113-127)
packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (8)
packages/web/src/primitives/useProject/index.js (1)
  • studies (43-43)
packages/web/src/primitives/useProjectPdfHandlers.js (2)
  • studies (24-24)
  • handleTagChange (151-153)
packages/web/src/components/project-ui/ProjectView.jsx (1)
  • studies (51-51)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (2)
  • study (30-30)
  • StudyCard (13-69)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1)
  • study (25-25)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)
  • study (29-29)
  • handleTagChange (81-83)
packages/web/src/primitives/useProject/sync.js (1)
  • study (59-88)
packages/web/src/components/project-ui/google-drive/GoogleDrivePickerModal.jsx (1)
  • GoogleDrivePickerModal (19-79)
packages/web/src/primitives/useProject/pdfs.js (2)
packages/web/src/primitives/useProject/index.js (2)
  • ydoc (29-29)
  • getYDoc (48-48)
packages/web/src/primitives/useProject/sync.js (1)
  • pdfsMap (109-109)
packages/ui/src/zag/RadioGroup.jsx (1)
packages/ui/src/zag/ToggleGroup.jsx (4)
  • local (23-23)
  • service (25-32)
  • api (34-34)
  • isVertical (47-47)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (4)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1)
  • study (30-30)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)
  • study (29-29)
  • pdfs (30-30)
packages/web/src/primitives/useProject/sync.js (1)
  • study (59-88)
packages/ui/src/zag/Menu.jsx (1)
  • Menu (31-125)
packages/ui/src/zag/ToggleGroup.jsx (4)
packages/ui/src/zag/Accordion.jsx (2)
  • service (22-26)
  • api (28-28)
packages/ui/src/zag/Clipboard.jsx (4)
  • service (23-27)
  • service (90-94)
  • api (29-29)
  • api (96-96)
packages/ui/src/zag/RadioGroup.jsx (3)
  • service (22-26)
  • isVertical (30-30)
  • api (28-28)
packages/ui/src/zag/Dialog.jsx (3)
  • service (25-31)
  • service (117-130)
  • api (33-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). (1)
  • GitHub Check: Workers Builds: corates
🔇 Additional comments (22)
packages/ui/src/zag/RadioGroup.jsx (3)

3-3: LGTM: Import correctly updated.

The addition of splitProps to the imports is necessary and properly used on line 20.


20-20: LGTM: splitProps correctly updated.

Adding 'orientation' to the local props array is correct and consistent with the pattern of extracting props that need local handling before being passed to the machine.


22-26: LGTM: useMachine initialization follows established pattern.

The migration to a functional initializer with useMachine is consistent with the parallel changes in other UI components (ToggleGroup, etc.) and properly passes the orientation configuration to the machine with the correct default value of 'vertical'.

packages/ui/src/zag/Progress.jsx (1)

3-3: LGTM: Unused import removed.

The removal of mergeProps is correct since the refactored code no longer uses it.

packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1)

148-154: Menu placement prop may not function as expected.

The placement='bottom-end' prop is passed, but based on a prior review of packages/ui/src/zag/Menu.jsx, the positioning is hardcoded to 'bottom-start'. This prop will have no effect until the Menu component is fixed to properly extract and apply the placement value.

packages/web/src/components/checklist-ui/pdf/PdfListItem.jsx (1)

171-195: LGTM - Delete confirmation dialog is well-implemented.

Good UX pattern with clear confirmation messaging and properly disabled state during operations.

packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx (1)

29-39: LGTM - Form reset effect is correctly implemented.

The effect properly resets form fields when the modal opens or PDF changes, ensuring consistent form state.

packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)

32-44: LGTM - Good use of createMemo for derived state.

The sorting logic and derived flags (hasPrimary, hasProtocol) are properly memoized, ensuring efficient reactive updates.


156-170: LGTM - Correct logic for hasPrimary/hasProtocol flags.

The conditional logic correctly indicates whether another PDF in the study has the primary/protocol tag, enabling proper UI hints in PdfListItem.

packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (2)

42-57: LGTM - OAuth redirect state restoration is well-implemented.

The mount hook properly checks for restore params, retrieves saved state, clears it after use, and handles errors gracefully.


227-263: LGTM - Modal integrations are properly wired.

All modals receive appropriate props and handlers. The Google Drive picker correctly targets specific studies via googleDriveTargetStudyId.

packages/web/src/primitives/useProject/index.js (1)

191-195: LGTM!

The new PDF operation passthroughs follow the established pattern consistently, using optional chaining for null safety before delegating to pdfOps.

packages/web/src/primitives/useProject/pdfs.js (5)

25-39: LGTM!

The getPdfsMap helper correctly handles null safety checks and the optional creation pattern.


45-54: LGTM!

The clearTag helper correctly enforces the single primary/protocol constraint by resetting conflicting tags to 'secondary'.


106-120: LGTM!

The removal logic correctly deletes by pdfId and updates the study timestamp.


175-202: LGTM with a note on documentation consistency.

The metadata update logic is correct, properly handling deletion of empty values. Note that handleUpdatePdfMetadata in useProjectPdfHandlers.js (line 159) documents abstract? as a metadata field, but it's not included in the fields array here. If abstract is intended to be supported, add it to line 186.


209-220: LGTM!

Clean convenience methods that improve API ergonomics.

packages/web/src/primitives/useProjectPdfHandlers.js (5)

26-33: LGTM!

The destructuring of projectActions is appropriate here since these are stable function references, not reactive values. The expanded API surface aligns with the new PDF operations.


92-123: LGTM!

The auto-primary logic for first PDF is a good UX improvement. Note: As mentioned in the pdfs.js review, consider handling the case where addPdfToStudy returns null to avoid orphaned R2 files.


130-143: LGTM!

The operation order (server delete → Y.js removal → cache clear) is appropriate. Server-side deletion should happen first to ensure the file is actually removed before updating local state.


151-163: LGTM!

Clean handler wrappers. Note the abstract? mentioned in the JSDoc (line 159) isn't currently supported in the underlying updatePdfMetadata implementation.


199-200: LGTM!

The convenience method wrappers are clear. Minor note: these could be simplified to direct references (setPdfAsPrimary, setPdfAsProtocol,) but the current form works fine.

Comment thread packages/ui/src/zag/Clipboard.jsx
Comment on lines +29 to +34
const service = useMachine(progress.machine, () => ({
...machineProps,
id: createUniqueId(),
min: 0,
max: 100,
});

const service = useMachine(progress.machine, context);
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix property override order to respect user-provided min/max values.

The current spread order (...machineProps first, then min: 0, max: 100) causes the hardcoded values to override any user-provided min and max props, contradicting the component documentation that lists these as customizable with defaults.

Apply this diff to fix the override order:
-  const service = useMachine(progress.machine, () => ({
-    ...machineProps,
-    id: createUniqueId(),
-    min: 0,
-    max: 100,
-  }));
+  const service = useMachine(progress.machine, () => ({
+    id: createUniqueId(),
+    min: 0,
+    max: 100,
+    ...machineProps,
+  }));

This ensures defaults are set first, then machineProps can override them while keeping id generation controlled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const service = useMachine(progress.machine, () => ({
...machineProps,
id: createUniqueId(),
min: 0,
max: 100,
});
const service = useMachine(progress.machine, context);
}));
const service = useMachine(progress.machine, () => ({
id: createUniqueId(),
min: 0,
max: 100,
...machineProps,
}));
🤖 Prompt for AI Agents
In packages/ui/src/zag/Progress.jsx around lines 29 to 34, the spread order puts
...machineProps before the hardcoded min: 0 and max: 100 so user-provided
min/max are being overridden; change the object construction so the defaults
(min: 0, max: 100) come first, then spread ...machineProps so user values can
override defaults, and keep id: createUniqueId() applied explicitly (e.g.,
defaults -> ...machineProps -> id) to preserve id generation control.

Comment on lines +25 to +32
const service = useMachine(toggle.machine, () => ({
...machineProps,
id: createUniqueId(),
orientation: 'horizontal',
loop: true,
rovingFocus: true,
deselectable: true,
});

const service = useMachine(toggle.machine, context);
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded defaults override machineProps, breaking documented API.

The initialization pattern spreads machineProps first, then sets hardcoded values for orientation, loop, rovingFocus, and deselectable. Since these come after the spread, they override any values passed via props, making these documented props non-functional.

For example, if a user passes orientation="vertical", it goes into machineProps but is immediately overridden by orientation: 'horizontal' on line 28.

Based on learnings, RadioGroup.jsx demonstrates the correct pattern: split these props into local, then use conditional defaults like local.orientation || 'horizontal'.

🔎 Apply this diff to respect props while providing defaults:
 export function ToggleGroup(props) {
-  const [local, machineProps] = splitProps(props, ['items', 'size', 'class']);
+  const [local, machineProps] = splitProps(props, [
+    'items',
+    'size',
+    'class',
+    'orientation',
+    'loop',
+    'rovingFocus',
+    'deselectable',
+  ]);

   const service = useMachine(toggle.machine, () => ({
     ...machineProps,
     id: createUniqueId(),
-    orientation: 'horizontal',
-    loop: true,
-    rovingFocus: true,
-    deselectable: true,
+    orientation: local.orientation || 'horizontal',
+    loop: local.loop ?? true,
+    rovingFocus: local.rovingFocus ?? true,
+    deselectable: local.deselectable ?? true,
   }));

Note: Using ?? (nullish coalescing) for boolean props to handle explicit false values correctly, and || for orientation since both undefined and empty string should fall back to the default.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const service = useMachine(toggle.machine, () => ({
...machineProps,
id: createUniqueId(),
orientation: 'horizontal',
loop: true,
rovingFocus: true,
deselectable: true,
});
const service = useMachine(toggle.machine, context);
}));
export function ToggleGroup(props) {
const [local, machineProps] = splitProps(props, [
'items',
'size',
'class',
'orientation',
'loop',
'rovingFocus',
'deselectable',
]);
const service = useMachine(toggle.machine, () => ({
...machineProps,
id: createUniqueId(),
orientation: local.orientation || 'horizontal',
loop: local.loop ?? true,
rovingFocus: local.rovingFocus ?? true,
deselectable: local.deselectable ?? true,
}));
🤖 Prompt for AI Agents
In packages/ui/src/zag/ToggleGroup.jsx around lines 25 to 32, the current
useMachine call spreads machineProps first and then unconditionally sets
orientation, loop, rovingFocus, and deselectable which overrides any values
passed in; change to extract local props from machineProps (or component props)
and pass defaults conditionally so passed-in values win: set id:
createUniqueId(), orientation: local.orientation || 'horizontal', loop:
local.loop ?? true, rovingFocus: local.rovingFocus ?? true, deselectable:
local.deselectable ?? true, and spread the remaining machineProps so documented
props are respected while preserving defaults.

Comment on lines +59 to +65
const service = useMachine(tour.machine, () => ({
...machineProps,
id: createUniqueId(),
closeOnInteractOutside: true,
closeOnEscape: true,
keyboardNavigation: true,
});

const service = useMachine(tour.machine, context);
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix property override order and move ID generation outside the factory.

The current implementation has two critical issues:

  1. Property override order is backwards: Spreading machineProps first, then setting defaults means user-provided props are ignored. Users cannot override closeOnInteractOutside, closeOnEscape, or keyboardNavigation despite the JSDoc (lines 36-38) documenting them as configurable with defaults.

  2. Unstable ID generation: Calling createUniqueId() inside the factory function passed to useMachine may cause stability issues if the factory is re-evaluated. Solid hooks like createUniqueId() should be called in the component body.

🔎 Apply this diff to fix both issues:
+  const id = createUniqueId();
+
-  const service = useMachine(tour.machine, () => ({
-    ...machineProps,
-    id: createUniqueId(),
+  const service = useMachine(tour.machine, () => ({
+    id,
     closeOnInteractOutside: true,
     closeOnEscape: true,
     keyboardNavigation: true,
+    ...machineProps,
   }));

This ensures:

  • The ID is stable and created once during component initialization
  • Defaults are applied first, then user props override them as documented
🤖 Prompt for AI Agents
In packages/ui/src/zag/Tour.jsx around lines 59 to 65, the factory passed to
useMachine both generates a unique ID inside the factory (unstable) and spreads
machineProps before defaults (so user props are overridden); fix by calling
createUniqueId() once in the component body to produce a stable id, then in the
factory spread the defaults first and then machineProps so user-provided options
override defaults (ensure id is set from the precomputed value).

Comment on lines +184 to +189
{/* Study count */}
<div class='flex items-center justify-between'>
<p class='text-sm text-gray-500'>
{studies().length} {studies().length === 1 ? 'study' : 'studies'} in this project
</p>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Study count displays during loading state.

The study count section renders regardless of hasData() state, which could show "0 studies" while data is loading. Consider wrapping it in the hasData() check or showing a skeleton.

Suggested fix
+    <Show when={hasData()}>
       {/* Study count */}
       <div class='flex items-center justify-between'>
         <p class='text-sm text-gray-500'>
           {studies().length} {studies().length === 1 ? 'study' : 'studies'} in this project
         </p>
       </div>
+    </Show>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* Study count */}
<div class='flex items-center justify-between'>
<p class='text-sm text-gray-500'>
{studies().length} {studies().length === 1 ? 'study' : 'studies'} in this project
</p>
</div>
<Show when={hasData()}>
{/* Study count */}
<div class='flex items-center justify-between'>
<p class='text-sm text-gray-500'>
{studies().length} {studies().length === 1 ? 'study' : 'studies'} in this project
</p>
</div>
</Show>
🤖 Prompt for AI Agents
In packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx around
lines 184 to 189, the study count block renders even when data is still loading
and can display "0 studies"; wrap the entire count block in the existing
hasData() check (or an equivalent loading guard) so it only renders when
hasData() is true, or alternatively render a loading skeleton/placeholder while
!hasData(); ensure you use the same JSX className conventions as the file and
preserve the current content for the true state.

Comment on lines +46 to +57
// Validate publication year
let parsedPublicationYear;
if (publicationYear()) {
const raw = publicationYear().toString().trim();
const num = Number(raw);
if (Number.isFinite(num)) {
const trunc = Math.trunc(num);
if (Number.isInteger(trunc) && trunc >= 1900 && trunc <= 2100) {
parsedPublicationYear = trunc;
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Invalid publication year is silently discarded without user feedback.

When a user enters an invalid year (outside 1900-2100 or non-numeric), the value is silently set to undefined. Consider showing a validation error or toast to inform the user.

Suggested approach
// Add validation state
const [yearError, setYearError] = createSignal('');

// In handleSave, before building metadata:
if (publicationYear()) {
  const raw = publicationYear().toString().trim();
  const num = Number(raw);
  if (!Number.isFinite(num) || num < 1900 || num > 2100) {
    setYearError('Year must be between 1900 and 2100');
    setSaving(false);
    return;
  }
  parsedPublicationYear = Math.trunc(num);
}
🤖 Prompt for AI Agents
In packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
around lines 46 to 57, the publication year validation silently drops invalid
input (non-numeric or outside 1900–2100) without user feedback; update the
component to surface validation errors: add a validation state (e.g., yearError
signal) and in the save/validation flow, when publicationYear is present but
invalid, set the yearError message (or show a toast), stop the save (reset
saving flag) and return; only parse/truncate and clear the error when the value
is valid.

Comment on lines +63 to 99
function addPdfToStudy(studyId, pdfInfo, tag = 'secondary') {
const ydoc = getYDoc();
if (!ydoc || !isSynced()) return null;

const studiesMap = ydoc.getMap('reviews');
const studyYMap = studiesMap.get(studyId);
if (!studyYMap) return null;

const pdfsMap = getPdfsMap(studyId, true);

// If setting as primary or protocol, clear existing tag first
if (tag === 'primary' || tag === 'protocol') {
clearTag(studyId, tag);
}

const pdfId = nanoid();
const pdfYMap = new Y.Map();
pdfYMap.set('id', pdfId);
pdfYMap.set('key', pdfInfo.key);
pdfYMap.set('fileName', pdfInfo.fileName);
pdfYMap.set('size', pdfInfo.size);
pdfYMap.set('uploadedBy', pdfInfo.uploadedBy);
pdfYMap.set('uploadedAt', pdfInfo.uploadedAt || Date.now());
pdfsMap.set(pdfInfo.fileName, pdfYMap);
pdfYMap.set('tag', tag);

// Citation metadata (optional, can be added later via updatePdfMetadata)
if (pdfInfo.title) pdfYMap.set('title', pdfInfo.title);
if (pdfInfo.firstAuthor) pdfYMap.set('firstAuthor', pdfInfo.firstAuthor);
if (pdfInfo.publicationYear) pdfYMap.set('publicationYear', pdfInfo.publicationYear);
if (pdfInfo.journal) pdfYMap.set('journal', pdfInfo.journal);
if (pdfInfo.doi) pdfYMap.set('doi', pdfInfo.doi);

pdfsMap.set(pdfId, pdfYMap);

studyYMap.set('updatedAt', Date.now());
return pdfId;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider how callers handle null return.

The function returns null on multiple early exit paths (lines 65, 69) but also returns pdfId on success. In useProjectPdfHandlers.js, handleUploadPdf (line 106) assigns the result directly without checking for null, and then returns it to the caller.

If the Y.Doc isn't ready or the study doesn't exist, the PDF file will have been uploaded to R2 but won't be tracked in Y.js, causing orphaned files.

Consider returning a consistent result or throwing an error on failure so callers can handle it appropriately.

🤖 Prompt for AI Agents
In packages/web/src/primitives/useProject/pdfs.js around lines 63 to 99, the
function addPdfToStudy currently returns null on early exits (ydoc missing or
study missing) but returns a pdfId string on success, which leads to callers
(e.g., handleUploadPdf) treating a null like a valid id and leaving uploaded
files orphaned. Change the function to return a consistent failure signal by
throwing a descriptive Error when the Y.Doc isn't ready or the study doesn't
exist (e.g., throw new Error('YDoc not ready') / throw new Error('Study not
found')), or alternatively return a consistent object { success: false, reason:
'...' } and on success return { success: true, id: pdfId }; then update callers
(handleUploadPdf and any other callers) to catch the thrown errors or check the
success flag and abort cleanup/upload handling so files are not left orphaned.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
packages/ui/src/zag/Tour.jsx (1)

59-65: Spread order fixed, but ID generation issue persists.

The spread order has been corrected—defaults are now properly placed before ...machineProps, allowing user-provided props to override them as documented. However, the previous review's concern about calling createUniqueId() inside the factory function remains unaddressed.

Per SolidJS best practices and the pattern shown in packages/ui/src/zag/Editable.jsx (lines 105-127), the ID should be generated once in the component body and then referenced inside the factory to ensure stability.

🔎 Recommended pattern (as shown in Editable.jsx):
+  const id = createUniqueId();
+
   const service = useMachine(tour.machine, () => ({
-    id: createUniqueId(),
+    id,
     closeOnInteractOutside: true,
     closeOnEscape: true,
     keyboardNavigation: true,
     ...machineProps,
   }));
packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx (1)

46-62: Invalid publication year handling unchanged.

The publication year validation still silently converts invalid input to 0 without user feedback. This issue was previously flagged in an earlier review.

packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (1)

165-165: Add null validation for addPdfToStudy return value before setting state.

The past review comment identified that addPdfToStudy can return null when the document isn't synced or the study doesn't exist. Line 165 calls setSelectedPdfId(pdfId) without checking if pdfId is valid, which could set the state to null and cause unexpected UI behavior.

🔎 Apply this diff to validate the return value:
-      setSelectedPdfId(pdfId);
+      if (pdfId) {
+        setSelectedPdfId(pdfId);
+      }
🧹 Nitpick comments (2)
packages/web/src/primitives/useProject/pdfs.js (1)

190-200: Consider explicit handling of falsy numeric values.

Line 194's condition treats publicationYear: 0 as a removal signal (since 0 !== '' is true but the overall logic deletes it). While 0 is not a valid publication year, the implicit behavior may be unclear to callers. Consider documenting this or adding an explicit check for numeric fields.

Suggested approach
   for (const field of fields) {
     if (field in metadata) {
-      if (metadata[field] !== undefined && metadata[field] !== null && metadata[field] !== '') {
+      const value = metadata[field];
+      // For numeric fields like publicationYear, treat 0 as deletion
+      if (value !== undefined && value !== null && value !== '' && value !== 0) {
         pdfYMap.set(field, metadata[field]);
       } else {
         pdfYMap.delete(field);
       }
     }
   }
packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (1)

304-311: Consider showing PDF info when only one PDF exists.

The PdfSelector is hidden when studyPdfs().length <= 1, which means users won't see any PDF information in the header for single-PDF studies. While the dropdown isn't needed, displaying the PDF name and tag (for primary/protocol PDFs) could improve context awareness.

💡 Example: Show single PDF info without dropdown
-      {/* PDF Selector - only shown when multiple PDFs exist */}
-      <Show when={studyPdfs().length > 1}>
+      {/* PDF Selector */}
+      <Show when={studyPdfs().length > 0}>
+        <Show when={studyPdfs().length > 1} fallback={
+          <div class='flex items-center gap-2 text-sm text-gray-600'>
+            <CgFileDocument class='w-4 h-4' />
+            <span class='truncate max-w-xs'>{currentPdf()?.fileName}</span>
+          </div>
+        }>
          <PdfSelector
            pdfs={studyPdfs()}
            selectedPdfId={selectedPdfId()}
            onSelect={handlePdfSelect}
          />
+        </Show>
       </Show>

Note: You would need to import CgFileDocument from solid-icons/cg.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35a9c0f and 5c7fe56.

📒 Files selected for processing (14)
  • packages/ui/src/zag/Accordion.jsx (2 hunks)
  • packages/ui/src/zag/Clipboard.jsx (3 hunks)
  • packages/ui/src/zag/Combobox.jsx (3 hunks)
  • packages/ui/src/zag/Menu.jsx (4 hunks)
  • packages/ui/src/zag/Progress.jsx (2 hunks)
  • packages/ui/src/zag/RadioGroup.jsx (2 hunks)
  • packages/ui/src/zag/ToggleGroup.jsx (3 hunks)
  • packages/ui/src/zag/Tour.jsx (1 hunks)
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (5 hunks)
  • packages/web/src/components/project-ui/ProjectView.jsx (4 hunks)
  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx (1 hunks)
  • packages/web/src/primitives/useProject/pdfs.js (3 hunks)
  • packages/web/src/primitives/useProjectPdfHandlers.js (2 hunks)
  • packages/web/src/primitives/useProjectStudyHandlers.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/zag/Progress.jsx
  • packages/ui/src/zag/RadioGroup.jsx
  • packages/ui/src/zag/Combobox.jsx
  • packages/web/src/components/project-ui/ProjectView.jsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Clipboard.jsx
  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/ui/src/zag/Tour.jsx
  • packages/ui/src/zag/Menu.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
  • packages/web/src/primitives/useProject/pdfs.js
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using config files rather than hardcoding values

Files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Clipboard.jsx
  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/ui/src/zag/Tour.jsx
  • packages/ui/src/zag/Menu.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
  • packages/web/src/primitives/useProject/pdfs.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Import stores directly where needed instead of passing values through multiple components
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

packages/web/src/**/*.{js,jsx,ts,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/web/src/primitives/useProjectPdfHandlers.js
  • packages/web/src/primitives/useProject/pdfs.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Use responsive design principles for UI components
Use Zag.js for UI components and design system. Zag components exist in packages/web/src/components/zag/* and should be reused

Files:

  • packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx
  • packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
🧠 Learnings (9)
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date

Applied to files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Tour.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date

Applied to files:

  • packages/ui/src/zag/Accordion.jsx
  • packages/ui/src/zag/Tour.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)

Applied to files:

  • packages/ui/src/zag/Tour.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : When you have complex state or state objects, use Solid's `createStore` for better performance and reactivity

Applied to files:

  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use Solid's createStore for complex state or state objects for better performance and reactivity

Applied to files:

  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context

Applied to files:

  • packages/ui/src/zag/Tour.jsx
  • packages/ui/src/zag/ToggleGroup.jsx
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering

Applied to files:

  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering

Applied to files:

  • packages/ui/src/zag/Tour.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Components should receive at most 1–5 props, and only for local configuration, not shared state

Applied to files:

  • packages/ui/src/zag/ToggleGroup.jsx
🧬 Code graph analysis (10)
packages/ui/src/zag/Accordion.jsx (10)
packages/ui/src/zag/Clipboard.jsx (2)
  • service (23-27)
  • service (90-94)
packages/ui/src/zag/Menu.jsx (1)
  • service (41-46)
packages/ui/src/zag/Progress.jsx (1)
  • service (29-34)
packages/ui/src/zag/RadioGroup.jsx (1)
  • service (22-26)
packages/ui/src/zag/ToggleGroup.jsx (1)
  • service (25-32)
packages/ui/src/zag/Tour.jsx (1)
  • service (59-65)
packages/ui/src/zag/Editable.jsx (1)
  • service (106-128)
packages/ui/src/zag/Avatar.jsx (1)
  • service (11-14)
packages/ui/src/zag/Collapsible.jsx (1)
  • service (22-30)
packages/ui/src/zag/Dialog.jsx (2)
  • service (25-31)
  • service (117-130)
packages/ui/src/zag/Clipboard.jsx (5)
packages/ui/src/zag/Accordion.jsx (1)
  • service (22-26)
packages/ui/src/zag/Combobox.jsx (1)
  • service (52-69)
packages/ui/src/zag/Progress.jsx (1)
  • service (29-34)
packages/ui/src/zag/Avatar.jsx (1)
  • service (11-14)
packages/ui/src/zag/Dialog.jsx (2)
  • service (25-31)
  • service (117-130)
packages/web/src/components/project-ui/all-studies/EditPdfMetadataModal.jsx (1)
packages/ui/src/zag/Dialog.jsx (1)
  • Dialog (18-92)
packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (1)
packages/web/src/components/checklist-ui/pdf/PdfSelector.jsx (1)
  • PdfSelector (15-99)
packages/web/src/primitives/useProjectStudyHandlers.js (3)
packages/web/src/api/better-auth-store.js (1)
  • user (147-170)
packages/web/src/components/profile-ui/ProfilePage.jsx (1)
  • user (33-33)
packages/web/src/api/pdf-api.js (1)
  • deletePdf (113-127)
packages/web/src/primitives/useProjectPdfHandlers.js (2)
packages/web/src/primitives/pdfCache.js (3)
  • getCachedPdf (82-102)
  • cachePdf (111-138)
  • removeCachedPdf (146-163)
packages/web/src/api/pdf-api.js (3)
  • downloadPdf (79-93)
  • uploadPdf (40-70)
  • deletePdf (113-127)
packages/ui/src/zag/Tour.jsx (15)
packages/ui/src/zag/Accordion.jsx (1)
  • service (22-26)
packages/ui/src/zag/Clipboard.jsx (2)
  • service (23-27)
  • service (90-94)
packages/ui/src/zag/Combobox.jsx (1)
  • service (52-69)
packages/ui/src/zag/Menu.jsx (1)
  • service (41-46)
packages/ui/src/zag/Progress.jsx (1)
  • service (29-34)
packages/ui/src/zag/RadioGroup.jsx (1)
  • service (22-26)
packages/ui/src/zag/ToggleGroup.jsx (1)
  • service (25-32)
packages/ui/src/zag/Editable.jsx (1)
  • service (106-128)
packages/ui/src/zag/Avatar.jsx (1)
  • service (11-14)
packages/ui/src/zag/Collapsible.jsx (1)
  • service (22-30)
packages/ui/src/zag/Checkbox.jsx (1)
  • service (39-49)
packages/ui/src/zag/Dialog.jsx (2)
  • service (25-31)
  • service (117-130)
packages/ui/src/zag/FileUpload.jsx (1)
  • service (123-148)
packages/ui/src/zag/FloatingPanel.jsx (1)
  • service (39-58)
packages/ui/src/zag/Toast.jsx (1)
  • machineProps (22-26)
packages/ui/src/zag/Menu.jsx (4)
packages/ui/src/zag/Accordion.jsx (3)
  • local (20-20)
  • service (22-26)
  • api (28-28)
packages/ui/src/zag/Popover.jsx (1)
  • local (28-37)
packages/ui/src/zag/Avatar.jsx (2)
  • service (11-14)
  • api (16-16)
packages/ui/src/zag/Checkbox.jsx (2)
  • service (39-49)
  • api (51-51)
packages/ui/src/zag/ToggleGroup.jsx (5)
packages/ui/src/zag/Accordion.jsx (2)
  • service (22-26)
  • api (28-28)
packages/ui/src/zag/Clipboard.jsx (4)
  • service (23-27)
  • service (90-94)
  • api (29-29)
  • api (96-96)
packages/ui/src/zag/Combobox.jsx (2)
  • service (52-69)
  • api (71-71)
packages/ui/src/zag/RadioGroup.jsx (3)
  • service (22-26)
  • isVertical (30-30)
  • api (28-28)
packages/ui/src/zag/Avatar.jsx (2)
  • service (11-14)
  • api (16-16)
packages/web/src/primitives/useProject/pdfs.js (2)
packages/web/src/primitives/useProject/index.js (2)
  • ydoc (29-29)
  • getYDoc (48-48)
packages/web/src/primitives/useProject/sync.js (1)
  • pdfsMap (109-109)
⏰ 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: corates
🔇 Additional comments (16)
packages/ui/src/zag/ToggleGroup.jsx (2)

25-32: Previous issue resolved - props now correctly override defaults.

The initialization order has been fixed. By placing defaults before ...machineProps, user-provided values now properly override the defaults (e.g., orientation="vertical" will work as documented), matching the pattern used in other Zag component wrappers like Accordion, RadioGroup, and Clipboard.


47-47: LGTM - consistent with service-based API pattern.

Accessing orientation via api().orientation correctly aligns with the refactored initialization pattern and matches similar usage in RadioGroup.jsx.

packages/ui/src/zag/Menu.jsx (1)

97-117: Conditional indicator rendering implemented correctly.

The trigger button properly integrates the new hideIndicator prop, conditionally rendering the chevron indicator based on the prop value. The implementation maintains accessibility and follows the existing styling patterns.

packages/web/src/primitives/useProject/pdfs.js (3)

25-39: LGTM!

The getPdfsMap helper properly handles null-safety and lazy initialization of the PDFs map.


45-54: LGTM!

The clearTag helper correctly enforces the single primary/protocol constraint by demoting conflicting PDFs to secondary.


64-104: Error handling improved.

The function now throws descriptive errors instead of returning null, which addresses the previous review concern about orphaned files. Callers can now handle failures appropriately.

packages/web/src/primitives/useProjectStudyHandlers.js (2)

78-92: Cleanup pattern is appropriate.

The fire-and-forget cleanup on line 90 (not awaited before rethrowing) is acceptable since it prevents blocking the error flow. Failed cleanup is logged via .catch(console.warn).


213-225: LGTM!

The cleanup pattern on metadata failure is consistent with the Google Drive import flow. The orphaned file is deleted without blocking the error handling.

packages/web/src/primitives/useProjectPdfHandlers.js (5)

57-83: LGTM!

The cache-first download approach is efficient, and resource cleanup with URL.revokeObjectURL is properly handled.


107-119: LGTM!

The function properly captures and returns the generated PDF ID, allowing callers to reference the newly uploaded PDF.


137-150: LGTM!

The delete handler properly coordinates server deletion, Y.js removal, and cache cleanup.


175-204: LGTM!

The Google Drive import handler applies consistent tagging logic and cleanup patterns as the upload flow.


96-98: No action needed. The code correctly accesses the pdfs property on study objects. Verification confirms that studies are initialized with pdfs: [] in the sync layer, and PDFs are properly populated into this array when synced from Y.js. The implementation at lines 96-98 is correct.

packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx (3)

50-72: Well-structured reactive state management for multi-PDF support.

The memo chain (studyPdfs → defaultPdf → currentPdf) correctly uses createMemo for derived values and implements a sensible fallback hierarchy: selected PDF → primary PDF → first available → null. Safe access patterns with optional chaining and fallback values are used throughout.


180-199: Correctly adapted for multi-PDF deletion.

The delete flow properly retrieves the current PDF, uses pdf.id for Y.js metadata removal (instead of fileName), and thoroughly cleans up state including the new selectedPdfId. The early return guard at line 183 prevents errors when no PDF is selected.


146-147: Auto-tagging logic limits tag specification to upload time; post-upload modifications are available.

The current implementation auto-tags new PDFs as 'primary' if no PDFs exist, otherwise 'secondary'. While updatePdfTag() and handleTagChange() support changing tags to any value ('primary', 'protocol', 'secondary') after upload, users cannot:

  • Upload a protocol PDF directly
  • Specify tags during the upload process

This means users must upload first, then manually change the tag via the tag selection UI—a two-step workflow instead of one-step specification. For the PR objective "Allow multiple pdf upload and tagging", consider whether tag selection during upload would improve UX, or if the current post-upload modification approach is sufficient.

Comment on lines +22 to +26
const service = useMachine(accordion.machine, () => ({
id: createUniqueId(),
collapsible: true,
...machineProps,
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Spread order fixed; consider moving ID generation outside the factory.

The default prop override issue from the previous review has been resolved—collapsible and other defaults now correctly appear before ...machineProps, allowing user configuration to take precedence.

However, following the pattern demonstrated in packages/ui/src/zag/Editable.jsx (lines 105-127), calling createUniqueId() inside the factory function should be avoided. Generate the ID once in the component body to ensure stability.

🔎 Apply this pattern for consistency:
+  const id = createUniqueId();
+
   const service = useMachine(accordion.machine, () => ({
-    id: createUniqueId(),
+    id,
     collapsible: true,
     ...machineProps,
   }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const service = useMachine(accordion.machine, () => ({
id: createUniqueId(),
collapsible: true,
...machineProps,
}));
const id = createUniqueId();
const service = useMachine(accordion.machine, () => ({
id,
collapsible: true,
...machineProps,
}));
🤖 Prompt for AI Agents
packages/ui/src/zag/Accordion.jsx around lines 22 to 26: currently
createUniqueId() is called inside the useMachine factory; move ID generation out
of the factory so the ID is created once per component render (e.g., generate
the ID in the component body before calling useMachine) and then pass that
stable id into the factory parameters, ensuring the id does not get re-generated
on each factory invocation.

Comment on lines +23 to +27
const service = useMachine(clipboard.machine, () => ({
id: createUniqueId(),
timeout: 3000,
});

const service = useMachine(clipboard.machine, context);
...machineProps,
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Spread order fixed in both components; consider moving ID generation outside the factory.

The default prop override issues from the previous review have been resolved in both the Clipboard and CopyButton components—timeout defaults now correctly precede ...machineProps, allowing user-provided timeout values to override the default.

However, both components call createUniqueId() inside the factory function. Following the pattern in packages/ui/src/zag/Editable.jsx (lines 105-127), generate the ID once in each component body to ensure stability and consistency with best practices.

🔎 Apply this pattern for both components:

For Clipboard component (lines 23-27):

+  const id = createUniqueId();
+
   const service = useMachine(clipboard.machine, () => ({
-    id: createUniqueId(),
+    id,
     timeout: 3000,
     ...machineProps,
   }));

For CopyButton component (lines 90-94):

+  const id = createUniqueId();
+
   const service = useMachine(clipboard.machine, () => ({
-    id: createUniqueId(),
+    id,
     timeout: 3000,
     ...machineProps,
   }));

Also applies to: 90-94

🤖 Prompt for AI Agents
In packages/ui/src/zag/Clipboard.jsx around lines 23-27 and
packages/ui/src/zag/CopyButton.jsx around lines 90-94, move the call to
createUniqueId() out of the useMachine factory so the ID is generated once per
component render (like Editable.jsx lines 105-127); create a const id =
createUniqueId() at the top of the component body and then pass that id into the
useMachine factory return object instead of calling createUniqueId() inside the
factory, ensuring a stable, single ID instance while keeping timeout and
...machineProps order as is.

Comment on lines +32 to +46
const [local, machineProps] = splitProps(props, [
'trigger',
'items',
'inDialog',
'hideIndicator',
'placement',
'class',
]);

const context = mergeProps(machineProps, {
const service = useMachine(menu.machine, () => ({
id: createUniqueId(),
closeOnSelect: true,
positioning: { placement: 'bottom-start' },
});

const service = useMachine(menu.machine, context);
positioning: { placement: local.placement || 'bottom-start' },
...machineProps,
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

New props implemented correctly; consider moving ID generation outside the factory.

The new hideIndicator and placement props are properly extracted and used:

  • placement is correctly converted to the machine's positioning configuration (line 44)
  • hideIndicator is appropriately used to conditionally render the indicator (line 102)
  • Default values are placed before ...machineProps, allowing user overrides

However, consistent with the pattern in packages/ui/src/zag/Editable.jsx (lines 105-127), createUniqueId() should be called once in the component body rather than inside the factory function to ensure stability.

🔎 Apply this pattern:
+  const id = createUniqueId();
+
   const service = useMachine(menu.machine, () => ({
-    id: createUniqueId(),
+    id,
     closeOnSelect: true,
     positioning: { placement: local.placement || 'bottom-start' },
     ...machineProps,
   }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [local, machineProps] = splitProps(props, [
'trigger',
'items',
'inDialog',
'hideIndicator',
'placement',
'class',
]);
const context = mergeProps(machineProps, {
const service = useMachine(menu.machine, () => ({
id: createUniqueId(),
closeOnSelect: true,
positioning: { placement: 'bottom-start' },
});
const service = useMachine(menu.machine, context);
positioning: { placement: local.placement || 'bottom-start' },
...machineProps,
}));
const [local, machineProps] = splitProps(props, [
'trigger',
'items',
'inDialog',
'hideIndicator',
'placement',
'class',
]);
const id = createUniqueId();
const service = useMachine(menu.machine, () => ({
id,
closeOnSelect: true,
positioning: { placement: local.placement || 'bottom-start' },
...machineProps,
}));
🤖 Prompt for AI Agents
In packages/ui/src/zag/Menu.jsx around lines 32 to 46, createUniqueId() is
currently invoked inside the useMachine factory which can create a new id each
time the factory runs; move the call to createUniqueId() to the top of the
component body (before useMachine) to generate the id once per component
instance, then pass that id into the factory via the id property (keep
closeOnSelect, default positioning and spread ...machineProps as-is).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (1)

170-175: Study count displays during loading state.

The study count section renders regardless of hasData() state, which could show "0 studies" while data is loading. This was flagged in a previous review.

🧹 Nitpick comments (1)
packages/web/src/primitives/useProjectStudyHandlers.js (1)

28-36: Consider adding input validation for robustness.

The helper works correctly for standard PDF filenames. However, consider validating that the filename actually ends with .pdf before processing, or handle cases where the filename might have multiple dots or no extension.

🔎 View suggested enhancement:
 const getStudyNameFromFilename = pdfFileName => {
-  if (!pdfFileName) return 'Untitled Study';
-  return pdfFileName.replace(/\.pdf$/i, '');
+  if (!pdfFileName) return 'Untitled Study';
+  // Only remove .pdf extension if present, otherwise use as-is
+  return pdfFileName.replace(/\.pdf$/i, '') || pdfFileName;
 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c7fe56 and e9de53d.

📒 Files selected for processing (10)
  • packages/web/src/components/project-ui/ProjectHeader.jsx (2 hunks)
  • packages/web/src/components/project-ui/ProjectSettings.jsx (0 hunks)
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/index.js (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1 hunks)
  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx (1 hunks)
  • packages/web/src/lib/__tests__/studyNaming.test.js (0 hunks)
  • packages/web/src/lib/studyNaming.js (0 hunks)
  • packages/web/src/primitives/useProjectStudyHandlers.js (7 hunks)
💤 Files with no reviewable changes (3)
  • packages/web/src/lib/tests/studyNaming.test.js
  • packages/web/src/components/project-ui/ProjectSettings.jsx
  • packages/web/src/lib/studyNaming.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/components/project-ui/all-studies/index.js
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Import stores directly where needed instead of passing values through multiple components
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

packages/web/src/**/*.{js,jsx,ts,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Use responsive design principles for UI components
Use Zag.js for UI components and design system. Zag components exist in packages/web/src/components/zag/* and should be reused

Files:

  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
🧠 Learnings (1)
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use Solid's createStore for complex state or state objects for better performance and reactivity

Applied to files:

  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx
🧬 Code graph analysis (4)
packages/web/src/primitives/useProjectStudyHandlers.js (3)
packages/web/src/primitives/useProject/sync.js (1)
  • study (59-88)
packages/web/src/api/better-auth-store.js (1)
  • user (147-170)
packages/web/src/api/pdf-api.js (1)
  • deletePdf (113-127)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (2)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1)
  • study (30-30)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)
  • study (29-29)
  • pdfs (30-30)
packages/web/src/components/project-ui/ProjectHeader.jsx (2)
packages/web/src/components/project-ui/ProjectContext.jsx (2)
  • useProjectContext (51-57)
  • userRole (18-23)
packages/ui/src/zag/Editable.jsx (1)
  • Editable (68-200)
packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (3)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (2)
  • study (26-26)
  • StudyCardHeader (17-146)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (2)
  • study (29-29)
  • StudyPdfSection (15-176)
packages/ui/src/zag/Collapsible.jsx (1)
  • Collapsible (15-70)
⏰ 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: corates
🔇 Additional comments (11)
packages/web/src/primitives/useProjectStudyHandlers.js (1)

6-6: LGTM!

The import of deletePdf supports the new orphaned file cleanup logic in the error handling paths.

packages/web/src/components/project-ui/tabs/OverviewTab.jsx (1)

16-24: LGTM!

The component follows SolidJS best practices: reads from store directly, uses context for shared handlers, and avoids prop drilling. The removal of ProjectSettings aligns with the PR's broader restructuring.

packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1)

13-69: Well-structured composition component.

The component cleanly separates header and content concerns using the Collapsible pattern. The accessors study() and expanded() maintain proper SolidJS reactivity.

One observation: While the prop count exceeds the typical 1-5 guideline, this is an acceptable pattern for a composition component that forwards callbacks to children. If the prop list grows further, consider grouping related callbacks (e.g., pdfHandlers={{ onView, onDownload, onUpload, onDelete, onTagChange }}).

packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (2)

17-66: Clean component with well-structured helpers.

Good SolidJS patterns:

  • const study = () => props.study preserves reactivity
  • Derived values (primaryPdf, assignedReviewers, citationLine) are properly memoized via function getters
  • handleNameChange correctly validates input before triggering the update

68-145: LGTM!

The menu implementation is clean with a clear handleMenuSelect dispatcher. The UI structure properly uses type='button' for accessibility, and the conditional rendering with Show follows SolidJS best practices.

packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx (2)

22-39: Good state organization.

Clean separation of concerns with signals for different modal states and UI tracking. Using createSignal(new Set()) for expandedStudies is appropriate, and the update pattern in toggleStudyExpanded correctly creates a new Set to trigger reactivity.


151-211: Clean rendering structure.

Good use of Show for conditional rendering with loading/empty states, and proper For iteration for the study list. The StudyCard delegation pattern correctly forwards all necessary handlers.

packages/web/src/components/project-ui/ProjectHeader.jsx (4)

1-14: Excellent simplification and proper reactive patterns!

The refactored imports and state management follow SolidJS best practices:

  • Uses solid-icons library for icons as required by guidelines
  • Props are never destructured (maintains reactivity)
  • canEdit is properly memoized for derived state
  • Component remains lean and focused on presentation

16-20: LGTM - good validation logic!

The name change handler properly validates input and prevents unnecessary updates when the value hasn't changed.


48-58: Clean simplification with the Editable component!

The inline name editing using the Editable component is well-implemented:

  • Properly configured with activationMode, readOnly, and showEditIcon based on user permissions
  • Follows the Zag component reuse guideline
  • Significantly simplifies the code by removing manual state management for name editing

68-112: Well-structured description editing UI!

The description editing implementation follows good practices:

  • Proper use of SolidJS Show for conditional rendering
  • Reactive signal bindings without prop destructuring
  • Good hover affordance (edit button appears on group-hover)
  • Clean Save/Cancel flow with appropriate button styling

Comment on lines +115 to +134
// PDF handlers
const handleViewPdf = (studyId, pdf) => {
handlers.pdfHandlers.handleViewPdf(studyId, pdf);
};

const handleDownloadPdf = (studyId, pdf) => {
handlers.pdfHandlers.handleDownloadPdf?.(studyId, pdf);
};

const handleUploadPdf = async (studyId, file) => {
await handlers.pdfHandlers.handleUploadPdf(studyId, file);
};

const handleDeletePdf = (studyId, pdf) => {
handlers.pdfHandlers.handleDeletePdf?.(studyId, pdf);
};

const handleTagChange = (studyId, pdfId, newTag) => {
handlers.pdfHandlers.handleTagChange?.(studyId, pdfId, newTag);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent optional chaining on PDF handlers.

Some handlers use optional chaining (handleDownloadPdf?., handleDeletePdf?., handleTagChange?.) while others don't (handleViewPdf, handleUploadPdf). This inconsistency could cause runtime errors if a handler is undefined.

Suggested fix for consistency:
 const handleViewPdf = (studyId, pdf) => {
-  handlers.pdfHandlers.handleViewPdf(studyId, pdf);
+  handlers.pdfHandlers.handleViewPdf?.(studyId, pdf);
 };

 const handleUploadPdf = async (studyId, file) => {
-  await handlers.pdfHandlers.handleUploadPdf(studyId, file);
+  await handlers.pdfHandlers.handleUploadPdf?.(studyId, file);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// PDF handlers
const handleViewPdf = (studyId, pdf) => {
handlers.pdfHandlers.handleViewPdf(studyId, pdf);
};
const handleDownloadPdf = (studyId, pdf) => {
handlers.pdfHandlers.handleDownloadPdf?.(studyId, pdf);
};
const handleUploadPdf = async (studyId, file) => {
await handlers.pdfHandlers.handleUploadPdf(studyId, file);
};
const handleDeletePdf = (studyId, pdf) => {
handlers.pdfHandlers.handleDeletePdf?.(studyId, pdf);
};
const handleTagChange = (studyId, pdfId, newTag) => {
handlers.pdfHandlers.handleTagChange?.(studyId, pdfId, newTag);
};
// PDF handlers
const handleViewPdf = (studyId, pdf) => {
handlers.pdfHandlers.handleViewPdf?.(studyId, pdf);
};
const handleDownloadPdf = (studyId, pdf) => {
handlers.pdfHandlers.handleDownloadPdf?.(studyId, pdf);
};
const handleUploadPdf = async (studyId, file) => {
await handlers.pdfHandlers.handleUploadPdf?.(studyId, file);
};
const handleDeletePdf = (studyId, pdf) => {
handlers.pdfHandlers.handleDeletePdf?.(studyId, pdf);
};
const handleTagChange = (studyId, pdfId, newTag) => {
handlers.pdfHandlers.handleTagChange?.(studyId, pdfId, newTag);
};
🤖 Prompt for AI Agents
In packages/web/src/components/project-ui/all-studies/AllStudiesTab.jsx around
lines 115 to 134, the PDF handler calls are inconsistent: some use optional
chaining and others don't, which risks runtime errors if handlers.pdfHandlers or
specific methods are undefined; update the code to consistently guard calls
(either by using optional chaining on handlers.pdfHandlers.method?.(...) for all
calls or by first checking that handlers.pdfHandlers and the specific method
exist before invoking), and ensure async handlers remain awaited where needed
(e.g., await handlers.pdfHandlers.handleUploadPdf?.(studyId, file)).

Comment thread packages/web/src/components/project-ui/ProjectHeader.jsx Outdated
Comment thread packages/web/src/primitives/useProjectStudyHandlers.js
Comment on lines +258 to +268
// Note: Studies require a PDF, so references need PDFs attached
const handleImportReferences = references => {
let successCount = 0;
const namingConvention = getNamingConvention();

for (const ref of references) {
try {
// Preserve the original title before any naming convention transformations
const originalTitle = ref.title || 'Untitled Study';

// Generate study name based on naming convention
const studyName = generateStudyName(ref, namingConvention);
// Use PDF filename if available, otherwise use title temporarily
const studyName =
ref.pdfFileName ?
getStudyNameFromFilename(ref.pdfFileName)
: ref.title || 'Untitled Study';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update misleading comment about PDF attachment.

The comment states "Studies require a PDF, so references need PDFs attached," but this function doesn't actually attach any PDFs—it only creates study records with metadata. The logic uses ref.pdfFileName for naming but doesn't upload or attach the corresponding PDF file.

Consider updating the comment to reflect the actual behavior or documenting that PDFs need to be uploaded separately.

🔎 Apply this diff to clarify the comment:
-  // Handle importing references from Zotero/EndNote files
-  // Note: Studies require a PDF, so references need PDFs attached
+  // Handle importing references from Zotero/EndNote files
+  // Note: Creates study records with metadata. PDFs must be uploaded separately.
   const handleImportReferences = references => {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/web/src/primitives/useProjectStudyHandlers.js around lines 258 to
268, the comment "Studies require a PDF, so references need PDFs attached" is
misleading because this function only creates study metadata and uses
ref.pdfFileName for naming — it does not upload or attach PDFs. Update the
comment to state that the handler only creates study records and derives a study
name from ref.pdfFileName if present, and add a note that actual PDF
upload/attachment must be handled separately (or reference the function
responsible for uploads).

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

♻️ Duplicate comments (1)
packages/web/src/primitives/useProjectStudyHandlers.js (1)

255-255: Clarify comment about PDF handling.

The comment suggests that PDFs are attached in this function, but handleImportReferences only creates study records with metadata and derives study names from PDF filenames when available. It doesn't actually upload or attach PDF files.

🔎 Suggested clarification:
-  // Handle importing references from Zotero/EndNote files
-  // Note: Studies require a PDF, so references need PDFs attached
+  // Handle importing references from Zotero/EndNote files
+  // Note: Creates study records with metadata. Derives study name from PDF filename if available.
🧹 Nitpick comments (7)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (4)

56-68: Minor edge case in citation formatting.

If only journal has a value (no author or year), the result would be "- Journal" with a leading dash. Consider conditionally adding the dash only when there's preceding content.

🔎 Suggested fix
   const citationLine = () => {
     const pdf = primaryPdf();
     const parts = [];
     // Prefer PDF-level metadata
     const author = pdf?.firstAuthor || study().firstAuthor;
     const year = pdf?.publicationYear || study().publicationYear;
     const journal = pdf?.journal || study().journal;
     if (author) parts.push(author);
     if (year) parts.push(`(${year})`);
-    if (journal) parts.push(`- ${journal}`);
+    if (journal) parts.push(parts.length > 0 ? `- ${journal}` : journal);
     return parts.join(' ');
   };

85-94: Consider adding a default case to the switch statement.

While currently only two menu items exist, a default case (even empty or with a comment) would make the code more maintainable when adding future menu actions.

🔎 Suggested fix
   const handleMenuSelect = details => {
     switch (details.value) {
       case 'assign-reviewers':
         props.onAssignReviewers?.();
         break;
       case 'delete':
         props.onDelete?.();
         break;
+      default:
+        break;
     }
   };

129-133: Consider memoizing citationLine() to avoid double computation.

citationLine() is called twice: once in the when condition and once for rendering. Using createMemo would ensure it's computed only once per reactive update.

As per coding guidelines, use createMemo for derived values based on props or state to ensure reactive updates.

🔎 Suggested fix
+import { Show, For, createMemo } from 'solid-js';
-import { Show, For } from 'solid-js';

Then change the function to a memo:

-  // Citation line: author, year, journal from primary PDF
-  const citationLine = () => {
+  // Citation line: author, year, journal from primary PDF (memoized)
+  const citationLine = createMemo(() => {
     const pdf = primaryPdf();
     const parts = [];
     // Prefer PDF-level metadata
     const author = pdf?.firstAuthor || study().firstAuthor;
     const year = pdf?.publicationYear || study().publicationYear;
     const journal = pdf?.journal || study().journal;
     if (author) parts.push(author);
     if (year) parts.push(`(${year})`);
     if (journal) parts.push(`- ${journal}`);
     return parts.join(' ');
-  };
+  });

136-149: Consider using Show with fallback prop for cleaner structure.

Using two separate Show blocks works but the fallback prop provides a more idiomatic pattern.

🔎 Suggested fix
       {/* Reviewer badges */}
-      <Show when={hasReviewers()}>
+      <Show
+        when={hasReviewers()}
+        fallback={<span class='text-xs text-gray-400 italic shrink-0'>No reviewers</span>}
+      >
         <div class='flex flex-wrap gap-1 shrink-0 cursor-default' data-selectable>
           <For each={assignedReviewers()}>
             {name => (
               <span class='inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 select-text cursor-text'>
                 {name}
               </span>
             )}
           </For>
         </div>
       </Show>
-      <Show when={!hasReviewers()}>
-        <span class='text-xs text-gray-400 italic shrink-0'>No reviewers</span>
-      </Show>
packages/web/src/components/project-ui/ProjectHeader.jsx (1)

104-111: Consider adding an accessible label to the description textarea.

The textarea lacks a label or aria-label attribute, which impacts screen reader accessibility. Consider adding an aria-label to improve the experience for users relying on assistive technologies.

🔎 Apply this diff to add an aria-label:
 <textarea
   value={descriptionDraft()}
   onInput={e => setDescriptionDraft(e.target.value)}
+  aria-label="Project description"
   class='w-full px-3 py-2 border border-gray-300 rounded-md text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none disabled:bg-gray-100 disabled:cursor-not-allowed'
   rows={3}
   placeholder='Add a project description...'
   disabled={isSaving()}
 />
packages/web/src/primitives/useProjectStudyHandlers.js (1)

78-96: Consider extracting duplicate error handling pattern.

The try/catch blocks for adding PDF metadata (lines 78-96 and 204-221) follow an identical pattern: call addPdfToStudy, catch errors, clean up orphaned PDFs via deletePdf, and rethrow. Extracting this into a helper function would improve maintainability and reduce duplication.

🔎 Example helper function:
/**
 * Adds PDF metadata to study with automatic orphan cleanup on failure
 */
const addPdfWithCleanup = async (studyId, pdfMetadata, tag) => {
  try {
    addPdfToStudy(studyId, pdfMetadata, tag);
  } catch (metaErr) {
    console.error('Failed to add PDF metadata:', metaErr);
    // Clean up orphaned file
    deletePdf(projectId, studyId, pdfMetadata.fileName).catch(console.warn);
    throw metaErr;
  }
};

Then replace both try/catch blocks with:

await addPdfWithCleanup(studyId, {
  key: importedFile.key,
  fileName: importedFile.fileName,
  size: importedFile.size,
  uploadedBy: user()?.id,
  uploadedAt: Date.now(),
  source: 'google-drive',
}, 'primary');

Also applies to: 204-221

packages/ui/src/index.d.ts (1)

119-142: Consider adding proper prop types incrementally.

The re-exported components use Component<any> and useTour returns any, which reduces type safety. While acceptable for initial implementation, consider defining proper prop interfaces for frequently-used components as you iterate.

As per coding guidelines, TypeScript definitions help catch errors early and improve developer experience.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9de53d and db970c7.

📒 Files selected for processing (6)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/index.d.ts (1 hunks)
  • packages/web/src/components/project-ui/ProjectHeader.jsx (2 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (1 hunks)
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1 hunks)
  • packages/web/src/primitives/useProjectStudyHandlers.js (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/ui/src/index.d.ts
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Import stores directly where needed instead of passing values through multiple components
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

packages/web/src/**/*.{js,jsx,ts,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/web/src/primitives/useProjectStudyHandlers.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Use responsive design principles for UI components
Use Zag.js for UI components and design system. Zag components exist in packages/web/src/components/zag/* and should be reused

Files:

  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/components/project-ui/ProjectHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx
  • packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx
  • packages/ui/package.json
  • packages/web/src/primitives/useProjectStudyHandlers.js
  • packages/ui/src/index.d.ts
🧠 Learnings (17)
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/components/**/*.{js,jsx,ts,tsx} : Use Zag.js for UI components and design system. Zag components exist in `packages/web/src/components/zag/*` and should be reused

Applied to files:

  • packages/ui/package.json
  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/components/zag/**/*.{js,jsx,ts,tsx} : Zag component exist in packages/web/src/components/zag/* and should be reused. Check the README.md in that folder for a list of existing components before adding new components or debugging

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-12-16T18:27:52.769Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.769Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Group related components in subdirectories with an index.js barrel export

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to **/{index,index.js,index.jsx,index.ts,index.tsx} : Group related components in subdirectories with an `index.js` barrel export

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use Zod for schema and input validation

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering

Applied to files:

  • packages/ui/package.json
  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., `ComponentName/` with `index.jsx` and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering

Applied to files:

  • packages/ui/package.json
  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:27:52.769Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.769Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Each file should handle one coherent responsibility

Applied to files:

  • packages/ui/package.json
📚 Learning: 2025-12-16T18:27:52.769Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.769Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use responsive design principles for UI components

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/components/**/*.{js,jsx,ts,tsx} : Use responsive design principles for UI components

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context (when scoped to a feature)

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function (e.g., () => props.name) to ensure they are always up-to-date

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use Solid's createStore for complex state or state objects for better performance and reactivity

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : When you have complex state or state objects, use Solid's `createStore` for better performance and reactivity

Applied to files:

  • packages/ui/src/index.d.ts
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/components/**/*.{js,jsx,ts,tsx} : For UI icons, use the `solid-icons` library or SVGs only. Do not use emojis

Applied to files:

  • packages/ui/src/index.d.ts
🧬 Code graph analysis (4)
packages/web/src/components/project-ui/ProjectHeader.jsx (3)
packages/web/src/components/project-ui/ProjectContext.jsx (2)
  • useProjectContext (51-57)
  • userRole (18-23)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (1)
  • handleNameChange (50-54)
packages/ui/src/index.d.ts (1)
  • Editable (14-14)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (3)
packages/web/src/components/project-ui/all-studies/study-card/StudyPdfSection.jsx (1)
  • study (29-29)
packages/web/src/lib/referenceParser.js (1)
  • author (349-349)
packages/ui/src/zag/Menu.jsx (1)
  • Menu (31-125)
packages/web/src/primitives/useProjectStudyHandlers.js (3)
packages/web/src/primitives/useProject/sync.js (1)
  • study (59-88)
packages/web/src/api/better-auth-store.js (1)
  • user (147-170)
packages/web/src/api/pdf-api.js (1)
  • deletePdf (113-127)
packages/ui/src/index.d.ts (3)
packages/ui/src/zag/Dialog.jsx (1)
  • useConfirmDialog (243-321)
packages/ui/src/lib/cn.js (1)
  • cn (16-18)
packages/ui/src/zag/Tour.jsx (1)
  • useTour (21-27)
⏰ 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: corates
🔇 Additional comments (19)
packages/web/src/components/project-ui/all-studies/study-card/StudyCardHeader.jsx (4)

1-17: LGTM!

Clean documentation header and appropriate imports from solid-js, solid-icons, and the UI library.


28-34: LGTM!

Proper reactive accessor pattern for study() and primaryPdf(). The fallback to pdfs[0] handles the case where no primary PDF exists, and the || [] guard handles missing pdfs array.


36-54: LGTM!

Reviewer accessors and name change handler are well-implemented. The validation in handleNameChange correctly checks for non-empty trimmed value and only updates when changed.


96-106: LGTM!

The handleHeaderClick implementation correctly prevents toggle when interacting with buttons, inputs, menus, and editable areas. The selector is comprehensive and well-thought-out.

packages/web/src/components/project-ui/all-studies/study-card/StudyCard.jsx (4)

1-12: LGTM!

Clean documentation and appropriate imports. The component composition pattern is well-documented.


13-31: High prop count but acceptable for composition component.

This component receives many props (~15), which typically exceeds the 1-5 guideline. However, as a thin composition wrapper that forwards callbacks to children, this pattern is reasonable. The alternative would be using context, but that might add unnecessary complexity for this localized component hierarchy.

Consider grouping related callbacks into objects (e.g., pdfActions, studyActions) if the prop list grows further.


35-53: LGTM!

Good defensive check in onOpenChange to prevent redundant toggles. The trigger as a function returning StudyCardHeader is the correct pattern for Collapsible integration.


54-68: LGTM!

Clean prop forwarding to StudyPdfSection with appropriate visual separation. All PDF-related callbacks are properly passed through.

packages/web/src/components/project-ui/ProjectHeader.jsx (1)

28-49: LGTM! Proper async handling and error management.

The description save flow correctly awaits the async update, handles errors with user feedback via toast, and only closes the edit panel on success. This properly addresses the concerns from previous reviews.

packages/ui/src/index.d.ts (9)

3-14: LGTM - Editable type definitions.

The EditableProps interface is well-defined with appropriate activation modes and variants.


16-25: LGTM - Collapsible type definitions.

The CollapsibleProps interface properly defines controlled/uncontrolled state management with a custom trigger render prop.


27-42: LGTM - Menu type definitions.

The MenuItem and MenuProps interfaces are comprehensive, including the new hideIndicator option for customizing menu appearance.


44-52: LGTM - Dialog type definitions.

Standard dialog interface with appropriate props for controlled state and content.


72-84: LGTM - Select type definitions.

The SelectOption and SelectProps interfaces properly define a controlled select component.


86-92: LGTM - Tooltip type definitions.

The TooltipProps interface appropriately allows both string and JSX content.


94-109: LGTM - Toast type definitions.

The toast API is well-structured with a Toaster component, imperative toaster methods, and a showToast helper.


112-114: LGTM - useWindowDrag type definition.

Clean primitive hook interface for managing window drag state.


117-117: LGTM - cn utility type definition.

The cn function signature correctly accepts conditional class values and matches the implementation using clsx and twMerge.

packages/ui/package.json (1)

8-13: LGTM - Proper TypeScript support configuration.

The changes correctly expose TypeScript declarations:

  • Top-level types field for backward compatibility
  • Conditional exports with types for modern module resolution
  • Maintains default export for runtime code

This follows standard practices for dual-mode packages with TypeScript support.

Comment on lines +66 to +70
export function useConfirmDialog(): {
open: () => boolean;
setOpen: (_open: boolean) => void;
confirm: () => Promise<boolean>;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix incomplete and incorrect return type for useConfirmDialog.

The declared return type doesn't match the actual implementation shown in Dialog.jsx. The actual hook returns additional properties and has different signatures:

Based on the implementation in Dialog.jsx (lines 242-320):

  • isOpen: () => boolean (not open: () => boolean)
  • open: (dialogConfig) => Promise<boolean> (takes config, returns Promise, not a getter)
  • close: () => void (missing)
  • setLoading: (loading: boolean) => void (missing)
  • ConfirmDialogComponent: () => JSX.Element (missing)
  • dialogProps: () => {...} (missing)
Apply this diff to correct the return type:
-export function useConfirmDialog(): {
-  open: () => boolean;
-  setOpen: (_open: boolean) => void;
-  confirm: () => Promise<boolean>;
-};
+export function useConfirmDialog(): {
+  isOpen: () => boolean;
+  open: (_config: {
+    title?: string;
+    description?: string;
+    confirmText?: string;
+    cancelText?: string;
+    variant?: 'default' | 'destructive';
+  }) => Promise<boolean>;
+  close: () => void;
+  setLoading: (_loading: boolean) => void;
+  ConfirmDialogComponent: () => JSX.Element;
+  dialogProps: () => {
+    open: boolean;
+    onOpenChange: (_newOpen: boolean) => void;
+    onConfirm: () => void;
+    loading: boolean;
+    title?: string;
+    description?: string;
+    confirmLabel?: string;
+    cancelLabel?: string;
+    destructive?: boolean;
+  };
+};
🤖 Prompt for AI Agents
In packages/ui/src/index.d.ts around lines 66 to 70, the declared return type
for useConfirmDialog is incomplete and incorrect compared to Dialog.jsx; update
the type to match the implementation: replace the current shape with an object
exposing isOpen(): boolean, open(dialogConfig): Promise<boolean>, close(): void,
setLoading(loading: boolean): void, ConfirmDialogComponent(): JSX.Element, and
dialogProps(): { title?: string; description?: string; confirmText?: string;
cancelText?: string; hideCancel?: boolean; loading?: boolean; intent?: string; }
(adjust property names/types to match Dialog.jsx), ensuring imports/types (e.g.,
JSX.Element) are available in the d.ts file.

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.

Multiple PDFs

2 participants