Skip to content

Entity re-sync dialog doesn't pre-fill saved manual mappings #415

Description

@lumaxis

Version: Zone Configurator 2.2.5, EP Pro firmware 1.3.0

Steps

  1. Rename the device's Occupancy entity in HA (e.g. to binary_sensor.living_room_presence), so it no longer matches binary_sensor.${name}_occupancy.
  2. Settings → Re-sync entities → map "Presence" manually → Continue. Save succeeds.
  3. Open Re-sync entities again.

Expected

"Presence" is pre-selected with the saved entity.

Actual

"Presence" shows "Select entity…" and "Unmatched required: 1" again, as if nothing had been saved. Pressing Continue at this point saves the incomplete auto-match and overwrites the earlier manual mapping.

Cause

In EntityDiscovery.tsx, existingMapping is loaded asynchronously in its own effect, but runDiscovery reads it inside a useCallback whose dependencies are only [deviceId, profileId]:

// Load existing device mapping to preserve user overrides during re-sync
useEffect(() => {
const loadExistingMapping = async () => {
try {
const mapping = await getMapping(deviceId);
setExistingMapping(mapping);
} catch {
setExistingMapping(null);
}
};
loadExistingMapping();
}, [deviceId, getMapping]);
const runDiscovery = useCallback(async () => {
setStatus('loading');
setError(null);
setValidationErrors(null);
setManualOverrides({});
setDiscoveredServices({});
try {
const result = await discoverEntities(deviceId, profileId);
setDiscoveryResult(result);
setAllEntities(result.deviceEntities);
setDiscoveredServices(result.serviceMappings ?? {});
const conflictCount = result.results.filter((r) => r.matchConfidence === 'conflict').length;
if (result.allMatched) {
setStatus(conflictCount > 0 ? 'partial' : 'success');
} else {
setStatus('partial');
// Auto-expand groups with unmatched entities
const groups = groupMatchResultsByCategory(result.results);
const groupsWithUnmatched = new Set<string>();
for (const [groupName, results] of Object.entries(groups)) {
if (results.some((r) => !r.matchedEntityId && !r.isOptional)) {
groupsWithUnmatched.add(groupName);
}
}
setExpandedGroups(groupsWithUnmatched);
}
// Seed manual overrides from existing mapping so user-set values persist on re-sync
if (existingMapping) {
const overrides: Record<string, string> = {};
for (const match of result.results) {
const key = match.templateKey;
const m = existingMapping.mappings;
const direct = m[key];
const withEntitySuffix = m[`${key}Entity`];
const withoutEntitySuffix = key.endsWith('Entity') ? m[key.replace(/Entity$/, '')] : undefined;
const chosen = direct || withEntitySuffix || withoutEntitySuffix;
if (chosen) {
overrides[key] = chosen;
}
}
setManualOverrides(overrides);
}
} catch (err) {
setError((err as Error).message);
setStatus('error');
// Try to at least get the device entities for manual mapping
try {
const { entities } = await getDeviceEntities(deviceId);
setAllEntities(entities);
} catch {
// Ignore - we'll just have no entities
}
}
}, [deviceId, profileId]);
useEffect(() => {
runDiscovery();
}, [runDiscovery]);

runDiscovery runs once with the first-render closure, where existingMapping is still null, so the "Seed manual overrides from existing mapping" block never runs.

A possible fix is to load the mapping inside runDiscovery (const existing = await getMapping(deviceId)) and seed the overrides from that.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions