Skip to content

207 reconcile for robins#243

Merged
InfinityBowman merged 9 commits into
mainfrom
207-reconcile-for-robins
Jan 7, 2026
Merged

207 reconcile for robins#243
InfinityBowman merged 9 commits into
mainfrom
207-reconcile-for-robins

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Jan 7, 2026

Summary by CodeRabbit

  • New Features

    • Added ROBINS-I reconciliation workflow with split-screen layout for comparing reviewer assessments and PDFs.
    • Introduced routing logic to support both AMSTAR2 and ROBINS-I checklist types.
    • Added Local-First Sync Strategy documentation outlining synchronization approach.
  • Bug Fixes

    • Improved memory management with cleanup on component unmount and PDF cache eviction.
    • Updated UI language from "Checklist" to "Appraisal" in confirmation dialogs and notifications.
  • Documentation

    • Added comprehensive Local-First Sync Roadmap with timelines and implementation phases.

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

@InfinityBowman InfinityBowman linked an issue Jan 7, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete ROBINS-I reconciliation feature with new components for comparing two ROBINS-I checklists across sections and domains in a split-screen layout with PDF viewer integration, adds Local-First Sync documentation, enhances PDF lifecycle management with memory cleanup and LRU caching, updates Dialog wrapper API usage, and refines checklist/appraisal terminology in UI text.

Changes

