Skip to content

Performance: slow edit specialists page#2333

Open
LiamStanziani wants to merge 7 commits intohotfix/01232026from
perf/slow-edit-specialists-page
Open

Performance: slow edit specialists page#2333
LiamStanziani wants to merge 7 commits intohotfix/01232026from
perf/slow-edit-specialists-page

Conversation

@LiamStanziani
Copy link
Collaborator

@LiamStanziani LiamStanziani commented Mar 10, 2026

Summary

Decouple specialist list data from inline JSP rendering into a separate
fetch() POST endpoint with client-side batch rendering.

Fixes #2321

Problem

The Edit Specialists and Display Service pages embed all specialist data
directly in the HTML response via server-side <% for %> loops. With
10k+ specialist records this causes:

  • ~226MB transient memory allocation per request (DTO list + JSON string
    • JSP output buffer all held simultaneously)
  • 1,705KB HTML response containing the full specialist table
  • ~4 second browser (local environment) DOM parsing delay before the page becomes interactive

Solution

DTO Projection (server):

  • JPQL constructor projection (SELECT NEW SpecialistListDTO(...))
    fetches only the 7 columns needed for list display, avoiding full
    entity hydration of heavy text fields (annotation, eDataOscarKey, etc.)

Separate JSON endpoint (server):

  • New SpecialistList2Action serves specialist data as JSON via POST,
    following the same pattern as ConsultationLookup2Action and
    TicklerList2Action
  • Jackson ArrayNode builds the response, written directly to
    response.getWriter() — decouples data payload from page shell

Batch rendering (client):

  • JSP pages load a lightweight shell (~9KB) with spinner
  • fetch() POST retrieves JSON from the new endpoint
  • requestAnimationFrame + document.createDocumentFragment() renders
    rows in batches of 1,000 for instant perceived load

Summary by Sourcery

Introduce a JSON-based specialist list endpoint and client-side rendering to decouple large specialist datasets from JSP pages and improve performance on specialist configuration screens.

New Features:

  • Add SpecialistList2Action JSON endpoints to serve specialist list data for Edit Specialists and Display Service pages.
  • Introduce SpecialistListDTO as a lightweight projection for specialist list views.

Enhancements:

  • Refactor EditSpecialists.jsp and DisplayService.jsp to fetch specialist data asynchronously and render rows client-side using a shared renderer script.
  • Optimize specialist data access in ProfessionalSpecialistDao with a DTO-based query that selects only columns needed for list display.
  • Simplify EctConDisplayServiceUtil by removing specialist list construction responsibilities.

Tests:

  • Add integration tests verifying ProfessionalSpecialistDao DTO projection behavior, ordering, and null-handling.

Summary by cubic

Move specialist data out of JSP into CSRF-protected JSON endpoints with client-side batch rendering to speed up Edit Specialists and Display Service; cuts payload and server memory for much faster interactivity. Fixes #2321.

  • Refactors
    • Add SpecialistList2Action with POST-only endpoints getSpecialists and getSpecialistsForService, CsrfGuard header support, _con read privilege check, and 400/405 error handling; mapped in struts.xml.
    • Add ProfessionalSpecialistDao#findAllListDTOs() using JPQL constructor projection to SpecialistListDTO (id, names, letters, address, phone, fax); include integration tests.
    • Add specialistListRenderer.js for batched requestAnimationFrame rendering and CSRF header; update EditSpecialists.jsp and DisplayService.jsp to fetch JSON and render checkboxes/links, keeping the spinner until the first batch renders.
    • Remove specialist vector building and inline JSP loops from EctConDisplayServiceUtil and drop related calls (e.g., from ConsultationFormRequest.jsp).

Written for commit a0d1175. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added API endpoint for retrieving specialist information, including support for filtering specialists by service and checking assignment status.
  • Refactor

    • Improved specialist list display performance by switching from server-side rendering to dynamic client-side loading with batched data rendering.
    • Streamlined specialist data initialization in consultation request workflows.

  Move specialist data loading out of inline JSP scriptlets into a
  dedicated SpecialistList2Action that serves JSON via fetch() POST.
  The JSP pages now load a lightweight shell with spinner, then fetch
  and batch-render rows client-side using requestAnimationFrame.
@LiamStanziani LiamStanziani self-assigned this Mar 10, 2026
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 10, 2026

Reviewer's Guide

This PR refactors the specialists configuration UI to load specialist data from a new JSON Struts action backed by a lightweight DTO projection, and batch-renders the table client-side, eliminating large server-side JSP loops and reducing memory and payload size.

Sequence diagram for specialist list JSON loading and client-side rendering

sequenceDiagram
    actor User
    participant Browser
    participant EditSpecialistsJSP
    participant specialistListRenderer_js as SpecialistListRendererJS
    participant SpecialistList2Action
    participant ProfessionalSpecialistDaoImpl
    participant ServiceSpecialistsDao
    participant Database

    User->>Browser: Navigate to EditSpecialists URL
    Browser->>EditSpecialistsJSP: HTTP GET /EditSpecialists.jsp
    EditSpecialistsJSP-->>Browser: HTML shell with spinner and JS
    Browser->>Browser: Parse HTML and load JS
    Browser->>specialistListRenderer_js: renderSpecialistList(config)

    specialistListRenderer_js->>SpecialistList2Action: POST /SpecialistList.do?method=getSpecialists
    activate SpecialistList2Action
    SpecialistList2Action->>ProfessionalSpecialistDaoImpl: findAllListDTOs()
    activate ProfessionalSpecialistDaoImpl
    ProfessionalSpecialistDaoImpl->>Database: JPQL SELECT NEW SpecialistListDTO(...)
    Database-->>ProfessionalSpecialistDaoImpl: List<SpecialistListDTO>
    deactivate ProfessionalSpecialistDaoImpl

    SpecialistList2Action-->>specialistListRenderer_js: JSON [[id,name,address,phone,fax],...]
    deactivate SpecialistList2Action

    specialistListRenderer_js->>Browser: Batch create rows with DocumentFragment
    Browser->>Browser: Insert 1000 row batches into tbody
    Browser->>Browser: Hide spinner and show interactive table

    %% Service specific variant
    User->>Browser: Navigate to DisplayService URL
    Browser->>DisplayServiceJSP: HTTP GET /DisplayService.jsp
    DisplayServiceJSP-->>Browser: HTML shell with spinner and JS
    Browser->>specialistListRenderer_js: renderSpecialistList(config with serviceId)

    specialistListRenderer_js->>SpecialistList2Action: POST /SpecialistList.do?method=getSpecialistsForService&serviceId
    activate SpecialistList2Action
    SpecialistList2Action->>ProfessionalSpecialistDaoImpl: findAllListDTOs()
    activate ProfessionalSpecialistDaoImpl
    ProfessionalSpecialistDaoImpl->>Database: JPQL SELECT NEW SpecialistListDTO(...)
    Database-->>ProfessionalSpecialistDaoImpl: List<SpecialistListDTO>
    deactivate ProfessionalSpecialistDaoImpl

    SpecialistList2Action->>ServiceSpecialistsDao: findByServiceId(serviceId)
    activate ServiceSpecialistsDao
    ServiceSpecialistsDao->>Database: SELECT ServiceSpecialists by serviceId
    Database-->>ServiceSpecialistsDao: List<ServiceSpecialists>
    deactivate ServiceSpecialistsDao

    SpecialistList2Action-->>specialistListRenderer_js: JSON [[id,name,address,phone,fax,checked],...]
    deactivate SpecialistList2Action

    specialistListRenderer_js->>Browser: Batch create rows with checkbox state
    Browser->>Browser: Insert 1000 row batches into tbody and hide spinner
Loading

Updated class diagram for specialist listing and rendering

classDiagram
    class SpecialistListDTO {
        +Integer id
        +String firstName
        +String lastName
        +String professionalLetters
        +String streetAddress
        +String phoneNumber
        +String faxNumber
        +SpecialistListDTO(Integer id, String firstName, String lastName, String professionalLetters, String streetAddress, String phoneNumber, String faxNumber)
        +Integer getId()
        +String getFirstName()
        +String getLastName()
        +String getProfessionalLetters()
        +String getStreetAddress()
        +String getPhoneNumber()
        +String getFaxNumber()
    }

    class ProfessionalSpecialistDao {
        <<interface>>
        +List findAll()
        +List findAllListDTOs()
        +List findByEDataUrlNotNull()
        +List findByFullName(String lastName, String firstName)
    }

    class ProfessionalSpecialistDaoImpl {
        -EntityManager entityManager
        +List findAll()
        +List findAllListDTOs()
        +List findByEDataUrlNotNull()
        +List findByFullName(String lastName, String firstName)
    }

    class ServiceSpecialistsDao {
        +List findByServiceId(Integer serviceId)
    }

    class SpecialistList2Action {
        -HttpServletRequest request
        -HttpServletResponse response
        -SecurityInfoManager securityInfoManager
        -ProfessionalSpecialistDao professionalSpecialistDao
        -static ObjectMapper MAPPER
        +String execute()
        -String getSpecialists()
        -String getSpecialistsForService()
        -Set loadAssignedSpecialistIds(Integer serviceId)
        -String buildDisplayName(SpecialistListDTO dto)
        -void writeJsonResponse(ArrayNode data)
        -String handleServerError(String logMessage, Exception e)
    }

    class EctConDisplayServiceUtil {
        -ConsultationServiceDao consultationServiceDao
        +Vector serviceName
        +Vector serviceId
        +String getServiceDesc(String serId)
        +void estServicesVectors()
    }

    class EditSpecialistsJSP {
        +tbodyId specialistBody
        +uses renderSpecialistList_config
    }

    class DisplayServiceJSP {
        +tbodyId specialistBody
        +String serviceId
        +uses renderSpecialistList_config
    }

    class specialistListRenderer_js {
        +void renderSpecialistList(Object config)
        +void fetchJson(String url, String csrfTokenName, String csrfTokenValue)
        +void renderInBatches(Array data, String tbodyId, Object buildRow)
    }

    ProfessionalSpecialistDao <|.. ProfessionalSpecialistDaoImpl
    SpecialistListDTO <.. ProfessionalSpecialistDaoImpl : JPQL_constructor_projection
    SpecialistList2Action --> ProfessionalSpecialistDao : uses
    SpecialistList2Action --> ServiceSpecialistsDao : uses
    SpecialistList2Action --> SpecialistListDTO : builds_display_rows
    EctConDisplayServiceUtil --> ConsultationServiceDao : uses
    EditSpecialistsJSP --> specialistListRenderer_js : calls_renderSpecialistList
    DisplayServiceJSP --> specialistListRenderer_js : calls_renderSpecialistList
    specialistListRenderer_js --> SpecialistList2Action : AJAX_POST_JSON
    DisplayServiceJSP --> EctConDisplayServiceUtil : getServiceDesc_only
Loading

File-Level Changes

