Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ secrets.txt
openapi.json

# Temp files
/docs/plans/*
/docs/plans*
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the .gitignore file around line 53
cat -n .gitignore | sed -n '45,60p'

Repository: InfinityBowman/corates

Length of output: 229


🏁 Script executed:

# Check if docs/plans directory exists
find . -type d -name "plans" 2>/dev/null | head -20

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

# Check the current state of docs directory
ls -la docs/ 2>/dev/null || echo "docs directory not found"

Repository: InfinityBowman/corates

Length of output: 561


Fix: Pattern change conflicts with documented guidelines for storing plans.

The pattern /docs/plans* ignores the docs/plans/ directory itself, not just its contents. Git patterns with /* match direct contents only, while patterns without the trailing / match the directory itself and any paths matching the glob pattern. Since the learnings from .github/copilot-instructions.md and .cursorrules state "Place plans in the docs/plans/ directory," this pattern prevents that directory from being tracked.

If committed plan files should live in docs/plans/, change the pattern to /docs/plans/* to ignore only the directory contents while allowing the directory structure to be tracked. Otherwise, update the documentation to reflect that plans should not be committed to the repository.

🤖 Prompt for AI Agents
In .gitignore around line 53, the pattern "/docs/plans*" unintentionally ignores
the docs/plans/ directory itself (preventing the tracked directory and its
intended plan files), so replace it with "/docs/plans/*" to ignore only the
contents while allowing the docs/plans/ directory to be tracked; alternatively,
if plans must not be committed, update the documentation to state that and keep
or adjust the ignore pattern accordingly.

46 changes: 46 additions & 0 deletions packages/landing/public/bimi.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions packages/web/src/components/checklist-ui/ChecklistWithPdf.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export default function ChecklistWithPdf(props) {
// props.onPdfChange - callback when PDF changes: (data, fileName) => void
// props.onPdfClear - callback when PDF is cleared
// props.readOnly - if true, disables checklist updates and PDF uploads
// props.allowDelete - if true, shows PDF delete button (only applies when !readOnly)
// props.pdfs - array of PDFs for multi-PDF selection
// props.selectedPdfId - currently selected PDF ID
// props.onPdfSelect - handler for PDF selection change

return (
<div class='h-full flex flex-col bg-blue-50'>
Expand All @@ -44,6 +48,10 @@ export default function ChecklistWithPdf(props) {
onPdfChange={props.onPdfChange}
onPdfClear={props.onPdfClear}
readOnly={props.readOnly}
allowDelete={props.allowDelete}
pdfs={props.pdfs}
selectedPdfId={props.selectedPdfId}
onPdfSelect={props.onPdfSelect}
/>
</SplitScreenLayout>
</div>
Expand Down
55 changes: 11 additions & 44 deletions packages/web/src/components/checklist-ui/ChecklistYjsWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import ChecklistWithPdf from '@checklist-ui/ChecklistWithPdf.jsx';
import useProject from '@/primitives/useProject/index.js';
import projectStore from '@/stores/projectStore.js';
import { downloadPdf, uploadPdf, deletePdf } from '@api/pdf-api.js';
import { getCachedPdf, cachePdf, removeCachedPdf } from '@primitives/pdfCache.js';
import { getCachedPdf, cachePdf } from '@primitives/pdfCache.js';
import { showToast, useConfirmDialog } from '@corates/ui';
import { useBetterAuth } from '@api/better-auth-store.js';
import { getChecklistTypeFromState, scoreChecklistOfType } from '@/checklist-registry';
import { IoChevronBack } from 'solid-icons/io';
import ScoreTag from '@/components/checklist-ui/ScoreTag.jsx';
import { PdfSelector } from '@checklist-ui/pdf/index.js';

export default function ChecklistYjsWrapper() {
const params = useParams();
Expand All @@ -24,13 +23,9 @@ export default function ChecklistYjsWrapper() {
const [selectedPdfId, setSelectedPdfId] = createSignal(null);

// Use full hook for write operations
const {
updateChecklistAnswer,
updateChecklist,
getChecklistData,
addPdfToStudy,
removePdfFromStudy,
} = useProject(params.projectId);
const { updateChecklistAnswer, updateChecklist, getChecklistData, addPdfToStudy } = useProject(
params.projectId,
);

// Read data directly from store for faster reactivity
const connectionState = () => projectStore.getConnectionState(params.projectId);
Expand Down Expand Up @@ -88,17 +83,15 @@ export default function ChecklistYjsWrapper() {
createEffect(() => {
const pdf = currentPdf();
const fileName = pdf?.fileName;
selectedPdfId(); // Explicitly track selection changes

// Skip if no PDF, already loaded this file, or currently loading
if (!fileName || attemptedPdfFile() === fileName || pdfLoading()) {
return;
}

// Clear previous PDF data when switching
if (attemptedPdfFile() && attemptedPdfFile() !== fileName) {
setPdfData(null);
setPdfFileName(null);
}
// Don't clear previous PDF - keep it visible until new one loads
// This prevents flashing empty state during transitions

setAttemptedPdfFile(fileName);
setPdfLoading(true);
Expand Down Expand Up @@ -179,27 +172,6 @@ export default function ChecklistYjsWrapper() {
}
};

// Handle PDF clear (delete current PDF)
const handlePdfClear = async () => {
const pdf = currentPdf();
if (!pdf) return;

try {
await deletePdf(params.projectId, params.studyId, pdf.fileName);
// Update Y.js to remove PDF metadata
removePdfFromStudy(params.studyId, pdf.id);
// Remove from cache
removeCachedPdf(params.projectId, params.studyId, pdf.fileName);
setPdfData(null);
setPdfFileName(null);
setSelectedPdfId(null);
setAttemptedPdfFile(null);
} catch (err) {
console.error('Failed to delete PDF:', err);
showToast.error('Delete Failed', 'Failed to delete PDF');
}
};

// Build the checklist object in the format AMSTAR2Checklist expects
const checklistForUI = createMemo(() => {
const checklist = currentChecklist();
Expand Down Expand Up @@ -329,14 +301,6 @@ export default function ChecklistYjsWrapper() {
{currentChecklist()?.type || 'AMSTAR2'} Checklist
</span>
</div>
{/* PDF Selector - only shown when multiple PDFs exist */}
<Show when={studyPdfs().length > 1}>
<PdfSelector
pdfs={studyPdfs()}
selectedPdfId={selectedPdfId()}
onSelect={handlePdfSelect}
/>
</Show>
<div class='ml-auto flex items-center gap-3'>
<ScoreTag currentScore={currentScore()} checklistType={checklistType()} />
<Show
Expand Down Expand Up @@ -393,8 +357,11 @@ export default function ChecklistYjsWrapper() {
pdfData={pdfData()}
pdfFileName={pdfFileName()}
onPdfChange={handlePdfChange}
onPdfClear={handlePdfClear}
readOnly={isReadOnly()}
allowDelete={false}
pdfs={studyPdfs()}
selectedPdfId={selectedPdfId()}
onPdfSelect={handlePdfSelect}
/>
</Show>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export default function LocalChecklistView() {
pdfFileName={pdfFileName()}
onPdfChange={handlePdfChange}
onPdfClear={handlePdfClear}
allowDelete={true}
/>
</Show>
</Show>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export default function ReconciliationWithPdf(props) {
// props.pdfData - ArrayBuffer of the study PDF (optional)
// props.pdfFileName - Name of the PDF file (optional)
// props.pdfLoading - Whether PDF is still loading
// props.pdfs - Array of PDFs for multi-PDF selection
// props.selectedPdfId - Currently selected PDF ID
// props.onPdfSelect - Handler for PDF selection change

// Layout state
const [showPdf, setShowPdf] = createSignal(true);
Expand Down Expand Up @@ -194,7 +197,14 @@ export default function ReconciliationWithPdf(props) {
</div>
}
>
<PdfViewer pdfData={props.pdfData} pdfFileName={props.pdfFileName} readOnly={true} />
<PdfViewer
pdfData={props.pdfData}
pdfFileName={props.pdfFileName}
readOnly={true}
pdfs={props.pdfs}
selectedPdfId={props.selectedPdfId}
onPdfSelect={props.onPdfSelect}
/>
</Show>
</div>
</Show>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,56 @@ export default function ReconciliationWrapper() {
const [pdfData, setPdfData] = createSignal(null);
const [pdfFileName, setPdfFileName] = createSignal(null);
const [pdfLoading, setPdfLoading] = createSignal(false);
const [selectedPdfId, setSelectedPdfId] = createSignal(null);

// Get study PDF info
const studyPdf = createMemo(() => {
// Get all PDFs from the study
const studyPdfs = createMemo(() => {
const study = currentStudy();
if (!study || !study.pdfs || study.pdfs.length === 0) return null;
return study.pdfs[0]; // Use the first PDF
return study?.pdfs || [];
});

// Get the primary PDF or first PDF as default selection
const defaultPdf = createMemo(() => {
const pdfs = studyPdfs();
if (!pdfs.length) return null;
// Prefer primary, then first available
return pdfs.find(p => p.tag === 'primary') || pdfs[0];
});

// The currently selected PDF (or default)
const currentPdf = createMemo(() => {
const pdfs = studyPdfs();
const selected = selectedPdfId();
if (selected) {
return pdfs.find(p => p.id === selected) || defaultPdf();
}
return defaultPdf();
});

// Track which PDF we've attempted to load (to prevent infinite retries)
const [attemptedPdfFile, setAttemptedPdfFile] = createSignal(null);

// Load PDF when study has one - try cache first, then cloud
// Auto-select primary PDF when study loads
createEffect(() => {
const pdf = studyPdf();
const pdf = defaultPdf();
if (pdf && !selectedPdfId()) {
setSelectedPdfId(pdf.id);
}
});

// Load PDF when selection changes - try cache first, then cloud
createEffect(() => {
const pdf = currentPdf();
const fileName = pdf?.fileName;

// Skip if no PDF, already loaded, currently loading, or already attempted this file
if (!fileName || pdfData() || pdfLoading() || attemptedPdfFile() === fileName) {
// Skip if no PDF, already loaded this file, or currently loading
if (!fileName || attemptedPdfFile() === fileName || pdfLoading()) {
return;
}

// Don't clear previous PDF - keep it visible until new one loads
// This prevents flashing empty state during transitions

setAttemptedPdfFile(fileName);
setPdfLoading(true);

Expand Down Expand Up @@ -98,6 +127,13 @@ export default function ReconciliationWrapper() {
});
});

// Handle PDF selection change
const handlePdfSelect = pdfId => {
setSelectedPdfId(pdfId);
// Reset attempted file to trigger reload
setAttemptedPdfFile(null);
};

// Get checklist metadata from store
const checklist1Meta = createMemo(() => {
const study = currentStudy();
Expand Down Expand Up @@ -273,6 +309,9 @@ export default function ReconciliationWrapper() {
pdfData={pdfData()}
pdfFileName={pdfFileName()}
pdfLoading={pdfLoading()}
pdfs={studyPdfs()}
selectedPdfId={selectedPdfId()}
onPdfSelect={handlePdfSelect}
/>
</Show>
</Show>
Expand Down
Loading