Cohort / File(s) Summary
Documentation
packages/docs/audits/local-first-roadmap.md, packages/docs/audits/local-first-sync-strategy.md
New markdown documents outlining Local-First Sync strategy with goals, scope, primitives, endpoints, versioning, offline queuing, and phased roadmap with ownership and acceptance criteria
Existing Component Updates
packages/web/src/components/checklist/ChecklistYjsWrapper.jsx
Added onCleanup lifecycle for memory management (pdfData, pdfFileName, selectedPdfId), updated dialog text from "Checklist" to "Appraisal", updated toast messaging
Dialog API Migration
packages/web/src/components/checklist/ROBINSIChecklist/ScoringSummary.jsx
Changed Dialog wrapper from <Dialog ...> to <Dialog.Root ...> preserving open/onOpenChange behavior
PDF Viewer Lifecycle
packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx
Commented out ConsoleLogger, added cleanup on unmount via useEffect to close active document and reset refs, retained error handling
Reconciliation Router
packages/web/src/components/project/reconcile-tab/amstar2-reconcile/ReconciliationWrapper.jsx
Added routing logic to distinguish ROBINS-I from AMSTAR2 via checklistType, imports RobinsIReconciliationWithPdf and CHECKLIST_TYPES, conditionally renders based on isRobinsI flag with new getRobinsText prop
Reconciliation Tab Exports
packages/web/src/components/project/reconcile-tab/index.js
New exports: ReconciliationWrapper (default from amstar2-reconcile), all exports from robins-i-reconcile/index.js
ROBINS-I Core Components
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsIReconciliation.jsx
Main reconciliation view managing navigation across sections (Section B, Domain questions/judgments, Overall judgment, Summary) with localStorage-backed state, checklist comparison, auto-fill on agreement, reset/save flows with confirmation dialogs
ROBINS-I PDF Wrapper
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsIReconciliationWithPdf.jsx
Split-screen wrapper combining RobinsIReconciliation (left) with read-only PDF viewer (right), manages navbarStore for deep reactivity, computes hasPdf from pdfData/pdfLoading, supports multi-PDF navigation
ROBINS-I Navbar
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsINavbar.jsx
Grouped navigation component rendering nav pills per section with status indicators (hasAnswer, isAgreement), critical risk warning, summary button, reset button, tooltip support
ROBINS-I Summary View
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsISummaryView.jsx
Consolidated summary view with grouped sections, item rows showing badges (numeric/label), agreement indicators, value badges, and statistics (total items, agreements, disagreement rate)
ROBINS-I Page Components
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/pages/{SectionBQuestionPage,DomainQuestionPage,DomainJudgementPage,OverallJudgementPage}.jsx
Four page components rendering 3-column reconciliation views (Reviewer 1, Reviewer 2 read-only, Final editable) with domain/question headers, agreement indicators, copy-to-final controls, optional direction rows
ROBINS-I Panel Components
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/panels/{RobinsAnswerPanel,JudgementPanel,DirectionPanel}.jsx
Three reusable panel components: RobinsAnswerPanel (response options with Y.Text comment editor for final), JudgementPanel (ROB judgment selection with badge styling), DirectionPanel (direction option selection with badge styling)
ROBINS-I Utilities
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/navbar-utils.js
Navigation utility functions: NAV_ITEM_TYPES constant, buildNavigationItems, getGroupedNavigationItems, hasNavItemAnswer, isNavItemAgreement, getNavItemPillStyle, getNavItemTooltip, getAnsweredCount, isSectionBCritical, getDomainDisplayNumber
ROBINS-I Module Exports
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/index.js
Central export aggregator re-exporting all ROBINS-I components (RobinsIReconciliation, RobinsIReconciliationWithPdf, RobinsINavbar, RobinsISummaryView), pages (SectionBQuestionPage, DomainQuestionPage, DomainJudgementPage, OverallJudgementPage), panels (RobinsAnswerPanel, JudgementPanel, DirectionPanel), and navbar utilities
PDF Cache Enhancement
packages/web/src/primitives/pdfCache.js
Added LRU eviction: MAX_CACHE_SIZE_BYTES (200 MB), MAX_SINGLE_FILE_SIZE (50 MB), getCacheStats(), evictIfNeeded(), enhanced cachePdf() with file size enforcement and eviction, new getCacheSize() public API

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant RW as RobinsIReconciliation<br/>WithPdf
    participant RIR as RobinsIReconciliation
    participant Comp as Comparison<br/>Logic
    participant Page as Page<br/>Component
    participant Panel as Answer/Judgment<br/>Panel
    participant PDF as PDF Viewer
    
    User->>RW: Navigate to reconciliation
    RW->>RIR: Initialize with checklist data
    RIR->>Comp: compareChecklists(reviewer1, reviewer2)
    Comp-->>RIR: Return comparison object
    RIR->>RIR: buildNavigationItems(protocol)
    RIR->>RW: setNavbarStore(navItems, currentPage, etc.)
    RW->>RW: Render split-screen layout
    RW->>PDF: Load pdfData/pdfUrl (read-only)
    
    User->>RW: Click nav pill or change page
    RW->>RIR: goToPage(index)
    RIR->>Page: Render SectionBQuestionPage<br/>(or DomainQuestionPage)
    Page->>Panel: Render 3 panels<br/>(reviewer1, reviewer2, final)
    
    User->>Panel: Select reviewer answer or<br/>click "Use This"
    Panel->>RIR: onUseThis() or<br/>onAnswerChange(value)
    RIR->>RIR: Update finalAnswers[key]
    RIR->>RIR: Recompute navbarStore
    RW->>RW: Re-render navbar with<br/>updated status indicators
    
    User->>RW: Click Save
    RIR->>RIR: Validate all items answered
    alt All answered
        RIR->>RIR: Show confirmation dialog
        User->>RIR: Confirm save
        RIR->>RIR: Persist reconciliation
        RIR->>RW: Show success toast
    else Missing answers
        RIR->>User: Show validation error
    end
    
    User->>RW: View summary or<br/>navigate summary page
    RIR->>RIR: Render RobinsISummaryView
    RIR->>RIR: getGroupedNavigationItems()
    RIR->>User: Display agreement rate,<br/>item summary cards
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • Reconcile for Robins #207: Implements complete ROBINS-I reconciliation feature (multi-component reconciliation view, navbar, pages, panels, comparison logic) directly addressing the "Reconcile for Robins" requirement

Possibly related PRs

  • 51 scoring for robins #208: Both PRs add and wire ROBINS-I reconciliation UI components, handlers, and utilities alongside related checklist operations and comparison logic
  • Robins i #50: Both PRs implement foundational ROBINS-I feature set including checklist models, comparison/reconciliation utilities, registry wiring, and the new ROBINS-I UI components
  • 82 pdf preview viewer instead of new tab #89: Both PRs enhance PDF viewer integration with lifecycle cleanup, caching improvements (LRU eviction), and support for multi-PDF selection flows used by the ROBINS-I reconciliation split-screen layout
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '207 reconcile for robins' is vague and uses non-descriptive terms. While 'robins' likely refers to ROBINS-I, the title lacks specificity about the core changes (adding reconciliation components, PDF integration, routing logic) and reads more like a ticket reference than a meaningful changeset summary. Consider revising to a more descriptive title like 'Add ROBINS-I reconciliation UI with split-screen PDF viewer' or 'Implement ROBINS-I reconciliation workflow components' to clearly convey the main purpose.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 88.71% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

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

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 1794e07 Commit Preview URL Jan 07 2026, 04:37 PM

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

🤖 Fix all issues with AI agents
In @packages/docs/audits/local-first-roadmap.md:
- Line 7: Update the incorrect cross-reference string
`docs/plans/local-first-sync-strategy.md` to the correct path
`docs/audits/local-first-sync-strategy.md` in the sentence that begins "This
roadmap turns the strategy..." so the link points to the actual file location;
ensure any other occurrences of `docs/plans/local-first-sync-strategy.md` in
this document are likewise replaced with
`docs/audits/local-first-sync-strategy.md`.
- Line 69: The cross-reference in packages/docs/audits/local-first-roadmap.md
incorrectly points to docs/plans/local-first-sync-strategy.md; update that
link/text to reference the actual file path
docs/audits/local-first-sync-strategy.md so the roadmap points to the correct
strategy document.

In @packages/docs/audits/local-first-sync-strategy.md:
- Line 54: Update the phrase "BroadcastChannel based approach" to
"BroadcastChannel-based approach" in the sentence that describes the service
worker and BroadcastChannel approach so the compound modifier is hyphenated
correctly; locate the sentence containing "Service worker & BroadcastChannel
based approach" and replace that fragment with "Service worker &
BroadcastChannel-based approach".
- Around line 117-118: Replace the informal closing "If you want, I can:" with a
more formal alternative that matches the document tone; update the options to
begin with a complete sentence such as "Please indicate whether you would like
me to: (A) generate the endpoint inventory now, (B) move selected Zod schemas to
`packages/shared`, or (C) scaffold `/api/metadata` and CI typegen." Ensure
punctuation and capitalization are consistent with surrounding documentation.

In @packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx:
- Around line 236-255: The cleanup returned from useEffect is currently calling
an async closeActiveDocument without awaiting, which makes the cleanup itself
async; make the cleanup synchronous by defining the async function
(closeActiveDocument) inside the effect but invoking it in a fire-and-forget
manner (for example: void closeActiveDocument(); or
closeActiveDocument().catch(err => console.warn(...))). Ensure you still call
docManagerRef.current.closeDocument(activeDocumentIdRef.current) inside that
async function and preserve the ref-clearing (docManagerRef,
activeDocumentIdRef, previousSelectedPdfIdRef, isLoadingRef) and error logging,
but do not return an async function from useEffect.
- Line 44: Remove the commented-out ConsoleLogger import and all commented
references to ConsoleLogger in this file (delete the "// import { ConsoleLogger
} from '@embedpdf/models';" and any other commented ConsoleLogger usages),
leaving no obsolete commented code paths referencing ConsoleLogger; ensure no
other code depends on it and run a quick build to confirm no unused symbol
warnings remain.

In
@packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsIReconciliationWithPdf.jsx:
- Around line 56-84: The JSX assigned to the headerContent variable is evaluated
once and won't react to changes in props.reviewer1Name, props.reviewer2Name or
props.onCancel; change headerContent into a function (e.g., const headerContent
= () => ( ... )) or inline the JSX where used so Solid can re-evaluate it, then
pass headerContent() (or the function itself if SplitScreenLayout expects a
render function) to SplitScreenLayout; keep the same internal references
(AiOutlineArrowLeft, RobinsINavbar, navbarStore) but ensure you call the
function where headerContent was previously referenced.

In @packages/web/src/primitives/pdfCache.js:
- Around line 16-18: Replace the hardcoded per-file limit with the shared
constant: add an import for PDF_LIMITS from '@corates/shared' at the top of the
file and set MAX_SINGLE_FILE_SIZE to PDF_LIMITS.MAX_SIZE instead of 50 * 1024 *
1024; keep MAX_CACHE_SIZE_BYTES unchanged and mirror the pattern used in
pdfValidation.js so the single-source-of-truth PDF_LIMITS.MAX_SIZE is used
across the codebase.
🧹 Nitpick comments (6)
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/panels/DirectionPanel.jsx (1)

