Skip to content

Implement Groups Functionality#67

Open
kflemin wants to merge 12 commits into
mainfrom
groups
Open

Implement Groups Functionality#67
kflemin wants to merge 12 commits into
mainfrom
groups

Conversation

@kflemin
Copy link
Copy Markdown
Contributor

@kflemin kflemin commented May 14, 2026

Adds full inventory group detail functionality, porting the AngularJS inventory groups feature to
Angular v21. Users can navigate into a group to view and manage its dashboard, properties, systems &
services, meters, and map.

Group Detail Layout

  • Sidebar navigation with tabs: Dashboard, Properties, Systems & Services, Meters, Map
  • White background with consistent padding across all detail pages

Systems & Services

  • Table-based layout with type-specific columns (DES Heating/Cooling, EVSE, Battery)
  • Full CRUD for systems and services via Material dialogs
  • Placeholder values matching old UI for all system type fields
  • Inline services count badge and "Add Service" button per system row
  • Expandable/collapsible service rows under each system
  • Service detail page with connected properties table and "Back to Systems" navigation

Group Meters

  • AG Grid with all columns matching old UI (ID, Type, Alias, Source, Connection Type, Property,
    System, Connection, Virtual, Scenario)
  • Clickable Property links → property detail meters page
  • Clickable Connection links → group systems page
  • Meter readings load automatically on page init with interval selector (Exact/Month/Year)
  • Edit Meter dialog with cascading fields: Alias, Flow Direction, Connection, Meter Usage, System,
    Service (scoped to current group)
  • Delete Meter confirmation dialog
  • Upload Meter Readings dialog with 3-step flow (file select → processing → confirmation)

Properties & Dashboard

  • Properties grid with display name from org settings
  • Dashboard with group summary data

API & Types

  • Extended GroupsService with get, fetchGroups, updateMeter, deleteMeter, uploadMeterReadings,
    getServiceDetail methods
  • Added GroupMeterConfig, extended GroupMeter with config, view_id, system_name, service_name,
    service_group, scenario_name, is_virtual

Bug Fixes

  • System update 500: Always send type field (not just on create) so backend can resolve model class
  • Service creation 400: Include system_id in service dialog payload
  • DES systems not rendering: Use startsWith() matching for type keys like "DES - Heating"
  • Meter delete dialog closing on error: Replace finalize with explicit success/error handlers
  • Meter edit 400: Preserve service_id for non-outside connections; simplified to current-group scope
  • Meter readings not loading on page init: Call loadReadings() after meters load
  • Navigation: Use absolute routes for reliable cross-component navigation
  • Backend: Guard _usages_by_year against meters with no readings (DoesNotExist crash)

Copy link
Copy Markdown
Contributor

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

Ports the AngularJS inventory groups feature into Angular v21, adding a full Group Detail experience (Dashboard, Properties, Systems & Services, Meters, Map) with associated dialogs, an extended GroupsService/types, and assorted fixes to the existing groups list.

