Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lerichardv/patolab-platform/llms.txt

Use this file to discover all available pages before exploring further.

PatoLab includes a fully collaborative, multi-section report editor designed specifically for pathology workflows. Multiple pathologists can simultaneously edit different sections of the same report in real time, with each user’s cursor position, edits, and formatting changes instantly visible to collaborators. The editor is built on TipTap as the rich-text foundation, Yjs as the conflict-free replicated data type (CRDT) engine, and Hocuspocus as the WebSocket collaboration server that brokers document state between connected clients and persists it back to Laravel.

Architecture Overview

Browser (TipTap + Yjs)

        │  WebSocket (Yjs CRDT sync)

Hocuspocus Express Server

        │  HTTP webhook (create / onChange / onConnect events)

Laravel  POST /api/collaboration


MySQL  specimen_reports table
       (HTML columns + binary Yjs state vectors)
1

Browser connects

The React editor opens a WebSocket connection to the Hocuspocus server using a room name formatted as report-{id}-{field} (e.g. report-42-macroscopy). The server fires an onConnect webhook to Laravel, which returns the saved Yjs binary state for that field so the editor is pre-populated.
2

User types

As a pathologist types, TipTap generates Yjs update operations. Hocuspocus broadcasts these deltas to all other connected clients in the same room in real time.
3

Auto-save

When a typing pause threshold is reached, Hocuspocus fires an onChange webhook to Laravel containing the updated binary Yjs state (document) and the rendered HTML (html). Laravel saves both: the binary state to the yjs_*_state column for future reconnections, and the HTML to the *_html column for PDF generation.
4

Manual save

Users can also trigger an explicit save via POST /specimens/{sequence_code}/report-editor/save, which accepts all HTML and Yjs state fields in a single payload.
The collaboration server must be running and reachable at the URL defined in the VITE_COLLABORATION_SERVER_URL environment variable. The React frontend uses this variable to establish WebSocket connections. If the server is unreachable, the editor will still function in single-user offline mode, but real-time collaboration and auto-save will not work.

Room Naming Convention

Each editable section of a report gets its own isolated collaboration room. Room names follow the pattern:
report-{report_id}-{field}
The supported field identifiers map to the following database columns:
Room suffixHTML columnYjs state column
macroscopymacroscopy_htmlyjs_macroscopy_state
microscopymicroscopy_htmlyjs_microscopy_state
diagnosisdiagnosis_htmlyjs_diagnosis_state
clinical_detailsclinical_details_htmlyjs_clinical_details_state
comments_notescomments_notes_htmlyjs_comments_notes_state
protocolsprotocols_htmlyjs_protocols_state
legendlegend_htmlyjs_legend_state
report_datereport_dateyjs_report_date_state
sections_ordersections_order(JSON, not binary)
The three primary diagnostic sections — macroscopy, microscopy, and diagnosis — are the main editing surfaces. The additional sections (clinical details, comments & notes, protocols, legend) are optional and can be toggled on/off via the sections_order configuration.

Report State Machine

A SpecimenReport is created when a pathologist first opens a specimen’s report editor. At that point, the specimen status advances to macroscopic_review. From there, report workflow transitions are driven through:
POST /specimens/{sequence_code}/report-editor/transition-state
{
  "status": "processing"
}
Valid transition targets and their effects:
Target statusTimestamp setNotes
macroscopic_reviewInitial state, set on report creation
processingmacroscopy_finalization_datetimeLocks macroscopy phase
microscopic_reviewmicroscopy_finalization_datetimeLocks microscopy phase
finalizedreport_finalization_datetimeGenerates PDF, calculates commissions, sends WhatsApp to patient
Transitioning to finalized requires all assigned pathologists to have a saved signature on their user profile. If any assigned pathologist is missing a signature, the transition is blocked and an error listing the missing names is returned.

Report Editor Endpoints

GET /specimens/{sequence_code}/report-editor
Renders the Inertia editor page with the specimen, its linked SpecimenReport, assigned pathologists, and available supply products (insumos) for the current user.
POST /specimens/{sequence_code}/report-editor
Creates a new SpecimenReport row for the specimen and advances the specimen status to macroscopic_review. If the pathologist has a saved specimen type template matching the specimen’s type and examination, the template’s HTML content is pre-populated into all editor sections.Returns an error if a report already exists for this specimen.
POST /specimens/{sequence_code}/report-editor/save
Saves all editor fields in a single request. Accepts any combination of HTML content fields and their corresponding Yjs binary state (base64-encoded). Write access is enforced per field: only pathologists with macroscopy_access can write macroscopy_html, and only those with microscopy_access can write microscopy_html. The diagnosis, clinical_details, comments_notes, protocols, and legend sections are writable by anyone with either access flag.
POST /specimens/{sequence_code}/report-editor/update-date
{
  "report_date": "2025-06-15"
}
Updates the report_date field on the linked SpecimenReport. Requires the calling user to have at least one access flag (macroscopy or microscopy) on this specimen.
POST /specimens/{sequence_code}/report-editor/generate-temp-pdf
Generates a temporary PDF from the current report content (without finalising) and stores it under storage/public/temp_reports/{sequence_code}/. Returns a public URL and the total page count so the editor can display a preview.
{
  "url": "https://app.example.com/storage/temp_reports/HP-0001-01-25/report_1749123456.pdf",
  "total_pages": 3
}
Any previously generated temp PDF for this specimen is deleted before the new one is stored.
POST /specimens/{sequence_code}/report-editor/upload-image
Uploads and optimises an image (max 10 MB) for embedding inside the TipTap editor. The optimised file is stored at storage/public/report-images/{sequence_code}/ and the public URL is returned.
{
  "url": "https://app.example.com/storage/report-images/HP-0001-01-25/image_optimized.jpg"
}
GET /specimens/{sequence_code}/report-editor/pdf
Downloads the finalized report PDF. If a stored file already exists at report_file on the SpecimenReport, it is served directly. Otherwise, the PDF is generated on the fly from the current report HTML.
POST /specimens/{sequence_code}/generate-report
Re-generates and stores the final report PDF for an already-finalized specimen without triggering a state transition. Useful for regenerating the PDF after correcting data.

Webhook Endpoint

The Hocuspocus server communicates with Laravel through a single webhook route:
POST /api/collaboration
This route is not protected by the standard web auth middleware — it is intended to be called only by the collaboration server. The webhook handles three event types: onConnect (initialization), create (room setup), and onChange (background save).
PDF generation for both reports and invoices requires Chromium to be installed and accessible to the PHP process via Browsershot. In production, configure the BROWSERSHOT_CHROME_PATH, BROWSERSHOT_NODE_BINARY, and BROWSERSHOT_NPM_BINARY environment variables to point to the correct binaries. Missing Chromium will cause all PDF generation to fail silently or throw an exception.

Build docs developers (and LLMs) love