10-25: Consider extracting shared button style helper.

getDirectionButtonStyle is nearly identical to getJudgementButtonStyle in JudgementPanel.jsx. Consider extracting this into a shared utility (e.g., getPanelButtonStyle) to reduce duplication. However, this is optional given the localized scope.

♻️ Potential shared utility

Create a shared helper in a panel-utils.js file:

// panel-utils.js
export function getPanelButtonStyle(isSelected, panelType) {
  if (!isSelected) {
    return 'border-gray-200 bg-white text-gray-700 hover:bg-gray-50';
  }

  switch (panelType) {
    case 'reviewer1':
      return 'border-blue-400 bg-blue-50 text-blue-800';
    case 'reviewer2':
      return 'border-purple-400 bg-purple-50 text-purple-800';
    case 'final':
      return 'border-green-400 bg-green-50 text-green-800';
    default:
      return 'border-gray-400 bg-gray-50 text-gray-800';
  }
}

Then import and use in both DirectionPanel and JudgementPanel.

packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsINavbar.jsx (1)

53-58: Minor: indexOf called per item creates O(n²) complexity.

For each item, indexOf scans the entire array. While acceptable for small navigation lists (typically <30 items), consider pre-computing indices if the list grows:

♻️ Alternative approach
// Pre-compute a Map of item → index
const itemIndexMap = createMemo(() => {
  const map = new Map();
  props.store.navItems?.forEach((item, idx) => map.set(item, idx));
  return map;
});

// Then in the For:
const itemIndex = () => itemIndexMap().get(item) ?? -1;
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsIReconciliation.jsx (2)

68-83: Potential re-initialization issue with localStorage effect.

This effect re-reads localStorage on every checklist1.id or checklist2.id change. If the user navigates and these IDs change, the current page position will be overwritten with the stored value (or reset to 0 if no stored value exists).

Consider adding a flag to only initialize once per component mount:

♻️ Suggested fix
+  const [initialized, setInitialized] = createSignal(false);
+
   // Initialize navigation state from localStorage
   createEffect(() => {
-    if (!props.checklist1?.id || !props.checklist2?.id) return;
+    if (!props.checklist1?.id || !props.checklist2?.id || initialized()) return;
     const key = getStorageKey();
     if (!key) return;

     try {
       const stored = localStorage.getItem(key);
       if (stored) {
         const parsed = JSON.parse(stored);
         setCurrentPage(parsed.currentPage ?? 0);
         setViewMode(parsed.viewMode ?? 'questions');
       }
+      setInitialized(true);
     } catch (e) {
       console.error('Failed to load navigation state:', e);
+      setInitialized(true);
     }
   });

251-281: Unnecessary async keyword on handleReset.

The function doesn't contain any await expressions, so async isn't needed:

♻️ Minor cleanup
-  async function handleReset() {
+  function handleReset() {
packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsISummaryView.jsx (1)

194-227: Consider extracting shared display helpers to navbar-utils.js.

getItemDisplayNumber and getItemLabel are very similar to getDisplayLabel and getSectionLabel in RobinsINavbar.jsx. Consider consolidating these into navbar-utils.js to reduce duplication.

packages/web/src/primitives/pdfCache.js (1)

144-177: Consider batching deletes for efficiency.

The eviction logic is correct, but each delete creates a separate transaction (lines 164-170). For better performance when evicting multiple entries, consider batching all deletes into a single transaction.

Additionally, while the current code is safe because cachePdf enforces MAX_SINGLE_FILE_SIZE, adding a defensive check at the start would make the function more robust:

♻️ Optional optimization
 async function evictIfNeeded(requiredSpace) {
   try {
+    // Defensive check: if required space exceeds cache limit, we can't satisfy it
+    if (requiredSpace > MAX_CACHE_SIZE_BYTES) {
+      console.warn(`Required space ${requiredSpace} exceeds cache limit ${MAX_CACHE_SIZE_BYTES}`);
+      return;
+    }
+
     const { totalSize, entries } = await getCacheStats();

For batching deletes in a single transaction:

     const db = await getDb();
+    const idsToDelete = [];
 
-    // Evict oldest entries until we're under the limit
     for (const entry of entries) {
       if (currentSize <= targetSize) break;
-
-      await new Promise((resolve, reject) => {
-        const transaction = db.transaction(PDF_STORE_NAME, 'readwrite');
-        const store = transaction.objectStore(PDF_STORE_NAME);
-        const request = store.delete(entry.id);
-        request.onsuccess = () => resolve();
-        request.onerror = () => reject(request.error);
-      });
-
+      idsToDelete.push(entry.id);
       currentSize -= entry.size;
     }
+
+    // Batch delete in single transaction
+    if (idsToDelete.length > 0) {
+      await new Promise((resolve, reject) => {
+        const transaction = db.transaction(PDF_STORE_NAME, 'readwrite');
+        const store = transaction.objectStore(PDF_STORE_NAME);
+        let completed = 0;
+        
+        for (const id of idsToDelete) {
+          const request = store.delete(id);
+          request.onsuccess = () => {
+            completed++;
+            if (completed === idsToDelete.length) resolve();
+          };
+          request.onerror = () => reject(request.error);
+        }
+      });
+    }

author: Team
---

This roadmap turns the strategy from `docs/plans/local-first-sync-strategy.md` into a concrete, time-boxed plan with owners, milestones, and acceptance criteria. It is intentionally pragmatic: ship small, measure, and expand.
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

Correct file path reference.

Line 7 references docs/plans/local-first-sync-strategy.md, but the file is located at docs/audits/local-first-sync-strategy.md. Fix this broken cross-reference.

🔗 Proposed fix
- This roadmap turns the strategy from `docs/plans/local-first-sync-strategy.md` into a concrete, time-boxed plan with owners, milestones, and acceptance criteria. It is intentionally pragmatic: ship small, measure, and expand.
+ This roadmap turns the strategy from `docs/audits/local-first-sync-strategy.md` into a concrete, time-boxed plan with owners, milestones, and acceptance criteria. It is intentionally pragmatic: ship small, measure, and expand.
📝 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
This roadmap turns the strategy from `docs/plans/local-first-sync-strategy.md` into a concrete, time-boxed plan with owners, milestones, and acceptance criteria. It is intentionally pragmatic: ship small, measure, and expand.
This roadmap turns the strategy from `docs/audits/local-first-sync-strategy.md` into a concrete, time-boxed plan with owners, milestones, and acceptance criteria. It is intentionally pragmatic: ship small, measure, and expand.
🤖 Prompt for AI Agents
In @packages/docs/audits/local-first-roadmap.md at line 7, Update the incorrect
cross-reference string `docs/plans/local-first-sync-strategy.md` to the correct
path `docs/audits/local-first-sync-strategy.md` in the sentence that begins
"This roadmap turns the strategy..." so the link points to the actual file
location; ensure any other occurrences of
`docs/plans/local-first-sync-strategy.md` in this document are likewise replaced
with `docs/audits/local-first-sync-strategy.md`.


Where to record progress

- Use `docs/plans/local-first-sync-strategy.md` for strategy 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 | 🟡 Minor

Correct file path reference.

Line 69 also references docs/plans/local-first-sync-strategy.md, but the file is located at docs/audits/local-first-sync-strategy.md. Fix this broken cross-reference to match the location of the strategy document.

🔗 Proposed fix
- Use `docs/plans/local-first-sync-strategy.md` for strategy context.
+ Use `docs/audits/local-first-sync-strategy.md` for strategy context.
📝 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
- Use `docs/plans/local-first-sync-strategy.md` for strategy context.
- Use `docs/audits/local-first-sync-strategy.md` for strategy context.
🤖 Prompt for AI Agents
In @packages/docs/audits/local-first-roadmap.md at line 69, The cross-reference
in packages/docs/audits/local-first-roadmap.md incorrectly points to
docs/plans/local-first-sync-strategy.md; update that link/text to reference the
actual file path docs/audits/local-first-sync-strategy.md so the roadmap points
to the correct strategy document.

- `minSupportedClient` (semver)
- `features`/capabilities
- Client reports its `clientVersion` on requests (header) and performs a boot-time check. If the server responds that the client is too old, the client shows a blocking upgrade UI.
- Service worker & BroadcastChannel based approach: server changes `minSupportedClient` — SW detects it and broadcasts a force-reload message to all tabs. This is less disruptive than immediate hard-blocks.
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

Hyphenate compound modifier.

Change "BroadcastChannel based approach" to "BroadcastChannel-based approach" for correct grammar.

📝 Proposed fix
- Service worker & BroadcastChannel based approach: server changes `minSupportedClient` — SW detects it and broadcasts a force-reload message to all tabs. This is less disruptive than immediate hard-blocks.
+ Service worker & BroadcastChannel-based approach: server changes `minSupportedClient` — SW detects it and broadcasts a force-reload message to all tabs. This is less disruptive than immediate hard-blocks.
📝 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
- Service worker & BroadcastChannel based approach: server changes `minSupportedClient` — SW detects it and broadcasts a force-reload message to all tabs. This is less disruptive than immediate hard-blocks.
- Service worker & BroadcastChannel-based approach: server changes `minSupportedClient` — SW detects it and broadcasts a force-reload message to all tabs. This is less disruptive than immediate hard-blocks.
🧰 Tools
🪛 LanguageTool

[grammar] ~54-~54: Use a hyphen to join words.
Context: ... UI. - Service worker & BroadcastChannel based approach: server changes `minSuppo...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In @packages/docs/audits/local-first-sync-strategy.md at line 54, Update the
phrase "BroadcastChannel based approach" to "BroadcastChannel-based approach" in
the sentence that describes the service worker and BroadcastChannel approach so
the compound modifier is hyphenated correctly; locate the sentence containing
"Service worker & BroadcastChannel based approach" and replace that fragment
with "Service worker & BroadcastChannel-based approach".

Comment on lines +117 to +118

If you want, I can: (A) generate the endpoint inventory now, (B) move selected Zod schemas to `packages/shared`, or (C) scaffold `/api/metadata` and CI typegen. Which should I start?
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

Adopt a more formal tone in the closing section.

The opening phrase "If you want, I can:" is too informal for documentation. Use more formal language to align with the professional tone of the rest of the document.

📝 Proposed fix
- If you want, I can: (A) generate the endpoint inventory now, (B) move selected Zod schemas to `packages/shared`, or (C) scaffold `/api/metadata` and CI typegen. Which should I start?
+ Next steps: (A) generate the endpoint inventory, (B) move selected Zod schemas to `packages/shared`, or (C) scaffold `/api/metadata` and CI typegen.
🧰 Tools
🪛 LanguageTool

[style] ~117-~117: This phrasing can be overused. Try elevating your writing with a more formal alternative.
Context: ... reconciliation for doc snapshots --- If you want, I can: (A) generate the endpoint inven...

(IF_YOU_WANT)

🤖 Prompt for AI Agents
In @packages/docs/audits/local-first-sync-strategy.md around lines 117 - 118,
Replace the informal closing "If you want, I can:" with a more formal
alternative that matches the document tone; update the options to begin with a
complete sentence such as "Please indicate whether you would like me to: (A)
generate the endpoint inventory now, (B) move selected Zod schemas to
`packages/shared`, or (C) scaffold `/api/metadata` and CI typegen." Ensure
punctuation and capitalization are consistent with surrounding documentation.

import { ThumbnailsSidebar } from './components/thumbnails-sidebar';
import { PageControls } from './components/page-controls';
import { ConsoleLogger } from '@embedpdf/models';
// import { ConsoleLogger } from '@embedpdf/models';
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
# Check ConsoleLogger usage across entire codebase
rg "ConsoleLogger" --type=ts --type=tsx

Repository: InfinityBowman/corates

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Also check if this is a common pattern with commented debug code
rg "// import.*from.*embedpdf" --type=ts --type=tsx

Repository: InfinityBowman/corates

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Check the actual file to see the context of the commented lines
if [ -f "packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx" ]; then
  echo "=== Lines 40-60 ==="
  sed -n '40,60p' packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx
  echo -e "\n=== Lines 80-90 ==="
  sed -n '80,90p' packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx
  echo -e "\n=== Lines 270-280 ==="
  sed -n '270,280p' packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx
else
  echo "File not found"
fi

Repository: InfinityBowman/corates

Length of output: 1808


🏁 Script executed:

#!/bin/bash
# Search for any active (non-commented) usage of ConsoleLogger
grep -r "ConsoleLogger" packages/web/src --include="*.tsx" --include="*.ts" | grep -v "^[^:]*://.*//.*ConsoleLogger"

Repository: InfinityBowman/corates

Length of output: 283


🏁 Script executed:

#!/bin/bash
# Check if ConsoleLogger is imported anywhere without comment
find packages/web/src -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "ConsoleLogger" {} \;

Repository: InfinityBowman/corates

Length of output: 128


Delete commented ConsoleLogger lines per coding guidelines.

The ConsoleLogger import and all references (lines 44, 58, 85, 274) are completely unused and commented out. Remove these lines entirely rather than leaving obsolete code paths in comments, per the guideline that states: "Do NOT reference removed or obsolete code paths in comments."

🤖 Prompt for AI Agents
In @packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx at line 44,
Remove the commented-out ConsoleLogger import and all commented references to
ConsoleLogger in this file (delete the "// import { ConsoleLogger } from
'@embedpdf/models';" and any other commented ConsoleLogger usages), leaving no
obsolete commented code paths referencing ConsoleLogger; ensure no other code
depends on it and run a quick build to confirm no unused symbol warnings remain.

Comment on lines +236 to +255
// Cleanup: close the active document and release resources on unmount
useEffect(() => {
return () => {
const closeActiveDocument = async () => {
if (docManagerRef.current && activeDocumentIdRef.current) {
try {
await docManagerRef.current.closeDocument(activeDocumentIdRef.current);
} catch (err) {
console.warn('Error closing document on unmount:', err);
}
}
// Clear refs to release memory
docManagerRef.current = null;
activeDocumentIdRef.current = null;
previousSelectedPdfIdRef.current = undefined;
isLoadingRef.current = false;
};
closeActiveDocument();
};
}, []);
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 async cleanup function in useEffect.

The cleanup function returned by useEffect must be synchronous, but closeActiveDocument is async and called without await. This means the document close operation won't complete before unmount, potentially causing memory leaks or orphaned resources in the PDF engine.

🐛 Proposed fix for synchronous cleanup

The cleanup should fire-and-forget the async operation, acknowledging it may not complete:

-  // Cleanup: close the active document and release resources on unmount
   useEffect(() => {
     return () => {
-      const closeActiveDocument = async () => {
-        if (docManagerRef.current && activeDocumentIdRef.current) {
-          try {
-            await docManagerRef.current.closeDocument(activeDocumentIdRef.current);
-          } catch (err) {
-            console.warn('Error closing document on unmount:', err);
-          }
-        }
-        // Clear refs to release memory
-        docManagerRef.current = null;
-        activeDocumentIdRef.current = null;
-        previousSelectedPdfIdRef.current = undefined;
-        isLoadingRef.current = false;
-      };
-      closeActiveDocument();
+      // Fire async close without awaiting (may not complete before unmount)
+      if (docManagerRef.current && activeDocumentIdRef.current) {
+        docManagerRef.current.closeDocument(activeDocumentIdRef.current)
+          .catch(err => console.warn('Error closing document on unmount:', err));
+      }
+      // Clear refs immediately
+      docManagerRef.current = null;
+      activeDocumentIdRef.current = null;
+      previousSelectedPdfIdRef.current = undefined;
+      isLoadingRef.current = false;
     };
   }, []);
🤖 Prompt for AI Agents
In @packages/web/src/components/pdf/embedpdf/preact/src/viewer.tsx around lines
236 - 255, The cleanup returned from useEffect is currently calling an async
closeActiveDocument without awaiting, which makes the cleanup itself async; make
the cleanup synchronous by defining the async function (closeActiveDocument)
inside the effect but invoking it in a fire-and-forget manner (for example: void
closeActiveDocument(); or closeActiveDocument().catch(err =>
console.warn(...))). Ensure you still call
docManagerRef.current.closeDocument(activeDocumentIdRef.current) inside that
async function and preserve the ref-clearing (docManagerRef,
activeDocumentIdRef, previousSelectedPdfIdRef, isLoadingRef) and error logging,
but do not return an async function from useEffect.

Comment on lines +56 to +84
const headerContent = (
<>
{/* Back button */}
<button
onClick={() => props.onCancel?.()}
class='shrink-0 rounded-lg p-2 transition-colors hover:bg-gray-100'
title='Go back'
>
<AiOutlineArrowLeft class='h-5 w-5 text-gray-600' />
</button>

{/* Title */}
<div class='shrink-0'>
<h1 class='text-lg font-bold text-gray-900'>ROBINS-I Reconciliation</h1>
<p class='text-xs text-gray-500'>
{props.reviewer1Name || 'Reviewer 1'} vs {props.reviewer2Name || 'Reviewer 2'}
</p>
</div>

<div class='h-8 w-px shrink-0 bg-gray-200' />

{/* Navbar - navigation pills */}
<Show when={navbarStore.navItems?.length > 0}>
<div class='flex flex-1 items-center gap-4 overflow-x-auto'>
<RobinsINavbar store={navbarStore} />
</div>
</Show>
</>
);
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

Reactivity issue: headerContent won't update when props change.

In SolidJS, JSX assigned to a variable outside the render path is evaluated once. If props.reviewer1Name, props.reviewer2Name, or props.onCancel change, this headerContent won't re-render.

Consider wrapping in a function or moving inline:

🔧 Suggested fix
-  // Build header content with back button, title, and navbar
-  const headerContent = (
-    <>
+  // Build header content with back button, title, and navbar
+  const headerContent = () => (
+    <>
       {/* Back button */}
       ...
     </>
   );

Then pass headerContent={headerContent()} to SplitScreenLayout, or if the layout component expects a function, pass headerContent={headerContent}.

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

🤖 Prompt for AI Agents
In
@packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsIReconciliationWithPdf.jsx
around lines 56 - 84, The JSX assigned to the headerContent variable is
evaluated once and won't react to changes in props.reviewer1Name,
props.reviewer2Name or props.onCancel; change headerContent into a function
(e.g., const headerContent = () => ( ... )) or inline the JSX where used so
Solid can re-evaluate it, then pass headerContent() (or the function itself if
SplitScreenLayout expects a render function) to SplitScreenLayout; keep the same
internal references (AiOutlineArrowLeft, RobinsINavbar, navbarStore) but ensure
you call the function where headerContent was previously referenced.

Comment on lines +16 to +18
// LRU cache limits
const MAX_CACHE_SIZE_BYTES = 200 * 1024 * 1024; // 200 MB total cache limit
const MAX_SINGLE_FILE_SIZE = 50 * 1024 * 1024; // 50 MB per file (matches PDF_LIMITS.MAX_SIZE)
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 PDF_LIMITS definitions
rg -n "PDF_LIMITS\s*=" --type js --type ts -g '!node_modules' -g '!dist'

Repository: InfinityBowman/corates

Length of output: 128


🏁 Script executed:

#!/bin/bash
# Search for where PDF_LIMITS is imported or used
rg -n "PDF_LIMITS" --type js --type ts -g '!node_modules' -g '!dist' -B 2 -A 2

Repository: InfinityBowman/corates

Length of output: 8542


🏁 Script executed:

#!/bin/bash
# Check the current content of pdfCache.js to see imports and the full context
head -40 packages/web/src/primitives/pdfCache.js

Repository: InfinityBowman/corates

Length of output: 1146


Import PDF_LIMITS.MAX_SIZE from @corates/shared instead of hardcoding the value.

The cache limit on line 18 hardcodes 50 * 1024 * 1024 with a comment referencing PDF_LIMITS.MAX_SIZE, but it should import and use the constant directly from @corates/shared (as done in pdfValidation.js) to maintain a single source of truth and prevent future desynchronization.

🤖 Prompt for AI Agents
In @packages/web/src/primitives/pdfCache.js around lines 16 - 18, Replace the
hardcoded per-file limit with the shared constant: add an import for PDF_LIMITS
from '@corates/shared' at the top of the file and set MAX_SINGLE_FILE_SIZE to
PDF_LIMITS.MAX_SIZE instead of 50 * 1024 * 1024; keep MAX_CACHE_SIZE_BYTES
unchanged and mirror the pattern used in pdfValidation.js so the
single-source-of-truth PDF_LIMITS.MAX_SIZE is used across the codebase.

@InfinityBowman InfinityBowman merged commit 2c59635 into main Jan 7, 2026
3 checks passed
@InfinityBowman InfinityBowman deleted the 207-reconcile-for-robins branch January 7, 2026 21:50
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.

Reconcile for Robins

2 participants