Skip to content

RG-T148 Search and HTML Fixes, Custom Number Ordering, Dispatch fixes - #562

Merged
ucswift merged 3 commits into
masterfrom
develop
Oct 24, 2026
Merged

ucswift merged 3 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Summary

This pull request delivers the RG-T148 search, HTML safety, custom numbering, and dispatch updates.

HTML safety and rich-text handling

  • Adds separate sanitization paths for:
    • Stored rich text.
    • Rich text rendered for display.
    • HTML converted to readable plain text for search, previews, and excerpts.
  • Removes scripts, event handlers, styles, frames, active content, and unsafe URLs while preserving approved formatting.
  • Applies sanitization when saving rich-text fields across calls, notes, messages, contacts, protocols, documents, calendar items, logs, inventory, work shifts, and related records.
  • Replaces unsafe Html.Raw usage for stored content with sanitized rendering or normal Razor encoding.
  • Adds HTML-safe JSON serialization for values embedded in scripts, including localized strings, map data, route data, and user-controlled values.
  • Escapes dynamic values inserted into JavaScript-generated HTML, DataTables, notifications, maps, tooltips, and dropdowns.
  • Improves search projections and existing search results by converting previously stored HTML into readable plain text.
  • Preserves plain text containing angle brackets and non-ASCII characters correctly.
  • Adds comprehensive security and sanitization tests.

Custom number searching

  • Introduces shared identifier parsing and normalization for department-formatted numbers.
  • Number searches now ignore separators, casing, and sequence-number padding, and can match by individual number components.
  • Adds number filtering to bids, invoices, disclosure requests, inspections, permits, investigations, and work orders.
  • Investigation case searches also match live evidence numbers associated with the caller’s cases.
  • Adds number filter controls to the relevant web pages and API endpoints, while preserving filters during pagination.
  • Adds indexed number keys for global search and records search so custom-formatted call, record, invoice, and bid numbers can be found by any part.
  • Increases the global and records search schema versions to trigger index regeneration for the new number fields.

Number ordering and numbering-year behavior

  • Adds custom call-number sort keys so call lists order numbers by their issuance year/date and sequence instead of lexicographic text order.
  • Supports legacy numbers and numbers created before a department changed its numbering pattern.
  • Updates call grids, archived calls, scheduled calls, pending calls, shared links, and related views to use the new ordering.
  • Adds department numbering-year windows for records and incident reports.
  • Year filters and year lists now follow the department’s configured calendar or fiscal numbering year and department time zone, rather than always using the UTC calendar year.

Dispatch updates

  • Refactors the nearest-unit board so dispatch unit lists are populated from the same ranked data.
  • Adds unit tabs for all units, ungrouped units, and station groups.
  • Keeps unit selections synchronized across tabs and the submitted dispatch inputs.
  • Adds ranked ETA, position, crew, role mix, status, and map-position information to dispatch lists.
  • Adds a map view for a selected unit or responder alongside the call location.
  • Limits nearest-unit personnel recommendations to the same department member set exposed by dispatch lists.
  • Adds station-group identifiers and improved loading/error text for unit lists.
  • Removes duplicate dispatch-board rendering and simplifies New Call and Update Call behavior.
  • Adds localized dispatch strings for the new unit-list and map states.

Inventory report presentation

  • Adds localized summaries for all inventory report types.
  • Displays report titles, descriptions, and actions consistently on the reports page.
  • Provides summaries in the supported locales included in the resource files.

Validation and regression coverage

  • Adds unit, service, search, security, repository, and cross-database tests covering:
    • HTML sanitization and plain-text conversion.
    • Safe script and JSON rendering.
    • Custom number searches and ordering.
    • Fiscal and time-zone-aware numbering years.
    • Dispatch personnel limits and unit synchronization.
    • Inventory report localization.
    • SQL Server and PostgreSQL number filtering behavior.

@request-info

request-info Bot commented Oct 23, 2026

Copy link
Copy Markdown

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 225 billable files and costs up to $56.25.

View limit details

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 343b379 and 14238fe.