placeholder that is populated via JavaScript.
  • Simplify spinner behavior to show immediately and rely on JS to hide when rendering completes.
  • to be filled by JavaScript.
  • Align spinner usage with EditSpecialists.jsp by showing it immediately and letting JS manage hiding after data load.
  • Change Details Files
    Refactor Edit Specialists JSP to render table rows client-side from JSON instead of server-side vectors.
    • Remove displayServiceUtil.estSpecialistVector() usage and server-side for-loop over specialist vectors.
    • Include CsrfGuard taglib and OWASP Encode import for safe JS embedding.
    • Load specialistListRenderer.js and call renderSpecialistList() with POST URL, CSRF token, and a buildRow callback to construct checkbox and link cells.
    • Replace static table rows with an empty
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp
    Refactor Display Service JSP to use the JSON specialist list endpoint with per-service checked state and client-side rendering.
    • Remove calls to displayServiceUtil.estSpecialistVector() and getSpecialistInField(), and the associated JSP for-loop that emitted specialist rows.
    • Add CsrfGuard taglib and OWASP Encode import; safely embed context path and serviceId into JavaScript.
    • Load specialistListRenderer.js and invoke renderSpecialistList() with getSpecialistsForService URL, CSRF tokens, and a buildRow callback that sets checkbox checked state from JSON.
    • Replace inline-generated table rows with
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp
    Remove specialist list building responsibilities from EctConDisplayServiceUtil and its JSP callers.
    • Delete vector fields and estSpecialist/estSpecialistVector methods that eagerly load all ProfessionalSpecialist entities into in-memory vectors.
    • Remove getSpecialistInField() helper that read ServiceSpecialists and built a vector of assigned IDs.
    • Drop the call to displayServiceUtil.estSpecialist() from ConsultationFormRequest.jsp since specialist lists now come from the new JSON endpoint.
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java
    src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp
    Introduce SpecialistListDTO and a DAO projection to fetch only list-relevant specialist fields.
    • Add SpecialistListDTO as an immutable, serializable DTO carrying id, name, letters, address, phone, and fax only.
    • Extend ProfessionalSpecialistDao with findAllListDTOs() returning ordered SpecialistListDTOs for list views.
    • Implement findAllListDTOs() in ProfessionalSpecialistDaoImpl using a JPQL constructor projection ordered by lastName, firstName.
    • Register ProfessionalSpecialist entity and DAO bean in the test Spring and JPA contexts so integration tests can run.
    src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java
    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java
    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java
    src/test-modern/resources/test-context-full.xml
    src/test-modern/resources/META-INF/persistence.xml
    Add SpecialistList2Action Struts endpoint to serve specialist lists as JSON for both Edit Specialists and Display Service pages.
    • Create SpecialistList2Action as a Struts2 ActionSupport that dispatches on a 'method' request parameter.
    • Implement getSpecialists() to load all SpecialistListDTOs, map them into a Jackson ArrayNode of [id, name, address, phone, fax], and write JSON directly to the response.
    • Implement getSpecialistsForService() to accept serviceId, load all SpecialistListDTOs plus assigned ServiceSpecialists, and emit [id, name, address, phone, fax, checked] rows.
    • Enforce _con read privilege via SecurityInfoManager at the start of execute(), returning 400 for bad method/serviceId and 500 for server errors.
    • Register the new action mapping oscarEncounter/SpecialistList in struts.xml for AJAX use by the JSPs.
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java
    src/main/webapp/WEB-INF/classes/struts.xml
    Add integration tests to validate the SpecialistListDTO projection query behavior.
    • Introduce ProfessionalSpecialistDaoDTOIntegrationTest to seed test ProfessionalSpecialist records and exercise findAllListDTOs().
    • Verify that the DTO list size matches the number of entities, ordering is by lastName then firstName, and all projected fields are mapped correctly.
    • Assert that null optional fields (letters, address, phone, fax) are handled gracefully in DTOs.
    • Include a test that returns an empty list when no ProfessionalSpecialist records exist.
    src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @LiamStanziani LiamStanziani requested a review from Copilot March 10, 2026 21:00
    @coderabbitai
    Copy link

    coderabbitai bot commented Mar 10, 2026

    📝 Walkthrough

    Walkthrough

    This pull request optimizes specialist list page loading by converting from server-side rendering to client-side AJAX-based rendering with batch processing. It introduces a lightweight DTO, a new JSON API endpoint, and updates JSP pages to fetch and render specialist data asynchronously in batches rather than loading all data server-side.

    Changes

    Cohort / File(s) Summary
    DAO Layer - Specialist Projection
    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java, src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java
    Added new method findAllListDTOs() to fetch lightweight specialist projections (id, name, letters, address, phone, fax) ordered by name, avoiding full entity hydration.
    DTO - Specialist List Projection
    src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java
    New serializable DTO with final fields for lightweight specialist list display, supporting JPQL constructor projection.
    API Endpoint
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java, src/main/webapp/WEB-INF/classes/struts.xml
    Added new Struts2 action exposing JSON endpoints: getSpecialists returns all specialists; getSpecialistsForService returns specialists with assignment status for a given service.
    Util Refactoring
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java
    Removed specialist-related public fields (fNameVec, lNameVec, etc.), methods (estSpecialist, estSpecialistVector), and DAO imports. Refocused on service data only in estServicesVectors().
    JSP Pages - Client-Side Rendering
    src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp, src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp, src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp
    Removed server-side specialist rendering and specialist util calls. Added client-side fetch logic with batched rendering (1000 rows per batch) using requestAnimationFrame for incremental DOM updates and error handling.

    Sequence Diagram(s)

    sequenceDiagram
        participant Browser
        participant JSP
        participant SpecialistList2Action
        participant ProfessionalSpecialistDao
        participant Database
    
        Browser->>JSP: Load EditSpecialists page
        activate JSP
        JSP-->>Browser: Page with empty tbody + init script
        deactivate JSP
        
        Browser->>Browser: Execute IIFE script
        Browser->>SpecialistList2Action: POST /SpecialistList.do?method=getSpecialists
        activate SpecialistList2Action
        SpecialistList2Action->>ProfessionalSpecialistDao: findAllListDTOs()
        activate ProfessionalSpecialistDao
        ProfessionalSpecialistDao->>Database: Query lightweight projections
        Database-->>ProfessionalSpecialistDao: SpecialistListDTO list
        deactivate ProfessionalSpecialistDao
        SpecialistList2Action-->>Browser: JSON [id, name, address, phone, fax]
        deactivate SpecialistList2Action
        
        Browser->>Browser: Parse JSON response
        Browser->>Browser: Batch render 1000 rows per frame
        Browser->>Browser: requestAnimationFrame loop
        Browser->>Browser: Append rows to specialistBody
        Browser->>Browser: Hide spinner when complete
    
    Loading

    Estimated code review effort

    🎯 3 (Moderate) | ⏱️ ~25 minutes

    Poem

    🐰 A specialist list once heavy and slow,
    Now springs to life with a JSON flow,
    Batches of rabbits hop onto the screen,
    Faster than ever—you know what I mean!
    Ten thousand specialists, rendered with care,
    Load time? A whisper through digital air!

    🚥 Pre-merge checks | ✅ 4 | ❌ 1

    ❌ Failed checks (1 warning)

    Check name Status Explanation Resolution
    Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
    ✅ Passed checks (4 passed)
    Check name Status Explanation
    Title check ✅ Passed The PR title clearly and specifically identifies the main performance improvement: converting server-side specialist list rendering to client-side batch rendering to address the slow Edit Specialists page issue.
    Linked Issues check ✅ Passed The PR fully addresses the linked issue #2321 by implementing client-side batch rendering with a lightweight JSON API, reducing server memory allocation and DOM parsing delays for large specialist datasets.
    Out of Scope Changes check ✅ Passed All changes are directly scoped to solving the performance issue: new DTO for lightweight projections, JSON API endpoint, refactored JSP pages for client-side rendering, and removal of legacy server-side logic.
    Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

    ✨ Finishing Touches
    • 📝 Generate docstrings (stacked PR)
    • 📝 Generate docstrings (commit on current branch)
    🧪 Generate unit tests (beta)
    • Create PR with unit tests
    • Post copyable unit tests in a comment
    • Commit unit tests in branch perf/slow-edit-specialists-page

    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.

    @LiamStanziani
    Copy link
    Collaborator Author

    @SourceryAI review

    @LiamStanziani
    Copy link
    Collaborator Author

    @coderabbitai review

    @gemini-code-assist
    Copy link

    Summary of Changes

    Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

    This pull request significantly enhances the performance and user experience of the Edit Specialists and Display Service pages. By moving from server-side rendering of large specialist datasets to a more efficient client-side approach with data projection and asynchronous batch rendering, the system now handles extensive data volumes with reduced memory footprint, smaller network payloads, and faster interactive page loads.

    Highlights

    • Performance Improvement: Decoupled specialist list data from inline JSP rendering, addressing significant performance issues such as high memory allocation, large HTML responses, and long DOM parsing delays caused by server-side processing of 10k+ specialist records.
    • DTO Projection: Implemented JPQL constructor projection to fetch only the essential columns for specialist list display, preventing the full hydration of heavy ProfessionalSpecialist entities and reducing data transfer overhead.
    • Separate JSON Endpoint: Introduced a new Struts2 action, SpecialistList2Action, to serve specialist data as JSON via a POST endpoint, following established patterns for other data lookups.
    • Client-Side Batch Rendering: Modified JSP pages to load a lightweight shell initially, then asynchronously fetch specialist data via fetch() and render it in batches of 1,000 rows using requestAnimationFrame and document.createDocumentFragment() for instant perceived load.
    Changelog
    • src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java
      • Added a new method findAllListDTOs() to retrieve lightweight specialist data transfer objects.
    • src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java
      • Implemented the findAllListDTOs() method using JPQL constructor projection to fetch specific specialist fields.
    • src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java
      • Created a new Data Transfer Object (DTO) to hold essential specialist information for list display.
    • src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java
      • Removed deprecated fields and methods related to server-side specialist data fetching and storage.
    • src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java
      • Added a new Struts2 action to provide JSON endpoints for fetching specialist lists, including options for all specialists and specialists associated with a service.
    • src/main/webapp/WEB-INF/classes/struts.xml
      • Registered the new SpecialistList2Action with the Struts2 framework.
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp
      • Removed the call to the now-deprecated estSpecialist() method.
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp
      • Removed server-side specialist data rendering logic and replaced it with client-side AJAX fetching and batch rendering using JavaScript.
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp
      • Removed server-side specialist data rendering logic and replaced it with client-side AJAX fetching and batch rendering using JavaScript.
    Using Gemini Code Assist

    The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

    Invoking Gemini

    You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

    Feature Command Description
    Code Review /gemini review Performs a code review for the current pull request in its current state.
    Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
    Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
    Help /gemini help Displays a list of available commands.

    Customization

    To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

    Limitations & Feedback

    Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

    You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

    Footnotes

    1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

    @coderabbitai
    Copy link

    coderabbitai bot commented Mar 10, 2026

    ✅ Actions performed

    Review triggered.

    Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

    Copy link

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey - I've found 3 issues, and left some high level feedback:

    • SpecialistList2Action stores HttpServletRequest/Response and Spring beans in instance fields, which can be problematic if the action is reused across threads; consider resolving them per-request inside execute()/helper methods or via proper DI instead of as mutable fields.
    • The batch-rendering fetch logic in EditSpecialists.jsp and DisplayService.jsp is nearly identical; consider extracting the common JavaScript into a shared function or script include to reduce duplication and keep future changes in one place.
    • When an invalid method parameter is passed to SpecialistList2Action, the action currently returns NONE without informing the client; consider returning an explicit 4xx error (e.g., 400) so callers can distinguish misconfiguration from an empty list or success.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - SpecialistList2Action stores HttpServletRequest/Response and Spring beans in instance fields, which can be problematic if the action is reused across threads; consider resolving them per-request inside execute()/helper methods or via proper DI instead of as mutable fields.
    - The batch-rendering fetch logic in EditSpecialists.jsp and DisplayService.jsp is nearly identical; consider extracting the common JavaScript into a shared function or script include to reduce duplication and keep future changes in one place.
    - When an invalid `method` parameter is passed to SpecialistList2Action, the action currently returns NONE without informing the client; consider returning an explicit 4xx error (e.g., 400) so callers can distinguish misconfiguration from an empty list or success.
    
    ## Individual Comments
    
    ### Comment 1
    <location path="src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java" line_range="75" />
    <code_context>
    +            return getSpecialistsForService();
    +        }
    +
    +        MiscUtils.getLogger().warn("SpecialistList2Action: invalid method parameter: " + method);
    +        return NONE;
    +    }
    </code_context>
    <issue_to_address>
    **suggestion (bug_risk):** Consider returning an explicit 400 status for invalid `method` values instead of just `NONE`.
    
    Right now, an invalid/missing `method` just logs a warning and returns `NONE`, which will go through normal Struts result handling. For this AJAX/JSON endpoint, consider instead sending a `400 Bad Request` (as done for `serviceId` errors in `getSpecialistsForService`) and returning `null`, so clients can clearly distinguish a bad request from a valid empty result.
    
    Suggested implementation:
    
    ```java
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            MiscUtils.getLogger().warn("SpecialistList2Action: invalid method parameter: " + method);
            return null;
    
    ```
    
    1. Ensure that this class has access to the `HttpServletResponse` instance as `response` (for example via `ServletActionContext.getResponse()` or dependency injection), consistent with how `getSpecialistsForService` sets the 400 status.
    2. Confirm that `javax.servlet.http.HttpServletResponse` is imported; if it isn't already (it likely is, given the existing 400 handling), add `import javax.servlet.http.HttpServletResponse;` at the top of the file.
    </issue_to_address>
    
    ### Comment 2
    <location path="src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java" line_range="61" />
    <code_context>
    
    +    @Override
    +    public List<SpecialistListDTO> findAllListDTOs() {
    +        Query query = entityManager.createQuery(
    +                "SELECT NEW ca.openosp.openo.consultation.dto.SpecialistListDTO("
    +                        + "x.id, x.firstName, x.lastName, x.professionalLetters, "
    </code_context>
    <issue_to_address>
    **suggestion:** Use a TypedQuery for `SpecialistListDTO` instead of a raw `Query` to improve type safety.
    
    You can switch this to `TypedQuery<SpecialistListDTO>` (via cast or a helper on `entityManager.createQuery(...)`) so `getResultList()` is type-safe and you can remove the `@SuppressWarnings("unchecked")` on the call.
    
    Suggested implementation:
    
    ```java
        @Override
        public List<SpecialistListDTO> findAllListDTOs() {
            TypedQuery<SpecialistListDTO> query = entityManager.createQuery(
                    "SELECT NEW ca.openosp.openo.consultation.dto.SpecialistListDTO("
                            + "x.id, x.firstName, x.lastName, x.professionalLetters, "
                            + "x.streetAddress, x.phoneNumber, x.faxNumber) "
                            + "FROM ProfessionalSpecialist x "
                            + "ORDER BY x.lastName, x.firstName",
                    SpecialistListDTO.class
            );
    
            List<SpecialistListDTO> results = query.getResultList();
    
            return results;
        }
    
    ```
    
    1. Add an import for `TypedQuery` at the top of `ProfessionalSpecialistDaoImpl.java`, matching your existing JPA package (e.g. `import javax.persistence.TypedQuery;` or `import jakarta.persistence.TypedQuery;`).
    2. If there is an unused `import javax.persistence.Query;` (or `jakarta.persistence.Query;`) after this change, remove it to keep imports clean.
    </issue_to_address>
    
    ### Comment 3
    <location path="src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp" line_range="180" />
    <code_context>
    +                    fragment.appendChild(tr);
    +                }
    +                tbody.appendChild(fragment);
    +                if (idx === Math.min(BATCH_SIZE, data.length)) {
    +                    HideSpin();
    +                }
    </code_context>
    <issue_to_address>
    **suggestion:** Hide the spinner in a more explicit way to avoid relying on an index equality edge case.
    
    Using `idx === Math.min(BATCH_SIZE, data.length)` couples spinner visibility to a specific batching/indexing detail, which makes the intent hard to read and fragile to future batching changes. Consider an explicit "first batch done" condition (e.g., a `firstBatchRendered` flag or a dedicated check before starting the loop) and apply the same approach in `DisplayService.jsp` for consistency.
    
    Suggested implementation:
    
    ```
                    tbody.appendChild(fragment);
                    if (!firstBatchRendered) {
                        firstBatchRendered = true;
                        HideSpin();
                    }
    
    ```
    
    1. In `EditSpecialists.jsp`, declare and initialize `firstBatchRendered` in the JavaScript scope that owns the batching/rendering logic, before the loop that processes `data`, for example:
       `let firstBatchRendered = false;`
    2. Remove any existing `HideSpin()` call that uses the index-based condition:
       `if (idx === Math.min(BATCH_SIZE, data.length)) { HideSpin(); }`
       so that `HideSpin()` is only called via the new `firstBatchRendered` logic.
    3. In `DisplayService.jsp`, apply the same pattern:
       - Introduce a `firstBatchRendered` flag in the appropriate scope.
       - After the first batch’s `tbody.appendChild(fragment);`, call `HideSpin()` guarded by the flag:
         `if (!firstBatchRendered) { firstBatchRendered = true; HideSpin(); }`
       - Remove any index/batch-size–based conditions currently used to call `HideSpin()` there.
    </issue_to_address>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Copy link

    @gemini-code-assist gemini-code-assist bot left a comment

    Choose a reason for hiding this comment

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

    Code Review

    This is an excellent pull request that addresses a critical performance issue on the specialist list pages. The strategy of decoupling the data loading from the initial page render is well-executed. Using a lightweight DTO projection on the backend, a dedicated JSON endpoint, and client-side batch rendering with requestAnimationFrame is a modern and effective solution. The code is clean and the changes significantly improve both server-side resource usage and client-side perceived performance. I have one minor suggestion to improve the robustness of the display name formatting.

    Copy link

    Copilot AI left a comment

    Choose a reason for hiding this comment

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

    Pull request overview

    This PR improves perceived and actual load time for consultation specialist configuration pages by moving the large specialist list out of server-side JSP rendering into a separate JSON endpoint and client-side batched rendering.

    Changes:

    • Replaced JSP <% for %> specialist row rendering with a lightweight shell that fetches specialist data asynchronously and renders in 1,000-row batches.
    • Added SpecialistList2Action Struts2 JSON endpoints backed by a lightweight JPQL constructor projection (SpecialistListDTO).
    • Removed legacy specialist vector-building logic from EctConDisplayServiceUtil and updated Struts mappings accordingly.

    Reviewed changes

    Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

    Show a summary per file
    File Description
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp Removes inline specialist table generation; adds fetch + batched DOM rendering.
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp Same as above, plus “checked” state rendering for a specific service.
    src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp Removes now-unused specialist preloading call.
    src/main/webapp/WEB-INF/classes/struts.xml Registers new SpecialistList2Action endpoint.
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java New JSON action endpoints for specialist list data.
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java Deletes specialist vector-building methods no longer used by JSPs.
    src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java New DTO for JPQL constructor projection (reduced hydration).
    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java Adds JPQL projection query (findAllListDTOs).
    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java Declares new projection method.

    Copy link

    @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

    Caution

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

    ⚠️ Outside diff range comments (1)
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp (1)

    109-127: ⚠️ Potential issue | 🔴 Critical

    Re-add the delete submit field.

    EctConEditSpecialists2Action still decides whether to delete by checking the submitted delete value. This form now renders only the checkboxes, so the delete branch is no longer reachable and row deletion silently stops working.

    Suggested fix
    -                            <td><form action="${pageContext.request.contextPath}/oscarEncounter/EditSpecialists.do" method="post">
    +                            <td><form action="${pageContext.request.contextPath}/oscarEncounter/EditSpecialists.do" method="post">
    +                                <fmt:message key="oscarEncounter.oscarConsultationRequest.config.EditSpecialists.btnDeleteSpecialist" var="deleteSpecialistLabel"/>
    ...
                                     <table>
    @@
                                         <tbody id="specialistBody"></tbody>
                                     </table>
    +                                <input type="submit" name="delete" value="${deleteSpecialistLabel}">
     
                                 </form></td>

    Also applies to: 124-125

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In
    `@src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp`
    around lines 109 - 127, The form in EditSpecialists.jsp no longer includes the
    submit field named "delete" that EctConEditSpecialists2Action uses to detect
    delete actions, so deletion no longer works; restore a submit input (e.g.,
    <input type="submit" name="delete" value="...">) inside the form so the action
    receives the "delete" parameter when users trigger deletions, ensuring the
    delete branch in EctConEditSpecialists2Action is reachable (also add it back
    where the other submit controls at lines ~124-125 were removed).
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Inline comments:
    In `@src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java`:
    - Around line 28-57: The public getters in SpecialistListDTO (getId,
    getFirstName, getLastName, getProfessionalLetters, getStreetAddress,
    getPhoneNumber, getFaxNumber) lack JavaDoc; add Javadoc blocks to each public
    accessor describing the returned value, include an `@return` with the exact return
    type (e.g., "Integer" or "String") and a `@since` tag, and for completeness add an
    `@throws` tag only if the method can throw (for these simple getters indicate
    "none" or omit if your project's style permits); ensure the Javadoc follows the
    repo standard used for the constructor so each getter has full public
    documentation.
    
    In
    `@src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java`:
    - Around line 75-76: The warning log concatenates the raw request parameter
    `method` in SpecialistList2Action which risks log injection; sanitize it before
    logging by encoding with Encode.forJava(method) (from the OWASP Encoder) and use
    the encoded value in the MiscUtils.getLogger().warn call (i.e., replace the
    direct `method` concatenation with the encoded result), ensuring any necessary
    import is added and nulls handled consistently in the SpecialistList2Action
    class.
    
    In
    `@src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp`:
    - Around line 194-196: The serviceId is injected unencoded into an HTML value
    attribute and into the fetch URL, creating XSS risk; update the HTML input/value
    where serviceId is rendered (the value="<%=serviceId %>") to use
    Encode.forHtmlAttribute(serviceId) and update the fetch call building the URL
    (the fetch("<%= request.getContextPath() %>/...&serviceId=<%=serviceId %>")
    fragment) to use a JavaScript/URI-safe encoding such as
    Encode.forJavaScriptBlock(serviceId) or Encode.forUriComponent(serviceId) so the
    rendered string is safe in both the HTML attribute and JavaScript URL contexts.
    
    ---
    
    Outside diff comments:
    In
    `@src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp`:
    - Around line 109-127: The form in EditSpecialists.jsp no longer includes the
    submit field named "delete" that EctConEditSpecialists2Action uses to detect
    delete actions, so deletion no longer works; restore a submit input (e.g.,
    <input type="submit" name="delete" value="...">) inside the form so the action
    receives the "delete" parameter when users trigger deletions, ensuring the
    delete branch in EctConEditSpecialists2Action is reachable (also add it back
    where the other submit controls at lines ~124-125 were removed).
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: defaults

    Review profile: CHILL

    Plan: Pro

    Run ID: 9aff9804-f8d9-4cbe-8e05-0d66c8cb33ca

    📥 Commits

    Reviewing files that changed from the base of the PR and between b244f18 and 07eb0d2.

    📒 Files selected for processing (9)
    • src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java
    • src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java
    • src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java
    • src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java
    • src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java
    • src/main/webapp/WEB-INF/classes/struts.xml
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp
    💤 Files with no reviewable changes (2)
    • src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp
    • src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java

    @LiamStanziani
    Copy link
    Collaborator Author

    Not fully ready as I want to do some more testing with recent changes but want to see another AI review pass through when setting it to ready for review

    @LiamStanziani LiamStanziani marked this pull request as ready for review March 11, 2026 14:32
    @qodo-code-review
    Copy link

    Review Summary by Qodo

    Decouple specialist list data from JSP into JSON endpoint with client-side batch rendering

    ✨ Enhancement

    Grey Divider

    Walkthroughs

    Description
    • Decouple specialist list data from server-side JSP rendering into separate JSON endpoint
    • Implement JPQL constructor projection to fetch only required columns for list display
    • Replace inline JSP loops with client-side batch rendering using fetch() and requestAnimationFrame
    • Remove unused specialist vector construction logic from EctConDisplayServiceUtil
    
    Diagram
    flowchart LR
      A["Server-side JSP<br/>with inline loops"] -->|Before| B["226MB memory<br/>1.7MB HTML<br/>4s delay"]
      C["SpecialistListDTO<br/>projection"] -->|JPQL| D["Lightweight<br/>DTO query"]
      D -->|JSON endpoint| E["SpecialistList2Action"]
      E -->|fetch POST| F["Client-side<br/>batch rendering"]
      F -->|requestAnimationFrame| G["Fast interactive<br/>page load"]
      A -.->|Refactored to| C
      B -.->|Improves to| G
    
    Loading

    Grey Divider

    File Changes

    1. src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java ✨ Enhancement +10/-0

    Add DTO projection method for lightweight specialist queries

    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java


    2. src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java ✨ Enhancement +16/-0

    Implement JPQL constructor projection to SpecialistListDTO

    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDaoImpl.java


    3. src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java ✨ Enhancement +71/-0

    Create lightweight DTO for specialist list display

    src/main/java/ca/openosp/openo/consultation/dto/SpecialistListDTO.java


    View more (9)
    4. src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java ✨ Enhancement +0/-60

    Remove unused specialist vector construction methods

    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/EctConDisplayServiceUtil.java


    5. src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java ✨ Enhancement +216/-0

    Create new Struts2 action for JSON specialist list endpoints

    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java


    6. src/main/webapp/WEB-INF/classes/struts.xml ⚙️ Configuration changes +1/-0

    Register SpecialistList2Action mapping for AJAX requests

    src/main/webapp/WEB-INF/classes/struts.xml


    7. src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp ✨ Enhancement +0/-2

    Remove specialist vector initialization call

    src/main/webapp/oscarEncounter/oscarConsultationRequest/ConsultationFormRequest.jsp


    8. src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp ✨ Enhancement +44/-44

    Replace server-side specialist loop with client-side fetch and batch rendering

    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp


    9. src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp ✨ Enhancement +50/-46

    Replace server-side specialist loop with client-side fetch and batch rendering

    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp


    10. src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java 🧪 Tests +137/-0

    Add integration tests for DTO projection query method

    src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java


    11. src/test-modern/resources/META-INF/persistence.xml ⚙️ Configuration changes +1/-0

    Register ProfessionalSpecialist entity for test persistence unit

    src/test-modern/resources/META-INF/persistence.xml


    12. src/test-modern/resources/test-context-full.xml ⚙️ Configuration changes +3/-0

    Add ProfessionalSpecialistDao bean definition for tests

    src/test-modern/resources/test-context-full.xml


    Grey Divider

    Qodo Logo

    @qodo-code-review
    Copy link

    qodo-code-review bot commented Mar 11, 2026

    Code Review by Qodo

    🐞 Bugs (2) 📘 Rule violations (5) 📎 Requirement gaps (0)

    Grey Divider


    Action required

    1. Wrong EntityManager unitName 📘 Rule violation ⛯ Reliability
    Description
    The modern integration test injects EntityManager with unitName = "entityManagerFactory" instead
    of the required testPersistenceUnit. This can cause tests to run against the wrong persistence
    configuration and become unreliable.
    
    Code

    src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java[R43-44]

    +    @PersistenceContext(unitName = "entityManagerFactory")
    +    private EntityManager entityManager;
    Evidence
    Compliance ID 11 requires modern tests to use `@PersistenceContext(unitName =
    "testPersistenceUnit"). The new test uses entityManagerFactory` instead.
    

    CLAUDE.md
    src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java[43-44]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    The modern JUnit 5 integration test injects an `EntityManager` using the wrong persistence unit name (`entityManagerFactory`) instead of the required `testPersistenceUnit`.
    ## Issue Context
    Compliance requires modern tests to run against the dedicated test persistence unit for reliability.
    ## Fix Focus Areas
    - src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java[43-44]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    2. Missing list renderer JS 🐞 Bug ✓ Correctness
    Description
    DisplayService.jsp and EditSpecialists.jsp call renderSpecialistList(), but the referenced
    /js/specialistListRenderer.js implementation is not present/defined, causing a runtime
    ReferenceError and preventing the specialist table from rendering. This also leaves the spinner
    shown indefinitely on these pages.
    
    Code

    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp[R72-73]

           <link rel="stylesheet" type="text/css" href="<%= request.getContextPath() %>/css/encounterStyles.css">
    +        <script type="text/javascript" src="<%= request.getContextPath() %>/js/specialistListRenderer.js"></script>
    Evidence
    Both JSPs include a new script path and then invoke renderSpecialistList(), but there is no
    definition of renderSpecialistList() anywhere in the webapp sources in this PR checkout, so the
    browser will fail when executing the inline script.
    

    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp[72-73]
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp[165-169]
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[60-61]
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[153-157]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    Both EditSpecialists.jsp and DisplayService.jsp include `/js/specialistListRenderer.js` and call `renderSpecialistList(...)`, but the implementation is not present/defined in the repo at the PR HEAD. This causes runtime JS errors and the pages will not render the specialist table.
    ### Issue Context
    The new JSPs rely on client-side batch rendering and expect a shared renderer function.
    ### Fix Focus Areas
    - src/main/webapp/js/specialistListRenderer.js[1-250]
    - src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp[72-73]
    - src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[60-61]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    3. Privilege check too weak 🐞 Bug ⛨ Security
    Description
    SpecialistList2Action authorizes access using only "_con" read, but the JSP pages that use it are
    guarded by "_admin,_admin.consult" rights (r/w). This allows users lacking the intended
    admin-consult privileges to directly call /oscarEncounter/SpecialistList.do and retrieve specialist
    contact data.
    
    Code

    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java[R62-65]

    +    public String execute() {
    +        if (!securityInfoManager.hasPrivilege(LoggedInInfo.getLoggedInInfoFromSession(request), "_con", "r", null)) {
    +            throw new SecurityException("missing required security object (_con)");
    +        }
    Evidence
    The new JSON endpoint checks a different (weaker) security object than the JSPs that historically
    controlled access to the specialist list UI. Because the endpoint is separately addressable, it must
    enforce at least the same privileges as the pages that depend on it.
    

    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java[62-65]
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[36-39]
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp[34-37]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    `SpecialistList2Action` currently authorizes with `_con` read, but the UI pages that use this endpoint are protected with `_admin,_admin.consult` (rights `r`/`w`). This mismatch allows direct access to the JSON endpoint without the intended admin-consult authorization.
    ### Issue Context
    - EditSpecialists.jsp requires `_admin,_admin.consult` with `w`.
    - DisplayService.jsp requires `_admin,_admin.consult` with `r`.
    The endpoint should mirror these requirements (or be stricter).
    ### Fix Focus Areas
    - src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java[62-73]
    - src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[36-39]
    - src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp[34-37]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



    Remediation recommended

    4. Non-BDD test method names 📘 Rule violation ✓ Correctness
    Description
    Two new JUnit 5 test method names use non-approved underscore patterns (_correctly, _gracefully)
    instead of the required BDD naming conventions. This reduces consistency and makes test intent
    harder to scan and filter.
    
    Code

    src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java[R90-125]

    +    @Test
    +    @Tag("query")
    +    @DisplayName("should populate all DTO fields correctly")
    +    void shouldPopulateAllDTOFields_correctly() {
    +        List<SpecialistListDTO> results = professionalSpecialistDao.findAllListDTOs();
    +
    +        SpecialistListDTO alice = results.stream()
    +                .filter(dto -> "Smith".equals(dto.getLastName()))
    +                .findFirst().orElse(null);
    +
    +        assertThat(alice).isNotNull();
    +        assertThat(alice.getId()).isNotNull();
    +        assertThat(alice.getFirstName()).isEqualTo("Alice");
    +        assertThat(alice.getLastName()).isEqualTo("Smith");
    +        assertThat(alice.getProfessionalLetters()).isEqualTo("MD, FRCPC");
    +        assertThat(alice.getStreetAddress()).isEqualTo("123 Main St");
    +        assertThat(alice.getPhoneNumber()).isEqualTo("555-1234");
    +        assertThat(alice.getFaxNumber()).isEqualTo("555-5678");
    +    }
    +
    +    @Test
    +    @Tag("query")
    +    @DisplayName("should handle null optional fields gracefully")
    +    void shouldHandleNullOptionalFields_gracefully() {
    +        List<SpecialistListDTO> results = professionalSpecialistDao.findAllListDTOs();
    +
    +        SpecialistListDTO carol = results.stream()
    +                .filter(dto -> "Adams".equals(dto.getLastName()))
    +                .findFirst().orElse(null);
    +
    +        assertThat(carol).isNotNull();
    +        assertThat(carol.getProfessionalLetters()).isNull();
    +        assertThat(carol.getStreetAddress()).isNull();
    +        assertThat(carol.getPhoneNumber()).isNull();
    +        assertThat(carol.getFaxNumber()).isNull();
    +    }
    Evidence
    Compliance ID 10 requires BDD-style test naming (e.g., should_when or other approved patterns).
    The methods shouldPopulateAllDTOFields_correctly and shouldHandleNullOptionalFields_gracefully
    do not match the specified conventions.
    

    CLAUDE.md
    src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java[92-114]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    Some new JUnit 5 test method names do not follow the required BDD naming convention.
    ## Issue Context
    Compliance requires consistent BDD-style naming for readability and diagnosability.
    ## Fix Focus Areas
    - src/test-modern/java/ca/openosp/openo/consultation/dao/ProfessionalSpecialistDaoDTOIntegrationTest.java[90-125]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    5. EditSpecialists.jsp missing doc block 📘 Rule violation ✓ Correctness
    Description
    EditSpecialists.jsp does not include the required comprehensive JSP documentation block after the
    copyright header. This violates the mandated JSP maintainability documentation standard for modified
    JSPs.
    
    Code

    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[R1-4]

    +<%--
    
       Copyright (c) 2001-2002. Department of Family Medicine, McMaster University. All Rights Reserved.
       This software is published under the GPL GNU General Public License.
    Evidence
    Compliance ID 14 requires a documentation block after the copyright header describing
    purpose/features/parameters with an @since. The file proceeds directly from the copyright header
    to taglib/import directives without such a block.
    

    CLAUDE.md
    src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[1-31]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    The modified JSP is missing the required documentation block after the copyright header.
    ## Issue Context
    Compliance requires JSPs to document purpose/features/parameters and include an `@since` tag.
    ## Fix Focus Areas
    - src/main/webapp/oscarEncounter/oscarConsultationRequest/config/EditSpecialists.jsp[1-31]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    6. findAllListDTOs() JavaDoc incomplete 📘 Rule violation ✓ Correctness
    Description
    The new public DAO API findAllListDTOs() JavaDoc is missing required metadata such as an @since
    tag. This reduces maintainability and fails the comprehensive JavaDoc requirement for public APIs.
    
    Code

    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java[R38-45]

    +    /**
    +     * Returns lightweight projections of all specialists for list display,
    +     * fetching only the columns needed (id, name, letters, address, phone, fax).
    +     * Avoids full entity hydration of heavy text fields.
    +     *
    +     * @return List&lt;SpecialistListDTO&gt; specialists ordered by last name, first name
    +     */
    +    List<SpecialistListDTO> findAllListDTOs();
    Evidence
    Compliance ID 12 requires comprehensive JavaDoc on all public classes and methods, including an
    accurate @since. The new public method has JavaDoc but omits @since.
    

    CLAUDE.md
    src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java[38-45]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    The new public DAO method JavaDoc is missing required comprehensive metadata (e.g., `@since`).
    ## Issue Context
    Public APIs in this codebase must include comprehensive JavaDoc for maintainability in a regulated environment.
    ## Fix Focus Areas
    - src/main/java/ca/openosp/openo/commn/dao/ProfessionalSpecialistDao.java[38-45]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    View more (1)
    7. execute() JavaDoc missing @throws 📘 Rule violation ✓ Correctness
    Description
    SpecialistList2Action.execute() can throw a SecurityException but its JavaDoc does not document
    this with an @throws tag. This fails the comprehensive JavaDoc requirement for public methods.
    
    Code

    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java[R56-65]

    +    /**
    +     * Main dispatcher that routes to the appropriate method based on 'method' parameter.
    +     *
    +     * @return String action result (null when response is written directly)
    +     */
    +    @Override
    +    public String execute() {
    +        if (!securityInfoManager.hasPrivilege(LoggedInInfo.getLoggedInInfoFromSession(request), "_con", "r", null)) {
    +            throw new SecurityException("missing required security object (_con)");
    +        }
    Evidence
    Compliance ID 12 requires public methods to document thrown exceptions. The method JavaDoc includes
    @return but omits @throws, while the implementation throws SecurityException on authorization
    failure.
    

    CLAUDE.md
    src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java[56-65]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    The public `execute()` method throws `SecurityException` but does not document it in JavaDoc.
    ## Issue Context
    Comprehensive JavaDoc on public methods must include `@throws` for thrown exceptions.
    ## Fix Focus Areas
    - src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java[56-65]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    Grey Divider

    ⓘ The new review experience is currently in Beta. Learn more

    Grey Divider

    Qodo Logo

    Copy link

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey - I've found 2 issues, and left some high level feedback:

    • In SpecialistList2Action, the JSON responses are positional arrays consumed via s[0], s[1], etc.; consider returning objects with named fields instead to make the client code more self-documenting and less brittle to ordering changes.
    • The new SpecialistList2Action endpoints are documented as POST-only but don't currently enforce HTTP method; adding an explicit request.getMethod() check and rejecting non-POST requests would better align behavior with the intended contract.
    • Rather than resolving HttpServletRequest, HttpServletResponse, and DAOs at field initialization time via ServletActionContext/SpringUtils, consider using Struts2 injection (e.g., ServletRequestAware/ServletResponseAware or setter injection) to improve testability and avoid static lookups in your action.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - In `SpecialistList2Action`, the JSON responses are positional arrays consumed via `s[0]`, `s[1]`, etc.; consider returning objects with named fields instead to make the client code more self-documenting and less brittle to ordering changes.
    - The new `SpecialistList2Action` endpoints are documented as POST-only but don't currently enforce HTTP method; adding an explicit `request.getMethod()` check and rejecting non-POST requests would better align behavior with the intended contract.
    - Rather than resolving `HttpServletRequest`, `HttpServletResponse`, and DAOs at field initialization time via `ServletActionContext`/`SpringUtils`, consider using Struts2 injection (e.g., `ServletRequestAware`/`ServletResponseAware` or setter injection) to improve testability and avoid static lookups in your action.
    
    ## Individual Comments
    
    ### Comment 1
    <location path="src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java" line_range="67-80" />
    <code_context>
    +            throw new SecurityException("missing required security object (_con)");
    +        }
    +
    +        String method = request.getParameter("method");
    +
    +        if ("getSpecialists".equals(method)) {
    +            return getSpecialists();
    +        } else if ("getSpecialistsForService".equals(method)) {
    </code_context>
    <issue_to_address>
    **🚨 suggestion (security):** Consider validating the HTTP method to match the documented POST-only API contract
    
    `execute()` routes solely on the `method` parameter and will process requests regardless of the actual HTTP verb. To enforce the POST-only contract, add a `request.getMethod()` check and return an appropriate error (e.g., 405 or 400) for non-POST requests. This helps prevent unintended GET usage and reduces CSRF/caching proxy risks.
    
    ```suggestion
            String httpMethod = request.getMethod();
            if (!"POST".equalsIgnoreCase(httpMethod)) {
                try {
                    response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "This endpoint only supports POST");
                } catch (IOException e) {
                    MiscUtils.getLogger().error("Error sending 405 response", e);
                }
                return null;
            }
    
            String method = request.getParameter("method");
    
            if ("getSpecialists".equals(method)) {
                return getSpecialists();
            } else if ("getSpecialistsForService".equals(method)) {
                return getSpecialistsForService();
            }
    
            try {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid or missing method parameter");
            } catch (IOException e) {
                MiscUtils.getLogger().error("Error sending 400 response", e);
            }
            return null;
    ```
    </issue_to_address>
    
    ### Comment 2
    <location path="src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java" line_range="90" />
    <code_context>
    +     *
    +     * @return null (response written directly to output stream)
    +     */
    +    private String getSpecialists() {
    +        try {
    +            List<SpecialistListDTO> specialists = professionalSpecialistDao.findAllListDTOs();
    </code_context>
    <issue_to_address>
    **issue (complexity):** Consider refactoring the action to extract shared JSON row/response-building helpers and access dependencies via explicit getters instead of instance fields to reduce duplication and hidden state.
    
    You can cut a fair bit of complexity/duplication without changing behavior by extracting the common row-building and response-writing logic, and by making the dependencies more explicit.
    
    ### 1. Extract shared row-building / response-writing
    
    `getSpecialists()` and `getSpecialistsForService()` both manually build very similar JSON arrays. You can centralize that logic and make each method a thin wrapper:
    
    ```java
    private ArrayNode buildRow(SpecialistListDTO dto) {
        ArrayNode row = MAPPER.createArrayNode();
        row.add(dto.getId());
        row.add(buildDisplayName(dto));
        row.add(StringUtils.defaultString(dto.getStreetAddress()));
        row.add(StringUtils.defaultString(dto.getPhoneNumber()));
        row.add(StringUtils.defaultString(dto.getFaxNumber()));
        return row;
    }
    
    private void writeSpecialistRows(List<SpecialistListDTO> specialists,
                                    Set<String> assignedIds) throws IOException {
        ArrayNode array = MAPPER.createArrayNode();
        for (SpecialistListDTO dto : specialists) {
            ArrayNode row = buildRow(dto);
            if (assignedIds != null) {
                row.add(assignedIds.contains(dto.getId().toString()));
            }
            array.add(row);
        }
        writeJsonResponse(array);
    }
    ```
    
    Then the action methods become smaller and easier to read:
    
    ```java
    private String getSpecialists() {
        try {
            List<SpecialistListDTO> specialists = professionalSpecialistDao.findAllListDTOs();
            writeSpecialistRows(specialists, null);
            return null;
        } catch (Exception e) {
            return handleServerError("Error loading specialist list", e);
        }
    }
    
    private String getSpecialistsForService() {
        try {
            String serviceIdParam = request.getParameter("serviceId");
            Integer serviceId = ConversionUtils.fromIntString(serviceIdParam);
    
            if (serviceId == null) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                        "Missing or invalid serviceId parameter");
                return null;
            }
    
            List<SpecialistListDTO> specialists = professionalSpecialistDao.findAllListDTOs();
            Set<String> assignedIds = loadAssignedSpecialistIds(serviceId);
    
            writeSpecialistRows(specialists, assignedIds);
            return null;
        } catch (Exception e) {
            return handleServerError("Error loading specialist list for service", e);
        }
    }
    ```
    
    This directly addresses the duplication and makes it clear that the only behavioral difference is whether `assignedIds` is present.
    
    ### 2. Make dependencies explicit and avoid static field initialization
    
    You can reduce implicit complexity by not initializing `request`, `response`, and DAOs at field declaration time. Instead, resolve them where needed or via a small helper. This keeps lifecycle assumptions local:
    
    ```java
    private HttpServletRequest getRequest() {
        return ServletActionContext.getRequest();
    }
    
    private HttpServletResponse getResponse() {
        return ServletActionContext.getResponse();
    }
    
    private SecurityInfoManager getSecurityInfoManager() {
        return SpringUtils.getBean(SecurityInfoManager.class);
    }
    
    private ProfessionalSpecialistDao getProfessionalSpecialistDao() {
        return SpringUtils.getBean(ProfessionalSpecialistDao.class);
    }
    ```
    
    Then update usage accordingly:
    
    ```java
    @Override
    public String execute() {
        HttpServletRequest request = getRequest();
        if (!getSecurityInfoManager()
                .hasPrivilege(LoggedInInfo.getLoggedInInfoFromSession(request), "_con", "r", null)) {
            throw new SecurityException("missing required security object (_con)");
        }
    
        String method = request.getParameter("method");
        // ...
    }
    ```
    
    And in the methods:
    
    ```java
    private String getSpecialists() {
        HttpServletResponse response = getResponse();
        try {
            List<SpecialistListDTO> specialists = getProfessionalSpecialistDao().findAllListDTOs();
            writeSpecialistRows(specialists, null);
            return null;
        } catch (Exception e) {
            return handleServerError("Error loading specialist list", e);
        }
    }
    ```
    
    This keeps behavior identical but makes dependencies clearer and avoids hidden state on the action instance.
    </issue_to_address>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Copy link

    @cubic-dev-ai cubic-dev-ai bot left a comment

    Choose a reason for hiding this comment

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

    2 issues found across 12 files

    Confidence score: 3/5

    • There is a concrete runtime regression risk in src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp: it references specialistListRenderer.js, but the file is missing, which can cause a 404 and leave renderSpecialistList undefined.
    • src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java dispatches based on the method query parameter without enforcing HTTP verb checks, creating a plausible CSRF exposure path if GET is exempt from token validation.
    • Given a user-facing script failure plus a moderate security concern (both with fairly high confidence), this carries some merge risk and merits validation before release.
    • Pay close attention to src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp and src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java - missing client script wiring and request-method handling could cause runtime breakage and unintended state-changing access.
    Prompt for AI agents (unresolved issues)
    
    Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
    
    
    <file name="src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp">
    
    <violation number="1" location="src/main/webapp/oscarEncounter/oscarConsultationRequest/config/DisplayService.jsp:73">
    P2: `specialistListRenderer.js` is referenced but does not exist in the webapp sources, so the script will 404 and `renderSpecialistList` will be undefined at runtime.</violation>
    </file>
    
    <file name="src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java">
    
    <violation number="1" location="src/main/java/ca/openosp/openo/encounter/oscarConsultationRequest/config/pageUtil/SpecialistList2Action.java:67">
    P2: The `execute()` method dispatches on the `method` query parameter without verifying the actual HTTP verb. Since CSRFGuard typically exempts GET from token validation (`UnprotectedMethods=GET`), a GET request to this endpoint bypasses CSRF protection entirely. Add an HTTP method check and return `405 Method Not Allowed` for non-POST requests to enforce the documented POST-only contract.</violation>
    </file>
    

    Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

    @LiamStanziani
    Copy link
    Collaborator Author

    LiamStanziani commented Mar 11, 2026

    Will need to review the remaining comments and do some testing.

    @LiamStanziani
    Copy link
    Collaborator Author

    Good for review at this point

    @LiamStanziani LiamStanziani requested a review from D3V41 March 11, 2026 19:09
    @LiamStanziani LiamStanziani linked an issue Mar 11, 2026 that may be closed by this pull request
    2 tasks
    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.

    [Performance]: Edit Specialists page is very slow to load

    2 participants