Skip to content

Native Swift 6 SwiftUI PowerSync iOS app (HIG / Liquid Glass) - #40

Closed
Artic0din wants to merge 6 commits into
mainfrom
cursor/native-swiftui-app-9f0a
Closed

Native Swift 6 SwiftUI PowerSync iOS app (HIG / Liquid Glass)#40
Artic0din wants to merge 6 commits into
mainfrom
cursor/native-swiftui-app-9f0a

Conversation

@Artic0din

Copy link
Copy Markdown
Collaborator

Summary

Adds a native Swift 6 / SwiftUI PowerSync mobile front end under ios/, built only with Apple frameworks (no third-party UI or networking libraries) and aligned with Apple HIG + the iOS 26/27 Liquid Glass design language.

What’s included

  • App target (cc.powersync.mobile) with Swift 6 strict concurrency
  • Widget + Live Activity extension (WidgetKit / ActivityKit)
  • Screens: Onboarding, Dashboard, Controls, Automations, Settings, Smart Optimization, Prices & Tariff, EV Charging, Battery Health
  • Demo Mode (works offline) and Home Assistant connection via native URLSession + Keychain
  • System UI only: TabView, NavigationStack, List/Form, Gauge, Picker, Toggle, SF Symbols, Swift Charts, .glassEffect / GlassEffectContainer, tabBarMinimizeBehavior

How to run

Open ios/PowerSync.xcodeproj in Xcode 26+ on a Mac, select the PowerSync scheme, set a Development Team (App Group group.cc.powersync.mobile), and run. Details in ios/README.md.

This Cloud Agent host is Linux and cannot compile or launch the iOS Simulator — please build on macOS.

Design mapping

Grounded in the existing HA integration surface (services.yaml, optimizer modes, tariff windows, EV modes, TEDAPI battery health) and the mockup set from the prior design pass.

Open in Web Open in Cursor 

Native iOS 26+ front end using only Apple frameworks (SwiftUI, Charts,
WidgetKit, ActivityKit). HIG-aligned TabView/List/Form screens for
dashboard, controls, automations, settings, optimization, prices, EV,
and battery health, plus Demo Mode and Home Assistant URLSession client.

Co-authored-by: Artic0din <Artic0din@users.noreply.github.com>
@cursor

cursor Bot commented Jul 18, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_74c4e884-8d15-4578-9b4e-77e0b8d03c1c)

@Artic0din
Artic0din marked this pull request as ready for review July 18, 2026 22:45
Copilot AI review requested due to automatic review settings July 18, 2026 22:45
@cursor

cursor Bot commented Jul 18, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9820839c-6d16-4bfb-be96-72e303653877)

Copilot AI 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.

Pull request overview

This PR introduces a new ios/ subtree containing a native Swift 6 / SwiftUI PowerSync companion app, including a WidgetKit/ActivityKit extension, and the Xcode project scaffolding needed to build it on macOS with Xcode 26+.

Changes:

  • Added a SwiftUI iOS app shell with demo-mode data, Home Assistant REST connectivity, and primary screens (Dashboard/Controls/Optimization/Prices/EV/Settings/Onboarding).
  • Added a WidgetKit bundle plus a Live Activity implementation that reads shared app-group snapshots.
  • Added an Xcode project generator script and checked in PowerSync.xcodeproj, plus Xcode-related .gitignore entries.

Reviewed changes

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