Files ignored due to path filters: 72
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/ContractorBilling/ContractorBilling.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.uk.resx is excluded by !**/*.resx
  • Tests/Resgrid.Tests/Framework/StringHelperTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Localization/RazorOutputEncodingTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Repositories/NumberFilterDatabaseTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/FakeIncidentStore.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/FakeRmsStore.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/IncidentReportsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordNumberingTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsDisclosureServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsHydrantsAndPermitsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsInvestigationsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsSearchIndexMaintenanceServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsSearchTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsRetentionDatabaseTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Search/GlobalSearchTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Search/IdentifierSearchKeysTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Search/SearchProjectionServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Search/UnifiedSearchHtmlTextTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Security/Audit20261005/CallsUnitsApiTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Security/Audit20261005/CallsUnitsMvcTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Security/StoredHtmlRenderingTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/CallNumberingTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/ContractorBillingServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/InventoryM5ReportTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/NearestUnitServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/PersonnelMapLabelTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/UnitActiveRolesNotificationTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/WorkOrderDatabaseTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/MappingControllerPersonnelLabelTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/MappingControllerUnitMapTests.cs is excluded by !**/Tests/**
Files selected for processing: 225
  • Core/Resgrid.Framework/StringHelpers.cs
  • Core/Resgrid.Model/CallNumbering.cs
  • Core/Resgrid.Model/EventingTypes.cs
  • Core/Resgrid.Model/Events/UnitActiveRolesUpdatedEvent.cs
  • Core/Resgrid.Model/IdentifierSearchKeys.cs
  • Core/Resgrid.Model/NumberingYear.cs
  • Core/Resgrid.Model/PersonnelMapLabel.cs
  • Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs
  • Core/Resgrid.Model/Records/RecordsSearchContracts.cs
  • Core/Resgrid.Model/Repositories/IContractorRepositories.cs
  • Core/Resgrid.Model/Repositories/IInvoicingRepositories.cs
  • Core/Resgrid.Model/Repositories/IRmsIncidentRepositories.cs
  • Core/Resgrid.Model/Repositories/IRmsPreventionRepositories.cs
  • Core/Resgrid.Model/Repositories/IRmsRepositories.cs
  • Core/Resgrid.Model/Search/SearchContracts.cs
  • Core/Resgrid.Model/Services/IBidsService.cs
  • Core/Resgrid.Model/Services/IIncidentReportsService.cs
  • Core/Resgrid.Model/Services/IRecordsDisclosureService.cs
  • Core/Resgrid.Model/Services/IRecordsInvestigationsService.cs
  • Core/Resgrid.Model/Services/IRecordsService.cs
  • Core/Resgrid.Model/Services/IUnitsService.cs
  • Core/Resgrid.Model/Services/IWorkOrdersService.cs
  • Core/Resgrid.Search/GlobalIndexFields.cs
  • Core/Resgrid.Search/GlobalSearchDocumentBuilder.cs
  • Core/Resgrid.Search/LuceneGlobalSearchService.cs
  • Core/Resgrid.Search/LuceneRecordsSearchService.cs
  • Core/Resgrid.Search/RecordsIndexFields.cs
  • Core/Resgrid.Search/RecordsSearchDocumentBuilder.cs
  • Core/Resgrid.Services/CalendarService.cs
  • Core/Resgrid.Services/ContactsService.cs
  • Core/Resgrid.Services/InventoryReportDocuments.cs
  • Core/Resgrid.Services/InventoryService.cs
  • Core/Resgrid.Services/Invoicing/BidsService.cs
  • Core/Resgrid.Services/NearestUnitService.cs
  • Core/Resgrid.Services/ProtocolsService.cs
  • Core/Resgrid.Services/Records/IncidentReportsService.cs
  • Core/Resgrid.Services/Records/RecordsDisclosureService.cs
  • Core/Resgrid.Services/Records/RecordsInvestigationsService.cs
  • Core/Resgrid.Services/Records/RecordsNumberingService.cs
  • Core/Resgrid.Services/Records/RecordsService.cs
  • Core/Resgrid.Services/Search/SearchProjectionService.cs
  • Core/Resgrid.Services/Search/UnifiedSearchService.cs
  • Core/Resgrid.Services/UnitsService.cs
  • Core/Resgrid.Services/WorkLogsService.cs
  • Core/Resgrid.Services/WorkOrdersService.cs
  • Core/Resgrid.Services/WorkShiftsService.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs
  • Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs
  • Repositories/Resgrid.Repositories.DataRepository/ContractorRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/InvoicingRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsIncidentRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsPreventionRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/WorkOrderRepository.cs
  • Web/Resgrid.Web.Eventing/Worker.cs
  • Web/Resgrid.Web.Services/Controllers/v4/BidsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/DisclosuresController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/InvoicesController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordInspectionsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordInvestigationsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordPermitsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/UnitRolesController.cs
  • Web/Resgrid.Web.Services/Models/v4/Mapping/GetUnitMapDataResult.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web/Areas/User/Apps/src/components/map/LeafletMapView.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/map/MapboxMapView.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/map/mapTypes.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/unitMap/UnitMapElement.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/unitMap/unitMap.css
  • Web/Resgrid.Web/Areas/User/Apps/src/components/unitMap/unitMapModel.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/elements.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/runtime/signalr.ts
  • Web/Resgrid.Web/Areas/User/Controllers/BidsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/ContactsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DisclosuresController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/InvoicingController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/LinksController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/MappingController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/ProtocolsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordDeploymentsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordInvestigationsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordPermitsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/UnitsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/WorkforceController.cs
  • Web/Resgrid.Web/Areas/User/Models/Calls/NewCallView.cs
  • Web/Resgrid.Web/Areas/User/Models/ContractorBilling/ContractorViews.cs
  • Web/Resgrid.Web/Areas/User/Models/Dispatch/CallListJson.cs
  • Web/Resgrid.Web/Areas/User/Models/Invoicing/InvoicingViews.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/DisclosureViewModels.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/RecordsRms5ViewModels.cs
  • Web/Resgrid.Web/Areas/User/Models/Units/UnitForListJson.cs
  • Web/Resgrid.Web/Areas/User/Models/Units/UnitMapView.cs
  • Web/Resgrid.Web/Areas/User/Views/Bids/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Bids/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CalOesMars/Rate.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Calendar/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Calendar/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Calendar/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Certifications/Settings.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Checklists/_ProtectionScripts.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contacts/Add.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contacts/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contacts/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contracts/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/RegionEditor.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomStatuses/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/DataProtection/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/DeploymentWizard/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Disclosures/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/AddArchivedCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/CloseCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/DeleteCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/FlagCallFile.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/FlagCallImage.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/FlagCallNote.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/PendingCalls.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/UpdateCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/_DispatchUnitLists.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/_NearestUnitsBoard.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Documents/EditDocument.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Documents/NewDocument.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Documents/ViewDocument.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Groups/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Home/EditUserProfile.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Inventory/ViewEntry.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Invoicing/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Logs/LogExport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Logs/NewLog.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Logs/ViewLog.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Messages/Compose.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Messages/ViewMessage.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Notes/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Notes/NewNote.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Notes/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Profile/Certifications.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Protocols/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Protocols/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RateSchedules/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInspections/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordPermits/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordsAnalytics/Readiness.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/FlaggedCallNotesReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/LogReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/ArchivedView.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/Directions.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Security/Sso.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_AdpRevealScripts.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shifts/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Trainings/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Trainings/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Trainings/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Trainings/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/TwoFactor/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Units/AddLog.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Units/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Units/ViewLogs.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Units/ViewMap.cshtml
  • Web/Resgrid.Web/Areas/User/Views/WorkOrders/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/WorkOrders/_FilterFields.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workforce/CompensationProfile.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workforce/ResourceCosts.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workshifts/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workshifts/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workshifts/ViewDay.cshtml
  • Web/Resgrid.Web/Helpers/HtmlHelpers.cs
  • Web/Resgrid.Web/wwwroot/System.Collections.Specialized.OrderedDictionary
  • Web/Resgrid.Web/wwwroot/js/app/common/notifications/resgrid.common.notifications.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/connect/resgrid.connect.posts.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/custommaps/resgrid.custommaps.regioneditor.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/department/resgrid.department.recipientsgrid.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/department/resgrid.department.smallstationsgrid.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.addArchivedCall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.archive.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.callData.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.dashboard.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.scheduledcalls.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.smallallcallgrid.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.smallcallgrid.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.viewcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.waitingcalls.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/forms/resgrid.forms.newform.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/groups/resgrid.groups.editgroup.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/groups/resgrid.groups.newgroup.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/indoormaps/resgrid.indoormaps.zoneeditor.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.adjust.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.byunit.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.history.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.index.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.manageTypes.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/links/resgrid.links.viewdata.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/logs/resgrid.logs.index.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/messages/resgrid.messages.inbox.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/messages/resgrid.messages.outbox.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/notifications/resgrid.notifications.addNotification.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/orders/resgrid.orders.index.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/personnel/resgrid.personnel.viewevents.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/protocols/resgrid.protocols.newprotocol.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/shifts/resgrid.shifts.shiftStaffing.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/types/resgrid.types.listordering.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/units/resgrid.units.viewevents.js
  • Web/Resgrid.Web/wwwroot/js/app/resgrid.js
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@Resgrid-Bot

This comment has been minimized.


private static string EncodeAngleBrackets(string text) => text.Replace("<", "&lt;").Replace(">", "&gt;");

private static readonly Regex HtmlTagToken = new Regex(@"<!--|<!doctype|<(/?)([a-zA-Z][\w:.-]*)(\s[^<>]*)?/?>",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Regex processing without a timeout can allow crafted HTML input to consume excessive CPU and cause a denial-of-service condition. Configure a timeout for HtmlTagToken and the other Regex instances at Core/Resgrid.Framework/StringHelpers.cs:191, 194, and 316, Core/Resgrid.Model/CallNumbering.cs:258, and the listed test files.

Kody rule violation: Specify Timeout for Regular Expressions

Prompt for LLM

File Core/Resgrid.Framework/StringHelpers.cs:

Line 174:

Regex processing without a timeout can allow crafted HTML input to consume excessive CPU and cause a denial-of-service condition. Configure a timeout for HtmlTagToken and the other Regex instances at Core/Resgrid.Framework/StringHelpers.cs:191, 194, and 316, Core/Resgrid.Model/CallNumbering.cs:258, and the listed test files.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment thread Core/Resgrid.Framework/StringHelpers.cs Outdated
var cleanHtml = SanitizeHtml(source);
if (cleanHtml == null)
{
Logging.LogError("Following string could not be stripped of Word HTML: " + source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Logging.LogError concatenates source into the message, potentially exposing sensitive input and producing unstructured logs. Emit a structured error containing the operation name and a non-sensitive identifier, including the occurrences in Web/Resgrid.Web/Areas/User/Controllers/DisclosuresController.cs:78 and Core/Resgrid.Framework/StringHelpers.cs:121 and 145.

Kody rule violation: Include error context in structured logs

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", sourceId = source.GetHashCode() });
Prompt for LLM

File Core/Resgrid.Framework/StringHelpers.cs:

Line 85:

Logging.LogError concatenates source into the message, potentially exposing sensitive input and producing unstructured logs. Emit a structured error containing the operation name and a non-sensitive identifier, including the occurrences in Web/Resgrid.Web/Areas/User/Controllers/DisclosuresController.cs:78 and Core/Resgrid.Framework/StringHelpers.cs:121 and 145.

Suggested Code:

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", sourceId = source.GetHashCode() });

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment thread Core/Resgrid.Framework/StringHelpers.cs Outdated
var cleanHtml = SanitizeHtml(source);
if (cleanHtml == null)
{
Logging.LogError("Following string could not be stripped of Word HTML: " + source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Logging.LogError writes the raw source text, which may contain personal data or secrets. Log only a consistent hash or another non-sensitive identifier.

Kody rule violation: Mask PII and secrets in logs

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", sourceHash = Hash(source) });
Prompt for LLM

File Core/Resgrid.Framework/StringHelpers.cs:

Line 85:

Logging.LogError writes the raw source text, which may contain personal data or secrets. Log only a consistent hash or another non-sensitive identifier.

Suggested Code:

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", sourceHash = Hash(source) });

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment thread Core/Resgrid.Framework/StringHelpers.cs Outdated
var cleanHtml = SanitizeHtml(source);
if (cleanHtml == null)
{
Logging.LogError("Following string could not be stripped of Word HTML: " + source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Logging.LogError writes rich text that may contain PHI, creating a sensitive-data exposure in application logs. Remove the raw source and retain only non-identifying troubleshooting metadata such as traceId.

Kody rule violation: Do not log PHI; mask and drop sensitive fields

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", traceId });
Prompt for LLM

File Core/Resgrid.Framework/StringHelpers.cs:

Line 85:

Logging.LogError writes rich text that may contain PHI, creating a sensitive-data exposure in application logs. Remove the raw source and retain only non-identifying troubleshooting metadata such as traceId.

Suggested Code:

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", traceId });

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment thread Core/Resgrid.Framework/StringHelpers.cs Outdated
var cleanHtml = SanitizeHtml(source);
if (cleanHtml == null)
{
Logging.LogError("Following string could not be stripped of Word HTML: " + source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Logging.LogError writes the raw source without the diagnostic purpose or lawful-basis metadata required for sensitive-data processing. Redact or hash source and attach purpose="diagnostics" and lawful_basis="legitimate_interest" metadata.

Kody rule violation: Redact PII in logs and metrics by default

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", sourceHash = Hash(source), gdpr = new { purpose = "diagnostics", lawful_basis = "legitimate_interest" } });
Prompt for LLM

File Core/Resgrid.Framework/StringHelpers.cs:

Line 85:

Logging.LogError writes the raw source without the diagnostic purpose or lawful-basis metadata required for sensitive-data processing. Redact or hash source and attach purpose="diagnostics" and lawful_basis="legitimate_interest" metadata.

Suggested Code:

Logging.LogError("HTML sanitization failed", new { operation = "SanitizeHtmlInString", sourceHash = Hash(source), gdpr = new { purpose = "diagnostics", lawful_basis = "legitimate_interest" } });

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


private static int Group(Match match, string name)
{
return match.Groups[name].Success ? int.Parse(match.Groups[name].Value, NumberStyles.None, CultureInfo.InvariantCulture) : 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

int.Parse on user or I/O input can throw a FormatException when the capture value is invalid. Use a TryParse-style API with NumberStyles.None and CultureInfo.InvariantCulture, and handle parsing failure explicitly.

Kody rule violation: Use TryParse for string conversions

Prompt for LLM

File Core/Resgrid.Model/CallNumbering.cs:

Line 301:

int.Parse on user or I/O input can throw a FormatException when the capture value is invalid. Use a TryParse-style API with NumberStyles.None and CultureInfo.InvariantCulture, and handle parsing failure explicitly.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Task<IEnumerable<Bid>> GetForDepartmentAsync(int departmentId, int? status, int skip, int take);
Task<int> CountForDepartmentAsync(int departmentId, int? status);
/// <summary>Newest number first; <paramref name="number"/> (optional) is part of the bid number as issued (IdentifierSearchKeys.ContainsParts).</summary>
Task<IEnumerable<Bid>> GetForDepartmentAsync(int departmentId, int? status, string number, int skip, int take);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Adding the required number parameter to IContractorRepositories.GetForDepartmentAsync changes the interface contract and may break existing implementations and callers. Add a BREAKING CHANGE section documenting affected consumers and migration steps for supplying the new number argument.

Kody rule violation: Call out breaking changes explicitly

Task<IEnumerable<Bid>> GetForDepartmentAsync(int departmentId, int? status, string number, int skip, int take);
Prompt for LLM

File Core/Resgrid.Model/Repositories/IContractorRepositories.cs:

Line 65:

Adding the required number parameter to IContractorRepositories.GetForDepartmentAsync changes the interface contract and may break existing implementations and callers. Add a BREAKING CHANGE section documenting affected consumers and migration steps for supplying the new number argument.

Suggested Code:

Task<IEnumerable<Bid>> GetForDepartmentAsync(int departmentId, int? status, string number, int skip, int take);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

public async Task<Inventory> SaveInventoryAsync(Inventory inventory, CancellationToken cancellationToken = default(CancellationToken))
{
await GuardLegacyWriteAsync(inventory.DepartmentId);
inventory.Note = StringHelpers.SanitizeHtmlForStorage(inventory.Note);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Calling synchronous StringHelpers.SanitizeHtmlForStorage inside an async method can perform CPU-bound or blocking work on the request thread and reduce asynchronous scalability. Provide and await StringHelpers.SanitizeHtmlForStorageAsync, or explicitly move the CPU-bound operation off the request thread.

Kody rule violation: Use Awaitable Methods in Async Code

inventory.Note = await StringHelpers.SanitizeHtmlForStorageAsync(inventory.Note);
Prompt for LLM

File Core/Resgrid.Services/InventoryService.cs:

Line 60:

Calling synchronous StringHelpers.SanitizeHtmlForStorage inside an async method can perform CPU-bound or blocking work on the request thread and reduce asynchronous scalability. Provide and await StringHelpers.SanitizeHtmlForStorageAsync, or explicitly move the CPU-bound operation off the request thread.

Suggested Code:

inventory.Note = await StringHelpers.SanitizeHtmlForStorageAsync(inventory.Note);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment on lines +58 to +62
public static NumberingYearWindow YearWindow(RecordsNumberingConfig config, int year, string timeZone)
{
var start = (config ?? new RecordsNumberingConfig()).YearStart();
timeZone = KnownZone(timeZone);
return new NumberingYearWindow(year, LocalToUtc(start.StartOfYear(year), timeZone), LocalToUtc(start.StartOfYear(year + 1), timeZone));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

YearWindow constructs the next boundary from year + 1 without validating the requested year, so public records search controllers passing user-supplied int year values such as 9999 or 0 can trigger DateTime/numbering-year range exceptions and return server errors instead of controlled empty or invalid-request responses. Validate year before constructing the window, requiring 1 <= year < 9999 or rejecting the request at the controller/service boundary with a controlled validation result.

if (year < 1 || year >= DateTime.MaxValue.Year) throw new ArgumentOutOfRangeException(nameof(year));
return new NumberingYearWindow(year, LocalToUtc(start.StartOfYear(year), timeZone), LocalToUtc(start.StartOfYear(year + 1), timeZone));
Prompt for LLM

File Core/Resgrid.Services/Records/RecordsNumberingService.cs:

Line 58 to 62:

YearWindow constructs the next boundary from year + 1 without validating the requested year, so public records search controllers passing user-supplied int year values such as 9999 or 0 can trigger DateTime/numbering-year range exceptions and return server errors instead of controlled empty or invalid-request responses. Validate year before constructing the window, requiring 1 <= year < 9999 or rejecting the request at the controller/service boundary with a controlled validation result.

Suggested Code:

if (year < 1 || year >= DateTime.MaxValue.Year) throw new ArgumentOutOfRangeException(nameof(year));
return new NumberingYearWindow(year, LocalToUtc(start.StartOfYear(year), timeZone), LocalToUtc(start.StartOfYear(year + 1), timeZone));

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

text = System.Net.WebUtility.HtmlDecode(text);
return Whitespace.Replace(text, " ").Trim();
var text = StringHelpers.HtmlToPlainText(html);
return text.Length == 0 ? null : text;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Accessing text.Length before checking for null can throw a NullReferenceException when the helper returns null. Use string.IsNullOrEmpty or an equivalent null-safe check so Strip handles empty and null results safely.

Kody rule violation: Add null checks before accessing properties

return string.IsNullOrEmpty(text) ? null : text;
Prompt for LLM

File Core/Resgrid.Services/Search/SearchProjectionService.cs:

Line 1115:

Accessing text.Length before checking for null can throw a NullReferenceException when the helper returns null. Use string.IsNullOrEmpty or an equivalent null-safe check so Strip handles empty and null results safely.

Suggested Code:

return string.IsNullOrEmpty(text) ? null : text;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

{
return QueryAsync<int>(
$"SELECT DISTINCT {YearOf(Col("CreatedOn"))} AS Y FROM {Tbl("RmsIncidentReports")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("DeletedOn")} IS NULL ORDER BY Y DESC",
$"SELECT DISTINCT {YearOf($"COALESCE({Col("CallCreatedOn")}, {Col("CreatedOn")})")} AS Y FROM {Tbl("RmsIncidentReports")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("DeletedOn")} IS NULL ORDER BY Y DESC",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The QueryAsync call in Repositories/Resgrid.Repositories.DataRepository/RmsIncidentRepositories.cs:199 propagates database failures without department context. Wrap the query in try/catch and rethrow a DataException that identifies the department while preserving the original exception.

Kody rule violation: Add try-catch blocks for external calls

try
{
    return await QueryAsync<int>(
        $"SELECT DISTINCT {YearOf($"COALESCE({Col("CallCreatedOn")}, {Col("CreatedOn")})")} AS Y FROM {Tbl("RmsIncidentReports")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("DeletedOn")} IS NULL ORDER BY Y DESC",
        new { DepartmentId = departmentId });
}
catch (Exception ex)
{
    throw new DataException($"Failed to retrieve numbering years for department {departmentId}.", ex);
}
Prompt for LLM

File Repositories/Resgrid.Repositories.DataRepository/RmsIncidentRepositories.cs:

Line 193:

The QueryAsync call in Repositories/Resgrid.Repositories.DataRepository/RmsIncidentRepositories.cs:199 propagates database failures without department context. Wrap the query in try/catch and rethrow a DataException that identifies the department while preserving the original exception.

Suggested Code:

try
{
    return await QueryAsync<int>(
        $"SELECT DISTINCT {YearOf($"COALESCE({Col("CallCreatedOn")}, {Col("CreatedOn")})")} AS Y FROM {Tbl("RmsIncidentReports")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("DeletedOn")} IS NULL ORDER BY Y DESC",
        new { DepartmentId = departmentId });
}
catch (Exception ex)
{
    throw new DataException($"Failed to retrieve numbering years for department {departmentId}.", ex);
}

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

result.Should().Be("<p>Fire at Main</p>");
}

[TestCase("<p onclick=\"alert(1)\">a<script>alert(2)</script>b</p><img src=\"x\" onerror=\"alert(3)\"><iframe src=\"https://evil.example\">i</iframe>")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The test input contains plain markup, which violates the project rule requiring Next.js Image with explicit dimensions and meaningful alt text for app assets. Replace the plain image markup in the listed test and JavaScript locations with the approved image component or test fixture.

Kody rule violation: Use next/image with explicit dimensions and alt

Prompt for LLM

File Tests/Resgrid.Tests/Framework/StringHelperTests.cs:

Line 211:

The test input contains plain <img> markup, which violates the project rule requiring Next.js Image with explicit dimensions and meaningful alt text for app assets. Replace the plain image markup in the listed test and JavaScript locations with the approved image component or test fixture.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

}

[TestCase("<x-el autofocus onfocus=\"alert(1)\">hi</x-el><p>ok</p>")]
[TestCase("<unknown tabindex=1 onfocus=alert(1)>hi</unknown> ok")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The test input uses tabindex=1, and positive tabIndex values disrupt the natural keyboard navigation order. Use tabindex=0 or tabindex=-1 when testing focus behavior.

Kody rule violation: Use appropriate tabIndex values

Prompt for LLM

File Tests/Resgrid.Tests/Framework/StringHelperTests.cs:

Line 254:

The test input uses tabindex=1, and positive tabIndex values disrupt the natural keyboard navigation order. Use tabindex=0 or tabindex=-1 when testing focus behavior.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

DataConfig.DatabaseType = type;
_database = Prefix + Guid.NewGuid().ToString("N");
await using (var master = Connect(_master))
await master.ExecuteAsync("CREATE DATABASE " + _database);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules critical

The CREATE DATABASE statement concatenates unsanitized _database input, allowing SQL injection if the database name is attacker-controlled. Use a safe identifier-quoting mechanism or a validated allowlist before executing the query at Tests/Resgrid.Tests/Repositories/NumberFilterDatabaseTests.cs:136-137.

Kody rule violation: Prevent SQL Injection in Queries

Prompt for LLM

File Tests/Resgrid.Tests/Repositories/NumberFilterDatabaseTests.cs:

Line 65:

The CREATE DATABASE statement concatenates unsanitized _database input, allowing SQL injection if the database name is attacker-controlled. Use a safe identifier-quoting mechanism or a validated allowlist before executing the query at Tests/Resgrid.Tests/Repositories/NumberFilterDatabaseTests.cs:136-137.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

{
public Task<RmsInspection> GetByIdForDepartmentAsync(int departmentId, string inspectionId) => Task.FromResult(Rows.FirstOrDefault(i => i.DepartmentId == departmentId && i.RmsInspectionId == inspectionId));
private IEnumerable<RmsInspection> Filter(int departmentId, RmsInspectionQuery q) => Live(departmentId).Where(i => (q.OccupancyId == null || i.RmsOccupancyId == q.OccupancyId) && (q.ProgramId == null || i.RmsInspectionProgramId == q.ProgramId) && (q.States == null || q.States.Count == 0 || q.States.Contains(i.State)) && (q.ScheduledBefore == null || i.ScheduledOn <= q.ScheduledBefore));
private IEnumerable<RmsInspection> Filter(int departmentId, RmsInspectionQuery q) => Live(departmentId).Where(i => (q.OccupancyId == null || i.RmsOccupancyId == q.OccupancyId) && (q.ProgramId == null || i.RmsInspectionProgramId == q.ProgramId) && (q.States == null || q.States.Count == 0 || q.States.Contains(i.State)) && (q.ScheduledBefore == null || i.ScheduledOn <= q.ScheduledBefore) && (q.Number == null || IdentifierSearchKeys.ContainsParts(i.InspectionNumber, q.Number)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The Filter method in Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs:202 combines occupancy, program, state, scheduling, and number predicates in one LINQ expression, making individual filtering rules difficult to review and maintain. Split the expression into named intermediate predicates or query stages.

Kody rule violation: Limit Lengthy LINQ Chains

Prompt for LLM

File Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs:

Line 133:

The Filter method in Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs:202 combines occupancy, program, state, scheduling, and number predicates in one LINQ expression, making individual filtering rules difficult to review and maintain. Split the expression into named intermediate predicates or query stages.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


private static string WebRoot()
{
var directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The DirectoryInfo instance in Tests/Resgrid.Tests/Security/StoredHtmlRenderingTests.cs is not disposed deterministically. Dispose it with using or another explicit cleanup path.

Kody rule violation: Use using statements for disposable resources

using (DirectoryInfo directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory))
Prompt for LLM

File Tests/Resgrid.Tests/Security/StoredHtmlRenderingTests.cs:

Line 45:

The DirectoryInfo instance in Tests/Resgrid.Tests/Security/StoredHtmlRenderingTests.cs is not disposed deterministically. Dispose it with using or another explicit cleanup path.

Suggested Code:

using (DirectoryInfo directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory))

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

if (!await EnabledAsync()) return Failed<BidsResult>("contractor_billing_disabled", StatusCodes.Status403Forbidden);
var bids = string.IsNullOrWhiteSpace(contactId)
? await _bids.GetBidsForDepartmentAsync(DepartmentId, status.HasValue && Enum.IsDefined(typeof(BidStatuses), status.Value) ? (BidStatuses?)status.Value : null, skip, take)
? await _bids.GetBidsForDepartmentAsync(DepartmentId, status.HasValue && Enum.IsDefined(typeof(BidStatuses), status.Value) ? (BidStatuses?)status.Value : null, number, skip, take)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The awaited _bids.GetBidsForDepartmentAsync operation can propagate task failures without controller context, leaving promise or task rejections unhandled. Guard the operation with try/catch and handle or propagate failures appropriately, using ConfigureAwait(false) where the library context does not need to be resumed.

Kody rule violation: Handle async operations with proper error handling

? await _bids.GetBidsForDepartmentAsync(DepartmentId, status.HasValue && Enum.IsDefined(typeof(BidStatuses), status.Value) ? (BidStatuses?)status.Value : null, number, skip, take).ConfigureAwait(false)
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/BidsController.cs:

Line 83:

The awaited _bids.GetBidsForDepartmentAsync operation can propagate task failures without controller context, leaving promise or task rejections unhandled. Guard the operation with try/catch and handle or propagate failures appropriately, using ConfigureAwait(false) where the library context does not need to be resumed.

Suggested Code:

? await _bids.GetBidsForDepartmentAsync(DepartmentId, status.HasValue && Enum.IsDefined(typeof(BidStatuses), status.Value) ? (BidStatuses?)status.Value : null, number, skip, take).ConfigureAwait(false)

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


var states = state.HasValue ? new List<RmsDisclosureState> { (RmsDisclosureState)state.Value } : null;
var requests = await _disclosures.QueryAsync(DepartmentId, UserId, states, Math.Max(0, skip), Math.Max(1, Math.Min(RecordsController.MaxPageSize, take)));
var requests = await _disclosures.QueryAsync(DepartmentId, UserId, states, Math.Max(0, skip), Math.Max(1, Math.Min(RecordsController.MaxPageSize, take)), number);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The new number parameter reaches the database-backed QueryAsync call without length validation, allowing oversized input to consume resources or violate downstream constraints. Validate and sanitize number in Web/Resgrid.Web.Services/Controllers/v4/DisclosuresController.cs and return a client error when it exceeds MaxDisclosureNumberLength.

Kody rule violation: Order validations before database queries

if (number != null && number.Length > MaxDisclosureNumberLength)
    return BadRequest();

var requests = await _disclosures.QueryAsync(DepartmentId, UserId, states, Math.Max(0, skip), Math.Max(1, Math.Min(RecordsController.MaxPageSize, take)), number);
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/DisclosuresController.cs:

Line 58:

The new number parameter reaches the database-backed QueryAsync call without length validation, allowing oversized input to consume resources or violate downstream constraints. Validate and sanitize number in Web/Resgrid.Web.Services/Controllers/v4/DisclosuresController.cs and return a client error when it exceeds MaxDisclosureNumberLength.

Suggested Code:

if (number != null && number.Length > MaxDisclosureNumberLength)
    return BadRequest();

var requests = await _disclosures.QueryAsync(DepartmentId, UserId, states, Math.Max(0, skip), Math.Max(1, Math.Min(RecordsController.MaxPageSize, take)), number);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

<a id="newCallButton" title="@localizer["NewCallHelp"]" class="btn btn-success" href="@Url.Action("NewCall", "Dispatch", new { area = "User" })">@localizer["NewCall"]</a>
}
</div>
<div class="btn-group top-page-buttons buttons-actions" style="float:right;padding-right:15px;">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The inline style in Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml hard-codes layout styling in the view and bypasses scoped styling conventions. Move the styling to a scoped stylesheet or component-scoped class.

Kody rule violation: Use component-scoped styling

<div class="btn-group top-page-buttons buttons-actions">
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml:

Line 45:

The inline style in Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml hard-codes layout styling in the view and bypasses scoped styling conventions. Move the styling to a scoped stylesheet or component-scoped class.

Suggested Code:

<div class="btn-group top-page-buttons buttons-actions">

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment on lines +42 to +45
var PERSONNEL_ETA_COLUMN = 4;
var PERSONNEL_BOARD_COLUMNS = [1, 4, 5];
var UNIT_ETA_COLUMN = 3;
var UNIT_BOARD_COLUMNS = [1, 3, 4, 5, 6];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

PERSONNEL_ETA_COLUMN, PERSONNEL_BOARD_COLUMNS, UNIT_ETA_COLUMN, and UNIT_BOARD_COLUMNS are compile-time bindings that are never reassigned. Declare them with const in Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js.

Kody rule violation: Use `readonly` or `const` for Immutable Data

const PERSONNEL_ETA_COLUMN = 4;
const PERSONNEL_BOARD_COLUMNS = [1, 4, 5];
const UNIT_ETA_COLUMN = 3;
const UNIT_BOARD_COLUMNS = [1, 3, 4, 5, 6];
Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js:

Line 42 to 45:

PERSONNEL_ETA_COLUMN, PERSONNEL_BOARD_COLUMNS, UNIT_ETA_COLUMN, and UNIT_BOARD_COLUMNS are compile-time bindings that are never reassigned. Declare them with const in Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js.

Suggested Code:

const PERSONNEL_ETA_COLUMN = 4;
const PERSONNEL_BOARD_COLUMNS = [1, 4, 5];
const UNIT_ETA_COLUMN = 3;
const UNIT_BOARD_COLUMNS = [1, 3, 4, 5, 6];

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

}
var html = '';
if (row.TypeIconUrl) {
html += '<img src="' + escapeAttr(row.TypeIconUrl) + '" alt="" aria-hidden="true" class="rg-type-icon" style="width:20px;height:20px;object-fit:contain;vertical-align:middle;margin-right:4px;" />';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The generated image in Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js lacks lazy loading, asynchronous decoding, and intrinsic dimensions, which can delay rendering and contribute to layout shifts. Add loading="lazy", decoding="async", and explicit width="20" and height="20" attributes.

Kody rule violation: Serve responsive images with modern formats and lazy-load

html += '<img src="' + escapeAttr(row.TypeIconUrl) + '" alt="" aria-hidden="true" loading="lazy" decoding="async" width="20" height="20" class="rg-type-icon" style="object-fit:contain;vertical-align:middle;margin-right:4px;" />';
Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js:

Line 284:

The generated image in Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.nearestboard.js lacks lazy loading, asynchronous decoding, and intrinsic dimensions, which can delay rendering and contribute to layout shifts. Add loading="lazy", decoding="async", and explicit width="20" and height="20" attributes.

Suggested Code:

html += '<img src="' + escapeAttr(row.TypeIconUrl) + '" alt="" aria-hidden="true" loading="lazy" decoding="async" width="20" height="20" class="rg-type-icon" style="object-fit:contain;vertical-align:middle;margin-right:4px;" />';

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


if (r === 0) {
questionHtml = questionHtml + `<option selected="selected" value="${answer.Weight}">${answer.Answer}</option>`;
questionHtml = questionHtml + `<option selected="selected" value="${answer.Weight}">${resgrid.escapeHtml(answer.Answer)}</option>`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules critical

answer.Weight is interpolated directly into an HTML value attribute, so backend or user-controlled content can inject markup or malformed attribute data. Validate or escape answer.Weight before interpolation in Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js:569 and the listed occurrences.

Kody rule violation: Always sanitize user inputs

questionHtml = questionHtml + `<option selected="selected" value="${resgrid.escapeHtml(answer.Weight)}">${resgrid.escapeHtml(answer.Answer)}</option>`;
Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js:

Line 276:

answer.Weight is interpolated directly into an HTML value attribute, so backend or user-controlled content can inject markup or malformed attribute data. Validate or escape answer.Weight before interpolation in Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js:569 and the listed occurrences.

Suggested Code:

questionHtml = questionHtml + `<option selected="selected" value="${resgrid.escapeHtml(answer.Weight)}">${resgrid.escapeHtml(answer.Answer)}</option>`;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

var tr = '<tr>';
tr += '<td>' + value.Name + " on " + value.Computer.Name + '</td>';
tr += `<td><a class="btn btn-xs btn-primary" onclick='resgrid.groups.editgroup.selectPrinter("${value.Id}","${value.Name.replace(/\//g, "-").replace(/ /g, "_")}");' data-dismiss="modal">Select this Printer</a></td>`;
tr += '<td>' + resgrid.escapeHtml(value.Name) + " on " + resgrid.escapeHtml(value.Computer.Name) + '</td>';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The expression dereferences value.Name and value.Computer.Name without verifying value or Computer, causing NullReferenceException-style failures for incomplete results. Use null-safe access for both properties in Web/Resgrid.Web/wwwroot/js/app/internal/groups/resgrid.groups.editgroup.js and the listed occurrences.

Kody rule violation: Add null checks to prevent NullReferenceException

tr += '<td>' + resgrid.escapeHtml(value?.Name ?? '') + " on " + resgrid.escapeHtml(value?.Computer?.Name ?? '') + '</td>';
Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/groups/resgrid.groups.editgroup.js:

Line 81:

The expression dereferences value.Name and value.Computer.Name without verifying value or Computer, causing NullReferenceException-style failures for incomplete results. Use null-safe access for both properties in Web/Resgrid.Web/wwwroot/js/app/internal/groups/resgrid.groups.editgroup.js and the listed occurrences.

Suggested Code:

tr += '<td>' + resgrid.escapeHtml(value?.Name ?? '') + " on " + resgrid.escapeHtml(value?.Computer?.Name ?? '') + '</td>';

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

@Resgrid-Bot

Resgrid-Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ❌
Security ✅
Business Logic ❌

Access your configuration settings here.

​

Comment on lines +37 to +42
return seats
.Where(r => unitNames.ContainsKey(r.UnitId))
.GroupBy(r => r.UserId, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key,
g => String.Join(", ", g.Select(r => unitNames[r.UnitId]).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(n => n, StringComparer.OrdinalIgnoreCase)),
StringComparer.OrdinalIgnoreCase);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The multi-stage LINQ expression in PersonnelMapLabel.cs combines filtering, grouping, and projection in a single statement, making each transformation harder to understand and maintain. Assign the filtered and grouped results to named intermediate queries before building the dictionary.

Kody rule violation: Limit Lengthy LINQ Chains

var namedSeats = seats.Where(r => unitNames.ContainsKey(r.UnitId));
			var groupedSeats = namedSeats.GroupBy(r => r.UserId, StringComparer.OrdinalIgnoreCase);
			return groupedSeats.ToDictionary(g => g.Key,
				g => String.Join(", ", g.Select(r => unitNames[r.UnitId]).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(n => n, StringComparer.OrdinalIgnoreCase)),
				StringComparer.OrdinalIgnoreCase);
Prompt for LLM

File Core/Resgrid.Model/PersonnelMapLabel.cs:

Line 37 to 42:

The multi-stage LINQ expression in `PersonnelMapLabel.cs` combines filtering, grouping, and projection in a single statement, making each transformation harder to understand and maintain. Assign the filtered and grouped results to named intermediate queries before building the dictionary.

Suggested Code:

var namedSeats = seats.Where(r => unitNames.ContainsKey(r.UnitId));
			var groupedSeats = namedSeats.GroupBy(r => r.UserId, StringComparer.OrdinalIgnoreCase);
			return groupedSeats.ToDictionary(g => g.Key,
				g => String.Join(", ", g.Select(r => unitNames[r.UnitId]).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(n => n, StringComparer.OrdinalIgnoreCase)),
				StringComparer.OrdinalIgnoreCase);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Id = Guid.NewGuid(), Type = (int)EventingTypes.UnitStatusAlertUpdated, TimeStamp = DateTime.UtcNow, DepartmentId = departmentId, ItemId = unitId.ToString()
}.SerializeJson());

public Task<bool> UnitActiveRolesUpdated(int departmentId, int unitId) => SendMessage(Topics.EventingTopic, new EventingMessage

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The UnitActiveRolesUpdated method returns the SendMessage task directly, so RabbitMQ failures are not wrapped with operation and identifier context. Await SendMessage inside an async try/catch and rethrow an InvalidOperationException containing the department and unit identifiers.

Kody rule violation: Add try-catch blocks for external calls

public async Task<bool> UnitActiveRolesUpdated(int departmentId, int unitId)
{
	try
	{
		return await SendMessage(Topics.EventingTopic, new EventingMessage
		{
			Id = Guid.NewGuid(), Type = (int)EventingTypes.UnitActiveRolesUpdated, TimeStamp = DateTime.UtcNow, DepartmentId = departmentId, ItemId = unitId.ToString()
		}.SerializeJson());
	}
	catch (Exception exception)
	{
		throw new InvalidOperationException($"Failed to publish UnitActiveRolesUpdated for department {departmentId}, unit {unitId}.", exception);
	}
}
Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs:

Line 94:

The `UnitActiveRolesUpdated` method returns the `SendMessage` task directly, so RabbitMQ failures are not wrapped with operation and identifier context. Await `SendMessage` inside an async `try/catch` and rethrow an `InvalidOperationException` containing the department and unit identifiers.

Suggested Code:

		public async Task<bool> UnitActiveRolesUpdated(int departmentId, int unitId)
		{
			try
			{
				return await SendMessage(Topics.EventingTopic, new EventingMessage
				{
					Id = Guid.NewGuid(), Type = (int)EventingTypes.UnitActiveRolesUpdated, TimeStamp = DateTime.UtcNow, DepartmentId = departmentId, ItemId = unitId.ToString()
				}.SerializeJson());
			}
			catch (Exception exception)
			{
				throw new InvalidOperationException($"Failed to publish UnitActiveRolesUpdated for department {departmentId}, unit {unitId}.", exception);
			}
		}

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


private async Task<Dictionary<string, string>> PersonnelTitles()
{
var result = (GetMapDataResult)(await Build().GetMapDataAndMarkers()).Result.Should().BeOfType<OkObjectResult>().Which.Value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules critical

Blocking access to .Result in MappingControllerPersonnelLabelTests.cs and Tests/Resgrid.Tests/Web/Services/MappingControllerUnitMapTests.cs:130 can deadlock asynchronous execution and prevents proper task propagation. Replace the blocking access with await throughout the call chain.

Kody rule violation: Avoid Blocking Calls to Async Methods

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/MappingControllerPersonnelLabelTests.cs:

Line 118:

Blocking access to `.Result` in `MappingControllerPersonnelLabelTests.cs` and `Tests/Resgrid.Tests/Web/Services/MappingControllerUnitMapTests.cs:130` can deadlock asynchronous execution and prevents proper task propagation. Replace the blocking access with `await` throughout the call chain.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


private async Task<Dictionary<string, string>> PersonnelTitles()
{
var result = (GetMapDataResult)(await Build().GetMapDataAndMarkers()).Result.Should().BeOfType<OkObjectResult>().Which.Value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Blocking access to .Result in MappingControllerPersonnelLabelTests.cs and Tests/Resgrid.Tests/Web/Services/MappingControllerUnitMapTests.cs:130 violates the async execution rule and can cause deadlocks. Await the operation end-to-end instead of using .Result or .Wait().

Kody rule violation: Await async operations properly

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/MappingControllerPersonnelLabelTests.cs:

Line 118:

Blocking access to `.Result` in `MappingControllerPersonnelLabelTests.cs` and `Tests/Resgrid.Tests/Web/Services/MappingControllerUnitMapTests.cs:130` violates the async execution rule and can cause deadlocks. Await the operation end-to-end instead of using `.Result` or `.Wait()`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

_rabbitInboundEventProvider.RegisterForSessionEvents(SessionEventReceived);
_rabbitInboundEventProvider.RegisterForUnitStatusAlertEvents((departmentId, id) => _eventingHub.Clients.Group(departmentId.ToString()).SendAsync("unitStatusAlertUpdated", id));
// Carries only the unit id; clients reload the crew through the REST endpoints, which apply the viewer's visibility.
_rabbitInboundEventProvider.RegisterForUnitActiveRolesEvents((departmentId, id) => _eventingHub.Clients.Group(departmentId.ToString()).SendAsync("unitActiveRolesUpdated", id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The SendAsync task created in the listener callback is neither awaited nor caught, so publishing failures can become unhandled rejections. Make the callback async, await SendAsync inside try/catch, and log the department and unit identifiers with the failure.

Kody rule violation: Handle async operations with proper error handling

_rabbitInboundEventProvider.RegisterForUnitActiveRolesEvents(async (departmentId, id) =>
{
	try
	{
		await _eventingHub.Clients.Group(departmentId.ToString()).SendAsync(UnitActiveRolesUpdatedEvent, id);
	}
	catch (Exception ex)
	{
		_logger.LogError(ex, "Failed to publish unit active roles update for department {DepartmentId} and unit {UnitId}", departmentId, id);
	}
});
Prompt for LLM

File Web/Resgrid.Web.Eventing/Worker.cs:

Line 66:

The `SendAsync` task created in the listener callback is neither awaited nor caught, so publishing failures can become unhandled rejections. Make the callback async, await `SendAsync` inside `try/catch`, and log the department and unit identifiers with the failure.

Suggested Code:

			_rabbitInboundEventProvider.RegisterForUnitActiveRolesEvents(async (departmentId, id) =>
			{
				try
				{
					await _eventingHub.Clients.Group(departmentId.ToString()).SendAsync(UnitActiveRolesUpdatedEvent, id);
				}
				catch (Exception ex)
				{
					_logger.LogError(ex, "Failed to publish unit active roles update for department {DepartmentId} and unit {UnitId}", departmentId, id);
				}
			});

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

_rabbitInboundEventProvider.RegisterForSessionEvents(SessionEventReceived);
_rabbitInboundEventProvider.RegisterForUnitStatusAlertEvents((departmentId, id) => _eventingHub.Clients.Group(departmentId.ToString()).SendAsync("unitStatusAlertUpdated", id));
// Carries only the unit id; clients reload the crew through the REST endpoints, which apply the viewer's visibility.
_rabbitInboundEventProvider.RegisterForUnitActiveRolesEvents((departmentId, id) => _eventingHub.Clients.Group(departmentId.ToString()).SendAsync("unitActiveRolesUpdated", id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The listener registration in Web/Resgrid.Web.Eventing/Worker.cs lacks explicit callback error handling and a deterministic unsubscribe or cleanup path during worker teardown. Register the async callback with try/catch logging and retain the provider's registration handle for disposal when the worker stops.

Kody rule violation: Provide error handlers to subscription/listener APIs

_rabbitInboundEventProvider.RegisterForUnitActiveRolesEvents(async (departmentId, id) =>
{
	try
	{
		await _eventingHub.Clients.Group(departmentId.ToString()).SendAsync(UnitActiveRolesUpdatedEvent, id);
	}
	catch (Exception ex)
	{
		_logger.LogError(ex, "Failed to publish unit active roles update for department {DepartmentId} and unit {UnitId}", departmentId, id);
	}
});
// Retain and dispose the registration when the worker stops.
Prompt for LLM

File Web/Resgrid.Web.Eventing/Worker.cs:

Line 66:

The listener registration in `Web/Resgrid.Web.Eventing/Worker.cs` lacks explicit callback error handling and a deterministic unsubscribe or cleanup path during worker teardown. Register the async callback with `try/catch` logging and retain the provider's registration handle for disposal when the worker stops.

Suggested Code:

			_rabbitInboundEventProvider.RegisterForUnitActiveRolesEvents(async (departmentId, id) =>
			{
				try
				{
					await _eventingHub.Clients.Group(departmentId.ToString()).SendAsync(UnitActiveRolesUpdatedEvent, id);
				}
				catch (Exception ex)
				{
					_logger.LogError(ex, "Failed to publish unit active roles update for department {DepartmentId} and unit {UnitId}", departmentId, id);
				}
			});
			// Retain and dispose the registration when the worker stops.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment on lines +510 to +515
var coordinatesArr = coordinates.Split(char.Parse(","));
if (double.TryParse(coordinatesArr[0], out newLat) && double.TryParse(coordinatesArr[1], out newLon))
{
centerLat = newLat;
centerLon = newLon;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

Culture-sensitive parsing in the shared map-center helper can misinterpret invariant geocoder output such as 39.5,-119.8 on comma-decimal servers, producing an incorrect or invalid center instead of the fallback, while malformed nonempty output can throw IndexOutOfRangeException. Validate that the split result has two components, parse both values with CultureInfo.InvariantCulture, and accept them only within latitude/longitude bounds.

var coordinatesArr = coordinates.Split(',');
if (coordinatesArr.Length == 2 &&
    double.TryParse(coordinatesArr[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var newLat) &&
    double.TryParse(coordinatesArr[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var newLon) &&
    newLat >= -90 && newLat <= 90 && newLon >= -180 && newLon <= 180)
{
    centerLat = newLat;
    centerLon = newLon;
}
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs:

Line 510 to 515:

Culture-sensitive parsing in the shared map-center helper can misinterpret invariant geocoder output such as `39.5,-119.8` on comma-decimal servers, producing an incorrect or invalid center instead of the fallback, while malformed nonempty output can throw `IndexOutOfRangeException`. Validate that the split result has two components, parse both values with `CultureInfo.InvariantCulture`, and accept them only within latitude/longitude bounds.

Suggested Code:

var coordinatesArr = coordinates.Split(',');
if (coordinatesArr.Length == 2 &&
    double.TryParse(coordinatesArr[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var newLat) &&
    double.TryParse(coordinatesArr[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var newLon) &&
    newLat >= -90 && newLat <= 90 && newLon >= -180 && newLon <= 180)
{
    centerLat = newLat;
    centerLon = newLon;
}

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​


if (!String.IsNullOrWhiteSpace(gpsCoordinates))
{
string[] coordinates = gpsCoordinates.Split(char.Parse(","));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Using char.Parse(",") for gpsCoordinates.Split in Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs and at lines 490 and 510 performs unnecessary parsing on input. Use a direct delimiter or a TryParse-based approach and validate the coordinate format and culture.

Kody rule violation: Use TryParse for string conversions

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs:

Line 467:

Using `char.Parse(",")` for `gpsCoordinates.Split` in `Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs` and at lines 490 and 510 performs unnecessary parsing on input. Use a direct delimiter or a TryParse-based approach and validate the coordinate format and culture.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

return (info, AsUtc(state.Timestamp));
}
}
catch { }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The empty catch { } at Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs:615 silently swallows exceptions, obscuring failures and leaving the operation undefined. Log the exception with context and either rethrow it or handle it explicitly.

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs:

Line 574:

The empty `catch { }` at `Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs:615` silently swallows exceptions, obscuring failures and leaving the operation undefined. Log the exception with context and either rethrow it or handle it explicitly.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

RoleName = String.Join(", ", member.Select(x => x.Role.Name))
};

if (await _authorizationService.CanUserViewPersonLocationViaMatrixAsync(person.UserId, UserId, DepartmentId))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Calling CanUserViewPersonLocationViaMatrixAsync inside the crew loop issues one authorization or network request per person, increasing latency and load. Batch the checks or use a bulk authorization API before iterating over the crew.

Kody rule violation: Detect N+1 style queries and suggest batching

var personIds = ...;
var visibilityByPerson = await Task.WhenAll(personIds.Select(async personId => ...));
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs:

Line 432:

Calling `CanUserViewPersonLocationViaMatrixAsync` inside the crew loop issues one authorization or network request per person, increasing latency and load. Batch the checks or use a bulk authorization API before iterating over the crew.

Suggested Code:

var personIds = ...;
var visibilityByPerson = await Task.WhenAll(personIds.Select(async personId => ...));

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

<div className="rg-map__toolbar">
<div className="rg-map__toolbar-section">
<label className="rg-map__checkbox">
<input type="checkbox" checked={keepInView} onChange={(event) => setKeepInView(event.target.checked)} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The inline onChange arrow function in UnitMapElement.tsx:415 creates a new function on every render, increasing component re-render overhead. Move the handler outside the JSX props.

Kody rule violation: Avoid using .bind() or arrow functions in JSX props

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Apps/src/components/unitMap/UnitMapElement.tsx:

Line 410:

The inline `onChange` arrow function in `UnitMapElement.tsx:415` creates a new function on every render, increasing component re-render overhead. Move the handler outside the JSX props.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

connection = established;
onConnected(attempt);
} catch (connectionError) {
console.error(`Unable to connect to realtime ${label}.`, connectionError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The realtime connection error log interpolates the connection label and passes the raw error positionally, preventing consistent structured querying. Log the operation name, connection label, and error as structured fields.

Kody rule violation: Include error context in structured logs

logger.error('Realtime connection failed', { operation: 'connect', label, error: connectionError });
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Apps/src/components/unitMap/UnitMapElement.tsx:

Line 119:

The realtime connection error log interpolates the connection label and passes the raw error positionally, preventing consistent structured querying. Log the operation name, connection label, and error as structured fields.

Suggested Code:

logger.error('Realtime connection failed', { operation: 'connect', label, error: connectionError });

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

@Html.Raw("<td>")

@Html.Raw($"<a type='button' href='{@Url.Action("ViewEvents", "Units", new { area = "User" })}?unitId={u.UnitId}' class='btn btn-xs btn-primary'>{Html.Encode(localizer["ViewEvents"].Value)}</a>&nbsp;<a type='button' href='{@Url.Action("ViewLogs", "Units", new { area = "User" })}?unitId={u.UnitId}' class='btn btn-xs btn-info'>{Html.Encode(localizer["Logs"].Value)}</a>&nbsp;")
@Html.Raw($"<a type='button' href='{@Url.Action("ViewEvents", "Units", new { area = "User" })}?unitId={u.UnitId}' class='btn btn-xs btn-primary'>{Html.Encode(localizer["ViewEvents"].Value)}</a>&nbsp;<a type='button' href='{@Url.Action("ViewLogs", "Units", new { area = "User" })}?unitId={u.UnitId}' class='btn btn-xs btn-info'>{Html.Encode(localizer["Logs"].Value)}</a>&nbsp;<a type='button' href='{@Url.Action("ViewMap", "Units", new { area = "User" })}?unitId={u.UnitId}' class='btn btn-xs btn-success'>{Html.Encode(localizer["UnitMap"].Value)}</a>&nbsp;")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules critical

Embedding u.UnitId in an Html.Raw-generated href bypasses Razor's normal HTML encoding and can produce an unsafe URL. Pass unitId through Url.Action route values and render the anchor normally.

Kody rule violation: Always sanitize user inputs

<a type="button" href="@Url.Action("ViewMap", "Units", new { area = "User", unitId = u.UnitId })" class="btn btn-xs btn-success">@Html.Encode(localizer["UnitMap"].Value)</a>&nbsp;
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Units/Index.cshtml:

Line 456:

Embedding `u.UnitId` in an `Html.Raw`-generated href bypasses Razor's normal HTML encoding and can produce an unsafe URL. Pass `unitId` through `Url.Action` route values and render the anchor normally.

Suggested Code:

<a type="button" href="@Url.Action("ViewMap", "Units", new { area = "User", unitId = u.UnitId })" class="btn btn-xs btn-success">@Html.Encode(localizer["UnitMap"].Value)</a>&nbsp;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

<div class="wrapper wrapper-content">
<div class="ibox float-e-margins">
<div class="ibox-content">
<rg-unit-map unitid="@Model.Unit.UnitId" departmentid="@ClaimsAuthorizationHelper.GetDepartmentId()" mapheight="650px" strings="@System.Text.Json.JsonSerializer.Serialize(unitMapStrings)" leafletosmurl="@mapConfig.TileUrl" mapattribution="@mapConfig.Attribution" mapprovider="@mapConfig.MapProvider" mapstyleurl="@mapConfig.StyleUrl" mapaccesstoken="@mapConfig.AccessToken"></rg-unit-map>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Dereferencing Unit and mapConfig during ViewMap.cshtml rendering can throw NullReferenceException when either value is missing. Guard both objects and provide safe defaults for map properties.

Kody rule violation: Add null checks to prevent NullReferenceException

<rg-unit-map unitid="@(Model.Unit?.UnitId)" departmentid="@ClaimsAuthorizationHelper.GetDepartmentId()" mapheight="650px" strings="@System.Text.Json.JsonSerializer.Serialize(unitMapStrings)" leafletosmurl="@(mapConfig?.TileUrl ?? string.Empty)" mapattribution="@(mapConfig?.Attribution ?? string.Empty)" mapprovider="@(mapConfig?.MapProvider ?? string.Empty)" mapstyleurl="@(mapConfig?.StyleUrl ?? string.Empty)" mapaccesstoken="@(mapConfig?.AccessToken ?? string.Empty)"></rg-unit-map>
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Units/ViewMap.cshtml:

Line 48:

Dereferencing `Unit` and `mapConfig` during `ViewMap.cshtml` rendering can throw `NullReferenceException` when either value is missing. Guard both objects and provide safe defaults for map properties.

Suggested Code:

<rg-unit-map unitid="@(Model.Unit?.UnitId)" departmentid="@ClaimsAuthorizationHelper.GetDepartmentId()" mapheight="650px" strings="@System.Text.Json.JsonSerializer.Serialize(unitMapStrings)" leafletosmurl="@(mapConfig?.TileUrl ?? string.Empty)" mapattribution="@(mapConfig?.Attribution ?? string.Empty)" mapprovider="@(mapConfig?.MapProvider ?? string.Empty)" mapstyleurl="@(mapConfig?.StyleUrl ?? string.Empty)" mapaccesstoken="@(mapConfig?.AccessToken ?? string.Empty)"></rg-unit-map>

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

<div class="wrapper wrapper-content">
<div class="ibox float-e-margins">
<div class="ibox-content">
<rg-unit-map unitid="@Model.Unit.UnitId" departmentid="@ClaimsAuthorizationHelper.GetDepartmentId()" mapheight="650px" strings="@System.Text.Json.JsonSerializer.Serialize(unitMapStrings)" leafletosmurl="@mapConfig.TileUrl" mapattribution="@mapConfig.Attribution" mapprovider="@mapConfig.MapProvider" mapstyleurl="@mapConfig.StyleUrl" mapaccesstoken="@mapConfig.AccessToken"></rg-unit-map>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Direct property access on Unit and mapConfig in ViewMap.cshtml can throw NullReferenceException when either object or its map settings are missing. Use null-safe access and provide safe defaults for all map properties.

Kody rule violation: Add null checks before accessing properties

<rg-unit-map unitid="@(Model.Unit?.UnitId)" departmentid="@ClaimsAuthorizationHelper.GetDepartmentId()" mapheight="650px" strings="@System.Text.Json.JsonSerializer.Serialize(unitMapStrings)" leafletosmurl="@(mapConfig?.TileUrl ?? string.Empty)" mapattribution="@(mapConfig?.Attribution ?? string.Empty)" mapprovider="@(mapConfig?.MapProvider ?? string.Empty)" mapstyleurl="@(mapConfig?.StyleUrl ?? string.Empty)" mapaccesstoken="@(mapConfig?.AccessToken ?? string.Empty)"></rg-unit-map>
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Units/ViewMap.cshtml:

Line 48:

Direct property access on `Unit` and `mapConfig` in `ViewMap.cshtml` can throw `NullReferenceException` when either object or its map settings are missing. Use null-safe access and provide safe defaults for all map properties.

Suggested Code:

<rg-unit-map unitid="@(Model.Unit?.UnitId)" departmentid="@ClaimsAuthorizationHelper.GetDepartmentId()" mapheight="650px" strings="@System.Text.Json.JsonSerializer.Serialize(unitMapStrings)" leafletosmurl="@(mapConfig?.TileUrl ?? string.Empty)" mapattribution="@(mapConfig?.Attribution ?? string.Empty)" mapprovider="@(mapConfig?.MapProvider ?? string.Empty)" mapstyleurl="@(mapConfig?.StyleUrl ?? string.Empty)" mapaccesstoken="@(mapConfig?.AccessToken ?? string.Empty)"></rg-unit-map>

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

@ucswift

ucswift commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift
ucswift merged commit 7280649 into master Oct 24, 2026
16 of 18 checks passed
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.

3 participants