Changes:

  • New nested route + layout (group-detail-layout) with sidebar nav, plus child pages for dashboard, properties, systems/services (incl. service detail), meters, and map.
  • Extended GroupsService with get/getById/getDashboard/getSankeyData/getProperties/getMeters/getMeterUsage, full system/service CRUD, meter update/delete, and uploadMeterReadings; new types in groups.types.ts.
  • Updates to existing groups list (cell renderer/click handling, refresh on dialog close) and form-modal (explicit next/error close handling).

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/app/modules/inventory/inventory.routes.ts Registers nested groups/:groupId routes for the new layout and child pages
src/app/modules/inventory-list/groups/modal/form-modal.component.ts Switches create/edit to explicit next/error handlers
src/app/modules/inventory-list/groups/groups.component.ts New name-cell renderer, navigation handling, refresh-on-modal-close changes
src/app/modules/inventory-list/groups/detail/group-detail-layout.{ts,html} New layout with sidebar navigation menu
src/app/modules/inventory-list/groups/detail/dashboard/dashboard.{ts,html} Group dashboard with cycle/meter-type selectors and sankey list
src/app/modules/inventory-list/groups/detail/properties/properties.{ts,html} AG Grid of group properties
src/app/modules/inventory-list/groups/detail/systems/systems.{ts,html} Systems & Services table with type-specific columns and CRUD wiring
src/app/modules/inventory-list/groups/detail/systems/system-dialog/* Create/edit/delete system dialog
src/app/modules/inventory-list/groups/detail/systems/service-dialog/* Create/edit/delete service dialog
src/app/modules/inventory-list/groups/detail/systems/service-detail/* Service detail page with connected-properties table
src/app/modules/inventory-list/groups/detail/systems/dialog-types.ts Shared dialog data types
src/app/modules/inventory-list/groups/detail/meters/meters.{ts,html} Meters AG Grid + readings panel + cell-action handling
src/app/modules/inventory-list/groups/detail/meters/dialogs/edit-meter-dialog.* Cascading meter-edit dialog
src/app/modules/inventory-list/groups/detail/meters/dialogs/delete-meter-dialog.* Meter delete confirmation dialog
src/app/modules/inventory-list/groups/detail/meters/dialogs/upload-readings-dialog.* 3-step meter readings upload dialog
src/app/modules/inventory-list/groups/detail/map/map.{ts,html} Placeholder map page
src/@seed/api/groups/groups.service.ts Extends API with group-detail/system/service/meter operations
src/@seed/api/groups/groups.types.ts New types for systems/services, dashboard, sankey, properties, meters
.spelling.dic Adds sankey, evse, EVSE to dictionary
Comments suppressed due to low confidence (1)

src/@seed/api/groups/groups.service.ts:167

  • get() (here) and getById() (added at line 159) are two methods that hit the exact same endpoint (/api/v3/inventory_groups/${id}/?organization_id=${orgId}) but interpret the response differently: get() types it as InventoryGroup directly, while getById() types it as InventoryGroupResponse and unwraps .data. Only one of these can match the actual backend response, so callers of one of them will receive the wrong shape (e.g. an object with {status, data} keys instead of an InventoryGroup, or undefined). The edit-meter dialog uses get() to read group.systems, while group-detail-layout uses getById() to read group.name — at least one of these will be broken at runtime. Please collapse to a single method and confirm whether the endpoint returns the wrapped {status, data} shape or the bare object.
  get(orgId: number, id: number): Observable<InventoryGroup> {
    const url = `/api/v3/inventory_groups/${id}/?organization_id=${orgId}`
    return this._httpClient.get<InventoryGroup>(url).pipe(
      catchError((error: HttpErrorResponse) => {
        return this._errorService.handleError(error, 'Error fetching group')
      }),
    )
  }

  update(orgId: number, id: number, data: InventoryGroup): Observable<InventoryGroup> {
    const url = `/api/v3/inventory_groups/${id}/?organization_id=${orgId}`
    return this._httpClient.put<InventoryGroup>(url, data).pipe(
      map((group) => {
        this._snackBar.success('Group updated successfully')
        this.list(orgId)
        return group
      }),
      catchError((error: HttpErrorResponse) => {
        return this._errorService.handleError(error, 'Error updating group')
      }),
    )
  }

  delete(orgId: number, id: number): Observable<unknown> {
    const url = `/api/v3/inventory_groups/${id}/?organization_id=${orgId}`
    return this._httpClient.delete(url).pipe(
      tap(() => {
        this._snackBar.success('Group deleted successfully')
        this.list(orgId)
      }),
      catchError((error: HttpErrorResponse) => {
        return this._errorService.handleError(error, 'Error deleting group')
      }),
    )
  }

  bulkUpdate(
    orgId: number,
    addGroupIds: number[],
    removeGroupIds: number[],
    viewIds: number[],
    type: 'property' | 'tax_lot',
  ): Observable<unknown> {
    const url = `/api/v3/inventory_group_mappings/put/?organization_id=${orgId}`
    const data = {
      inventory_ids: viewIds,
      add_group_ids: addGroupIds,
      remove_group_ids: removeGroupIds,
      inventory_type: type,
    }
    return this._httpClient.put(url, data).pipe(
      tap(() => {
        this.list(orgId)
      }),
      catchError((error: HttpErrorResponse) => {
        return this._errorService.handleError(error, 'Error updating groups')
      }),
    )
  }

  getById(orgId: number, groupId: number): Observable<InventoryGroup> {
    const url = `/api/v3/inventory_groups/${groupId}/?organization_id=${orgId}`
    return this._httpClient.get<InventoryGroupResponse>(url).pipe(
      map(({ data }) => data),
      catchError((error: HttpErrorResponse) => {
        return this._errorService.handleError(error, 'Error fetching group')
      }),
    )
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 38 to 115
list(orgId: number) {
const url = `/api/v3/inventory_groups/?organization_id=${orgId}`
this._httpClient
.get<InventoryGroupsResponse>(url)
.get<InventoryGroup[]>(url)
.pipe(
take(1),
map(({ data }) => {
this._groups.next(data)
return data
map((data) => {
const groups = Array.isArray(data) ? data : []
this._groups.next(groups)
return groups
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching groups')
}),
)
.subscribe()
}

// inventoryIds (Property/TaxLot[]) are not viewIds
listForInventory(orgId: number, inventoryIds: number[], type: InventoryType) {
const url = `/api/v3/inventory_groups/filter/?organization_id=${orgId}&inventory_type=${type}`
const body = { selected: inventoryIds }
this._httpClient
.post<InventoryGroupsResponse>(url, body)
.pipe(
take(1),
map(({ data }) => {
this._groups.next(data)
return data
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching groups for inventory')
}),
)
.subscribe()
}

fetchGroups(orgId: number): Observable<InventoryGroup[]> {
const url = `/api/v3/inventory_groups/?organization_id=${orgId}`
return this._httpClient.get<InventoryGroup[]>(url).pipe(
map((data) => (Array.isArray(data) ? data : [])),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching groups')
}),
)
}

create(orgId: number, data: InventoryGroup): Observable<InventoryGroup> {
const url = `/api/v3/inventory_groups/?organization_id=${orgId}`
return this._httpClient.post<InventoryGroupResponse>(url, data).pipe(
map(({ data }) => {
return this._httpClient.post<InventoryGroup>(url, data).pipe(
map((group) => {
this._snackBar.success('Group created successfully')
this.list(orgId)
return data
return group
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error updating group')
}),
)
}

get(orgId: number, id: number): Observable<InventoryGroup> {
const url = `/api/v3/inventory_groups/${id}/?organization_id=${orgId}`
return this._httpClient.get<InventoryGroup>(url).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching group')
}),
)
}

update(orgId: number, id: number, data: InventoryGroup): Observable<InventoryGroup> {
const url = `/api/v3/inventory_groups/${id}/?organization_id=${orgId}`
return this._httpClient.put<InventoryGroupResponse>(url, data).pipe(
map(({ data }) => {
return this._httpClient.put<InventoryGroup>(url, data).pipe(
map((group) => {
this._snackBar.success('Group updated successfully')
this.list(orgId)
return data
return group
}),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 6d5ba86. The groups service now consistently unwraps the backend { status, data } envelope for list/fetch/create/get/update, and I also corrected the create-path error message.

Comment on lines +87 to +108
obs
.pipe(
finalize(() => {
this._dialogRef.close(true)
}),
)
.subscribe()
}

deleteSystem() {
if (this.submitted) return
this.submitted = true
const systemId = this._data.system?.id
this._groupsService
.deleteSystem(this._data.orgId, this._data.groupId, systemId)
.pipe(
finalize(() => {
this._dialogRef.close(true)
}),
)
.subscribe()
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 0b2c26f. The system dialog now uses explicit next/error handlers instead of finalize(), ensuring the dialog only closes on success and users see error feedback on failures.

Comment on lines +51 to +71
obs
.pipe(
finalize(() => {
this._dialogRef.close(true)
}),
)
.subscribe()
}

deleteService() {
if (this.submitted) return
this.submitted = true
const serviceId = this._data.service?.id
this._groupsService
.deleteService(this._data.orgId, this._data.groupId, this._data.systemId, serviceId)
.pipe(
finalize(() => {
this._dialogRef.close(true)
}),
)
.subscribe()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 0b2c26f. The service dialog now uses explicit next/error handlers instead of finalize(), ensuring the dialog only closes on success and users see error feedback on failures.

Comment on lines +33 to +47
this._organizationService.currentOrganization$
.pipe(
takeUntil(this._unsubscribeAll$),
take(1),
tap(({ org_id }) => {
this.orgId = org_id
}),
switchMap(() => this._organizationService.getById(this.orgId)),
tap((org) => {
this.cycles = org.cycles
this.cycleId = org.cycles[0]?.cycle_id
}),
switchMap(() => this.loadDashboard()),
)
.subscribe()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in a745a56. Added filter guard filter((org) => org?.org_id != null) to prevent destructuring errors when currentOrganization$ emits null/undefined on initial load.

Comment thread src/app/modules/inventory-list/groups/detail/meters/meters.component.ts Outdated
Comment on lines +26 to +47
// eslint-disable-next-line @typescript-eslint/no-explicit-any
service: any = null
loading = true

ngOnInit() {
this.systemId = parseInt(this._route.snapshot.paramMap.get('systemId'))
this.serviceId = parseInt(this._route.snapshot.paramMap.get('serviceId'))

// Walk up to find groupId from parent routes
let route = this._route.parent
while (route) {
const gid = route.snapshot.paramMap.get('groupId')
if (gid) {
this.groupId = parseInt(gid)
break
}
route = route.parent
}

// Get inventory type from URL
const urlParts = this._router.url.split('/')
this.inventoryType = urlParts.find((p) => p === 'properties' || p === 'taxlots') ?? 'properties'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 4c2f87a. Service detail now uses pathFromRoot to reliably extract inventoryType from route params instead of fragile URL parsing. Added GroupServiceDetail type to replace the any type for the service field, improving type safety.

Comment thread src/app/modules/inventory-list/groups/detail/properties/properties.component.ts Outdated
Comment on lines +71 to +85
changeCycle(cycleId: number) {
this.cycleId = cycleId
this.loadDashboard()
.pipe(switchMap(() => this.loadSankey()))
.subscribe()
}

loadSankey() {
if (!this.meterType) return this._groupsService.getSankeyData(this.orgId, this.groupId, this.cycleId, '')
return this._groupsService.getSankeyData(this.orgId, this.groupId, this.cycleId, this.meterType).pipe(
tap((data) => {
this.sankeyData = data
}),
)
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in cb28df8. The loadSankey() method now resets sankeyData to an empty array and returns of([]) when meterType is empty, preventing stale data from lingering in the UI.

Comment thread src/app/modules/inventory-list/groups/groups.component.ts Outdated
Comment thread .spelling.dic Outdated
kflemin and others added 3 commits May 15, 2026 10:05
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 15, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4686 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-73963542 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5845834581417653269,16327382481619564483,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,9046949187290639812,7038218228252314612,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --user-data-dir=/tmp/karma-73963542 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications http://localhost:9876/?id=73963542 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
  • clients2.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4686 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-73963542 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5845834581417653269,16327382481619564483,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,9046949187290639812,7038218228252314612,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --user-data-dir=/tmp/karma-73963542 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications http://localhost:9876/?id=73963542 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
  • clientservices.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4686 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-73963542 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5845834581417653269,16327382481619564483,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,9046949187290639812,7038218228252314612,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --user-data-dir=/tmp/karma-73963542 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications http://localhost:9876/?id=73963542 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
  • redirector.gvt1.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4686 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-73963542 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5845834581417653269,16327382481619564483,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,9046949187290639812,7038218228252314612,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --user-data-dir=/tmp/karma-73963542 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications http://localhost:9876/?id=73963542 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
  • safebrowsingohttpgateway.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4686 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-73963542 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5845834581417653269,16327382481619564483,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,9046949187290639812,7038218228252314612,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --user-data-dir=/tmp/karma-73963542 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications http://localhost:9876/?id=73963542 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4686 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-73963542 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5845834581417653269,16327382481619564483,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,9046949187290639812,7038218228252314612,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --user-data-dir=/tmp/karma-73963542 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications http://localhost:9876/?id=73963542 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI and others added 5 commits May 15, 2026 16:16
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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