Show a summary per file
File Description
ios/scripts/generate_xcodeproj.py Generates PowerSync.xcodeproj/project.pbxproj for the app + widgets targets.
ios/README.md Build/run instructions for the iOS app and project regeneration steps.
ios/PowerSyncWidgets/StatusLockScreenWidget.swift Lock screen/status widget using shared snapshot data.
ios/PowerSyncWidgets/SharedSnapshot.swift Widget-side snapshot model + app-group read helper.
ios/PowerSyncWidgets/PowerSyncWidgetsBundle.swift Widget bundle entry point registering widgets + live activity.
ios/PowerSyncWidgets/PowerSyncWidgets.entitlements App Group entitlement for sharing snapshots.
ios/PowerSyncWidgets/OptimizationLiveActivity.swift Live Activity UI for optimization status/savings.
ios/PowerSyncWidgets/Info.plist Widget extension Info.plist (WidgetKit extension point + Live Activities flag).
ios/PowerSyncWidgets/ImportPriceWidget.swift Widget showing import price and solar generation.
ios/PowerSyncWidgets/BatteryGaugeWidget.swift Widget showing battery SOC gauge.
ios/PowerSync/Views/Settings/SettingsView.swift Settings landing page (battery system, services, connection, about).
ios/PowerSync/Views/Settings/ConnectionSettingsView.swift HA URL/token entry UI and connect action.
ios/PowerSync/Views/Settings/BatteryHealthView.swift Battery health UI including charts and rescan action.
ios/PowerSync/Views/RootView.swift Root routing between onboarding and main tabs.
ios/PowerSync/Views/Prices/PricesView.swift Prices & tariff UI including forecast chart and TOU schedule.
ios/PowerSync/Views/Optimization/OptimizationView.swift Optimization UI (mode picker, status/summary cards, plan chart).
ios/PowerSync/Views/Onboarding/OnboardingView.swift Onboarding screen with demo mode and connect entry points.
ios/PowerSync/Views/MainTabView.swift TabView-based primary navigation across app sections.
ios/PowerSync/Views/EV/EVChargingView.swift EV charging UI and mode toggles.
ios/PowerSync/Views/Dashboard/OptimizationGlanceCard.swift Dashboard card linking to optimization details.
ios/PowerSync/Views/Dashboard/EnergyFlowSection.swift Dashboard “live energy flow” grid UI.
ios/PowerSync/Views/Dashboard/DashboardView.swift Dashboard composition and refresh actions.
ios/PowerSync/Views/Controls/ControlsView.swift Controls UI for force modes, reserve slider, grid settings.
ios/PowerSync/Views/Automations/AutomationsView.swift Automations list with enable/disable toggles.
ios/PowerSync/Services/SharedSnapshotWriter.swift App-side snapshot encoding to app group + widget/live activity refresh triggers.
ios/PowerSync/Services/LiveActivityManager.swift Starts/updates ActivityKit live activities from app state.
ios/PowerSync/Services/KeychainStore.swift Keychain persistence for HA URL/token.
ios/PowerSync/Services/HomeAssistantClient.swift URLSession-based Home Assistant REST client actor.
ios/PowerSync/Services/Formatters.swift Shared formatting helpers for currency/units/dates.
ios/PowerSync/Services/DemoDataFactory.swift Demo-mode data generation for snapshots/optimization/prices/etc.
ios/PowerSync/Services/AppModel.swift Main observable app model: state, demo mode, HA sync, and service calls.
ios/PowerSync/Resources/Assets.xcassets/Contents.json Asset catalog root metadata.
ios/PowerSync/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json Placeholder AppIcon asset slot.
ios/PowerSync/Resources/Assets.xcassets/AccentColor.colorset/Contents.json AccentColor definition.
ios/PowerSync/PowerSyncApp.swift SwiftUI app entry point and environment injection.
ios/PowerSync/PowerSync.entitlements App Group entitlement for sharing snapshots.
ios/PowerSync/Models/EnergyModels.swift Domain model types for snapshot/optimization/prices/EV/etc.
ios/PowerSync/LiveActivity/OptimizationActivityAttributes.swift App-target ActivityAttributes definition for live activity.
ios/PowerSync.xcodeproj/xcshareddata/xcschemes/PowerSync.xcscheme Shared Xcode scheme configuration.
ios/PowerSync.xcodeproj/project.pbxproj Checked-in Xcode project configuration for app + widgets targets.
.gitignore Adds Xcode-derived/xcuserdata ignore rules.

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

Comment thread ios/scripts/generate_xcodeproj.py
Comment on lines +555 to +565
text = "\n".join(lines) + "\n"
# Patch: Assets.xcassets fileRef should use path relative to Resources
# We'll move Assets into Resources by adjusting app_group generation — already complex.
# Simplest fix: change assets path to Resources/Assets.xcassets and put it in app group children with name.

# Rewrite assets file reference to include Resources/ path from PowerSync root
text = text.replace(
f"{assets_ref} /* Assets.xcassets */ = {{isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; }};",
f"{assets_ref} /* Assets.xcassets */ = {{isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Resources/Assets.xcassets; sourceTree = \"<group>\"; }};",
)

Comment thread ios/PowerSync/Services/AppModel.swift
}

Section("About") {
LabeledContent("PowerSync", value: "2.12")
Comment on lines +95 to +117
HStack(spacing: 10) {
ForEach(Array(model.touSlots.enumerated()), id: \.element.id) { index, slot in
VStack(spacing: 6) {
Text(slot.time.formatted(Formatters.time))
.font(.caption.weight(.semibold))
Text("B \(slot.buyCents.formatted(.number.precision(.fractionLength(0))))¢")
.font(.caption2)
.foregroundStyle(.red)
Text("S \(slot.sellCents.formatted(.number.precision(.fractionLength(0))))¢")
.font(.caption2)
.foregroundStyle(.secondary)
}
.padding(12)
.glassEffect(index == 2 ? .regular : .identity, in: .rect(cornerRadius: 16))
.overlay {
if index == 2 {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(.mint, lineWidth: 2)
}
}
}
}
}
Comment on lines +125 to +131
private func makeRequest(path: String) throws -> URLRequest {
guard let baseURL, let token, !token.isEmpty else {
throw HomeAssistantClientError.invalidURL
}
guard let url = URL(string: path, relativeTo: baseURL)?.absoluteURL else {
throw HomeAssistantClientError.invalidURL
}

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

https://github.com/Artic0din/PowerSync/blob/df52a194e509c04464506ea1162dc73e157438aa/ios/PowerSync/Views/Controls/ControlsView.swift#L232-L233
P2 Badge Debounce backup reserve writes while dragging

In live mode, SwiftUI fires this onChange for every intermediate slider value, so dragging from 20% to 80% can enqueue dozens of power_sync.set_backup_reserve calls and potentially hit the battery provider APIs repeatedly. Send the service call only when editing ends, or debounce/coalesce the changes before writing to Home Assistant.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


func configure(baseURLString: String, token: String) throws {
guard let url = URL(string: baseURLString.trimmingCharacters(in: .whitespacesAndNewlines)),
url.scheme == "http" || url.scheme == "https"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Either reject HTTP or add an ATS exception

When a user enters a common local Home Assistant URL like http://homeassistant.local:8123, this validation accepts it, but the generated app Info.plist does not define any NSAppTransportSecurity allowance, so iOS URLSession will block the cleartext request during ping(). Either add an ATS/local-network exception for the supported HTTP case or reject HTTP up front so onboarding does not offer a URL that cannot connect.

Useful? React with 👍 / 👎.


var next = snapshot
if let v = double(from: state(for: "solar_power")) { next.solarKW = v / 1000.0 }
if let v = double(from: state(for: "home_power")) { next.homeKW = abs(v) / 1000.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.

P1 Badge Map live data to PowerSync's actual entity IDs

For a connected PowerSync Home Assistant instance, the integration exposes sensor.power_sync_home_load, sensor.power_sync_battery_level, sensor.power_sync_current_import_price, and sensor.power_sync_current_export_price (see the sensor type constants in custom_components/power_sync/const.py), not home_power, battery_soc, general_price, or feed_in_price. As written, refreshes leave the home load, SOC, and prices at demo values even after a successful /api/states fetch; update these suffixes or use a central entity map.

Useful? React with 👍 / 👎.

}

Section("Operation Mode") {
Picker("Mode", selection: $model.operationMode) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Send picker changes to Home Assistant

In live mode, changing this picker only mutates the local observable value; there is no onChange or AppModel call to the existing power_sync.set_operation_mode service, and the grid export/charging/storm toggles below follow the same pattern. Users can leave this screen believing the battery configuration changed while Home Assistant remains unchanged, so either wire these controls to the HA services or make them explicitly read-only/demo-only.

Useful? React with 👍 / 👎.

}

var next = snapshot
if let v = double(from: state(for: "solar_power")) { next.solarKW = v / 1000.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.

P2 Badge Preserve HA kW units in the live snapshot

The PowerSync HA power sensors are already exposed in kW (for example sensor.power_sync_solar_power is UnitOfPower.KILO_WATT in custom_components/power_sync/sensor.py), so a connected site reporting 5.0 kW solar will be displayed as 0.005 kW here. Keep the snapshot units consistent, and likewise convert the grid/battery kW states before storing them in fields that the UI treats as watts.

Useful? React with 👍 / 👎.


func forceCharge() async {
await call(service: "force_charge", data: ["duration": String(forceChargeDuration.rawValue)])
statusMessage = "Force charge for \(forceChargeDuration.label)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report command success only after the service succeeds

When the live Home Assistant service call fails (network error, unauthorized token, or non-2xx response), call() catches the error and only sets connectionError, but this line still shows a successful force-charge alert. Users can be told a force mode is active even though HA rejected it; have call() return success or throw, and set the success message only after that succeeds.

Useful? React with 👍 / 👎.

else { return }
defaults.set(data, forKey: snapshotKey)
WidgetCenter.shared.reloadAllTimelines()
LiveActivityManager.sync(from: model)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid auto-starting demo Live Activities

On a fresh install AppModel.init defaults into demo data and calls reloadDemo(), which writes a snapshot and reaches this line; if no activity exists, LiveActivityManager.sync immediately requests one with demo optimization data before onboarding is complete. Gate Live Activity creation behind an explicit user action or completed live connection, and only update existing activities from background snapshot writes.

Useful? React with 👍 / 👎.

}

do {
try await client.configure(baseURLString: url, token: token)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the existing client on failed reconnects

If a live-connected user edits the connection and enters a reachable but invalid URL/token, configure mutates the actor before ping() proves the new credentials, and the catch path does not restore the previous client state. The UI and keychain still point at the old connection, but refreshes and controls now use the failed in-memory credentials until the user reconnects or restarts; stage the new configuration or restore the prior base URL/token on failure.

Useful? React with 👍 / 👎.

import SwiftUI
import WidgetKit

struct OptimizationActivityAttributes: ActivityAttributes {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Share one Live Activity attributes type

The app requests Activity<OptimizationActivityAttributes> using the app target's separate definition, while the widget extension registers an ActivityConfiguration for this duplicate extension-local type. Because ActivityKit matches activities by their attributes type, app-created activities can fail to render in the widget extension; put a single attributes source file in both targets instead of defining two independent types.

Useful? React with 👍 / 👎.


func refreshOptimization() async {
await refresh()
optimization.lastOptimized = .now

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fetch optimizer data before stamping it refreshed

In live mode, Refresh Now for Smart Optimization only runs the generic state refresh and then overwrites lastOptimized, so the schedule, costs, and actions can remain demo/stale data while the UI claims they were optimized just now. The HA integration already exposes /api/power_sync/optimization; fetch and apply that response, or avoid updating this timestamp when no optimizer payload was loaded.

Useful? React with 👍 / 👎.

- Remove unused `uuid` import from generate_xcodeproj.py
- Add fail-fast check for fragile text.replace() in generate_xcodeproj.py
- Flip usesDemoMode=true and persist it when falling back to demo on reconnect failure in AppModel.swift
- Read app version from CFBundleShortVersionString instead of hard-coding '2.12' in SettingsView.swift
- Highlight active TOU slot based on current time instead of hard-coded index 2 in PricesView.swift
- Add notConfigured error case for unconfigured client in HomeAssistantClient.swift
Artic0din and others added 3 commits July 22, 2026 10:56
* chore(workflow): adopt GitHub-native task tracking

Replace Linear-first agent guidance with GitHub Issues and the shared Development Project. Keep pull requests and CI authoritative for implementation.

* fix(docs): remove stray workflow marker
…oud setup) (#52)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci(github): install Repo Assist

Install the current upstream Copilot workflow, compiled lock file, and
agentic-workflow tooling. Use GitHub-native issues, pull requests, and
persistent repository memory.

* fix(github): satisfy workflow security gates

Disable checkout credential persistence and document the generated
workflow exceptions required by repository linting.

* fix(github): address Repo Assist review findings

Authenticate scheduled checks, remove the undefined merge driver, align
the Copilot setup action, and clarify generated configuration.

* fix(github): use keyless Copilot authentication

Use the per-run GitHub token for Copilot inference, keep capacity skips
green, and align active task guidance with GitHub Issues.

* fix(github): harden Repo Assist pull requests

Use trusted labels for automation capacity and branch updates, produce
conventional pull request titles, and guarantee three distinct tasks.

* fix(github): align Repo Assist maintenance markers

Match generated pull request titles and labels, disclose automated status messages, and avoid an undefined merge driver.

* fix(github): complete Repo Assist output policy

Allow required changelog updates and narrowly permit closing labeled Repo Assist summary issues.

* fix(github): serialize Repo Assist runs

Prevent overlapping repository runs and suppress noisy no-op and failure issues while preserving visible workflow failures.

* fix(github): expose workflow upgrade tools

Allow cloud agents to run the fix and upgrade routes advertised by the installed Agentic Workflows skill.
@Artic0din Artic0din closed this Jul 23, 2026
@Artic0din
Artic0din deleted the cursor/native-swiftui-app-9f0a branch July 23, 2026 10:59

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08a5c914be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +103 to +104
Button(role: .destructive) {
Task { await model.goOffGrid() }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require confirmation before islanding the Powerwall

On a live Powerwall install, tapping this destructive button immediately calls powerwall_go_off_grid, so an accidental tap can island the site without any second confirmation; the HA service only checks SOC, not user intent. Please gate this hardware command behind a confirmation dialog before dispatching it.

AGENTS.md reference: AGENTS.md:L3-L4

Useful? React with 👍 / 👎.

Comment on lines +75 to +76
.onChange(of: model.backupReservePercent) { _, newValue in
Task { await model.setBackupReserve(newValue) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Read the live reserve before sending slider writes

In live mode the reserve slider is initialized from the hard-coded backupReservePercent = 20 and refreshFromHomeAssistant() never loads the actual number.power_sync_tesla_backup_reserve, so a site currently held at, for example, 50% will see 20% and the first drag here sends set_backup_reserve near that stale value, lowering the outage reserve. Disable this write until the live reserve has been read, or fetch it before enabling the slider.

AGENTS.md reference: AGENTS.md:L3-L4

Useful? React with 👍 / 👎.

import Foundation

enum Formatters {
static let currency: FloatingPointFormatStyle<Double>.Currency = .currency(code: "AUD")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive displayed currency from Home Assistant

For live sites using non-AUD providers that PowerSync supports, such as Octopus (GBP), EPEX (EUR), or NZ tariffs, every cost/savings value is formatted as AUD and the price helper also hard-codes ¢/kWh. The HA sensors already expose currency metadata, so the iOS model should load and use that instead of presenting GBP/EUR/NZD amounts as Australian dollars.

Useful? React with 👍 / 👎.

Comment on lines +248 to +250
func updateSummaryPeriod(_ period: SummaryPeriod) {
summaryPeriod = period
energyTotals = DemoDataFactory.makeEnergyTotals(for: period)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fetch live energy history instead of demo totals

When a connected user changes the Day/Week/Month/Year picker, this always overwrites the displayed totals with DemoDataFactory values; it never calls PowerSync's mobile calendar-history endpoint (/api/power_sync/calendar_history or the matching service) that exists for these summaries. Live users therefore see fabricated generation/export totals whenever they switch periods.

Useful? React with 👍 / 👎.

Comment on lines +233 to +236
func rescanBattery() async {
batteryHealth.lastScan = .now
statusMessage = "Battery health scan recorded"
SharedSnapshotWriter.write(from: self)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Send battery scans to HA instead of stamping locally

In live mode the "Rescan Battery (TEDAPI)" action only changes the in-memory timestamp and writes the unchanged demo health values to the shared snapshot; it never performs a TEDAPI read or posts to PowerSync's /api/power_sync/battery_health/sync_battery_health path. Users can be told a scan was recorded while Home Assistant and the displayed capacity/health numbers were never updated.

Useful? React with 👍 / 👎.

get: {
model.automations.first(where: { $0.id == rule.id })?.isEnabled ?? false
},
set: { model.setAutomation(rule, enabled: $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.

P2 Badge Toggle real automations instead of demo rows

For a live connection, this setter only flips the local demo AutomationRule array; it does not load the real automation list from /api/power_sync/automations or call the /toggle endpoint, whose IDs are backend automation IDs rather than these generated UUIDs. A user can disable a price/grid automation in the app while the Home Assistant automation remains active.

Useful? React with 👍 / 👎.

Comment on lines +253 to +254
func setEVMode(_ mode: EVChargingMode, enabled: Bool) {
evModes[mode] = enabled

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Send EV mode changes to the backend

For connected users, changing any EV charging mode only mutates the in-memory evModes dictionary; it never calls the existing PowerSync EV endpoints such as auto-schedule toggle, solar-surplus config, or price-level settings. Users can believe car-charging automation changed while Home Assistant continues running the previous EV policy.

AGENTS.md reference: AGENTS.md:L3-L4

Useful? React with 👍 / 👎.

Comment on lines +53 to +54
var batterySystemName = "Tesla Powerwall"
var isBatteryAutoDetected = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect the actual battery system before enabling controls

On live non-Tesla installations such as Sigenergy, Sungrow, FoxESS, or GoodWe, this remains "Tesla Powerwall" and isBatteryAutoDetected stays true because the app never reads PowerSync's /api/power_sync/backend_config battery_system/capability data. That mislabels the site and leaves Tesla-specific controls like off-grid available for the wrong hardware, so fetch the backend configuration before marking the system auto-detected or showing those controls.

AGENTS.md reference: AGENTS.md:L3-L4

Useful? React with 👍 / 👎.

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.

4 participants