mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-20 09:12:05 +08:00
update
This commit is contained in:
@@ -53,7 +53,7 @@ All metadata (titles, descriptions, options, min/max/step/unit) lives **inline o
|
||||
|
||||
**Visibility design**: Settings are always visible. When visibility rules fail, the setting is dimmed with an UNAVAILABLE badge, so users know it exists but is not applicable.
|
||||
|
||||
**Enablement rules**: Greyed out (disabled) when rules fail. Frontend shows a contextual badge explaining why.
|
||||
**Enablement rules**: Grayed out (disabled) when rules fail. Frontend shows a contextual badge explaining why.
|
||||
|
||||
**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `enable_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`
|
||||
|
||||
@@ -216,7 +216,7 @@ Individual options within `multiple_button` or `option` widgets can have their o
|
||||
}
|
||||
```
|
||||
|
||||
When an option's enablement fails, that option is greyed out (disabled) but still visible.
|
||||
When an option's enablement fails, that option is grayed out (disabled) but still visible.
|
||||
|
||||
### Show only when another setting is on
|
||||
|
||||
@@ -246,16 +246,16 @@ Note: Due to the "dim instead of hide" design, this setting will be dimmed (not
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "OptionA",
|
||||
"key": "FeatureAlpha",
|
||||
"widget": "toggle",
|
||||
"title": "Option A",
|
||||
"enablement": [{"type": "param", "key": "OptionB", "equals": false}]
|
||||
"title": "Feature Alpha",
|
||||
"enablement": [{"type": "param", "key": "FeatureBeta", "equals": false}]
|
||||
},
|
||||
{
|
||||
"key": "OptionB",
|
||||
"key": "FeatureBeta",
|
||||
"widget": "toggle",
|
||||
"title": "Option B",
|
||||
"enablement": [{"type": "param", "key": "OptionA", "equals": false}]
|
||||
"title": "Feature Beta",
|
||||
"enablement": [{"type": "param", "key": "FeatureAlpha", "equals": false}]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ Root
|
||||
| `unit` | No | Unit label. Static: `"seconds"`. Dynamic: `{"metric": "km/h", "imperial": "mph"}` (frontend resolves based on `IsMetric` param). See [Dynamic Units](#dynamic-units). |
|
||||
| `value_map` | No | Maps stored values to display labels |
|
||||
| `visibility` | No | Rules controlling visibility. Settings are **never hidden**, always dimmed with UNAVAILABLE badge when rules fail. See [Visibility vs Enablement](#visibility-vs-enablement). |
|
||||
| `enablement` | No | Rules controlling enabled/disabled state. Greyed out with contextual badge when rules fail. |
|
||||
| `enablement` | No | Rules controlling enabled/disabled state. Grayed out with contextual badge when rules fail. |
|
||||
| `blocked` | No | When `true`, this param cannot be modified remotely (device-only). Frontend shows as read-only. |
|
||||
| `title_param_suffix` | No | Dynamic title suffix. Object with `param` (param key) and `values` (mapping of param values to suffix strings). |
|
||||
| `sub_items` | No | Child items that appear indented below this item |
|
||||
@@ -235,13 +235,13 @@ Root
|
||||
|
||||
- **`visibility` rules** (NEW): Settings are **never hidden**. When rules fail, the setting is **dimmed with an UNAVAILABLE badge** so users know it exists but isn't applicable. This prevents confusion and preserves UI stability.
|
||||
|
||||
- **`enablement` rules**: When rules fail, the setting is **greyed out** with a contextual badge explaining why (e.g., "Requires longitudinal control"). User can still see it exists.
|
||||
- **`enablement` rules**: When rules fail, the setting is **grayed out** with a contextual badge explaining why (e.g., "Requires longitudinal control"). User can still see it exists.
|
||||
|
||||
The "dim instead of hide" approach (visibility-based dimming instead of hiding) provides better UX: settings remain discoverable, and users understand why a setting is unavailable.
|
||||
|
||||
## Rules Reference
|
||||
|
||||
Rules control **visibility** (dimmed) and **enablement** (greyed out).
|
||||
Rules control **visibility** (dimmed) and **enablement** (grayed out).
|
||||
|
||||
- All rules in an array use **AND** logic (all must pass for the rule to pass)
|
||||
- If ANY rule fails, the condition is unsatisfied
|
||||
@@ -660,7 +660,7 @@ Individual options within `multiple_button` or `option` widgets can have their o
|
||||
}
|
||||
```
|
||||
|
||||
When an option's enablement fails, that option is **greyed out** (disabled but still visible and selectable). This prevents users from changing to an unavailable option but keeps the UI stable.
|
||||
When an option's enablement fails, that option is **grayed out** (disabled but still visible and selectable). This prevents users from changing to an unavailable option but keeps the UI stable.
|
||||
|
||||
---
|
||||
|
||||
@@ -1057,7 +1057,7 @@ At device boot, the generator reads `settings_ui.json`, compresses it, and write
|
||||
|
||||
**Q: What's the difference between `visibility` and `enablement`?**
|
||||
- `visibility`: hidden entirely when rules fail (user doesn't know it exists)
|
||||
- `enablement`: visible but greyed out when rules fail (user sees it but can't change it)
|
||||
- `enablement`: visible but grayed out when rules fail (user sees it but can't change it)
|
||||
|
||||
**Q: How do I test my changes locally?**
|
||||
Run the generator directly to see the full output:
|
||||
|
||||
Regular → Executable
Regular → Executable
+14
-10
@@ -284,8 +284,10 @@ def check_structural(data: dict, result: ValidationResult) -> None:
|
||||
if "widget" not in item:
|
||||
errors.append(f"{path}: item missing required field 'widget'")
|
||||
elif item["widget"] not in VALID_WIDGETS:
|
||||
errors.append(f"{path}: item '{item.get('key', '?')}' has invalid widget '{item['widget']}' "
|
||||
f"(must be one of {VALID_WIDGETS})")
|
||||
errors.append(
|
||||
f"{path}: item '{item.get('key', '?')}' has invalid widget '{item['widget']}'"
|
||||
+ f" (must be one of {VALID_WIDGETS})"
|
||||
)
|
||||
|
||||
if errors:
|
||||
result.error("structural", "; ".join(errors))
|
||||
@@ -298,7 +300,7 @@ def check_item_completeness(data: dict, result: ValidationResult) -> None:
|
||||
all_items = collect_all_items(data)
|
||||
issues: list[str] = []
|
||||
|
||||
for path, item in all_items:
|
||||
for _path, item in all_items:
|
||||
key = item.get("key", "unknown")
|
||||
if "title" not in item:
|
||||
issues.append(f"{key}: missing 'title'")
|
||||
@@ -350,7 +352,6 @@ def check_no_duplicate_keys(data: dict, result: ValidationResult) -> None:
|
||||
def check_rule_wellformedness(data: dict, result: ValidationResult) -> None:
|
||||
"""Check 5: All rules have valid structure."""
|
||||
all_items = collect_all_items(data)
|
||||
has_errors = False
|
||||
|
||||
# Save current error count to detect new errors
|
||||
error_count_before = len(result.failed)
|
||||
@@ -358,8 +359,7 @@ def check_rule_wellformedness(data: dict, result: ValidationResult) -> None:
|
||||
for path, item in all_items:
|
||||
for ctx, rules in collect_rules_from_item(item):
|
||||
for i, rule in enumerate(rules):
|
||||
if not validate_rule(rule, f"{path} > {ctx}[{i}]", result, CAPABILITY_FIELDS):
|
||||
has_errors = True
|
||||
validate_rule(rule, f"{path} > {ctx}[{i}]", result, CAPABILITY_FIELDS)
|
||||
|
||||
# Also validate trigger_condition rules on sub_panels
|
||||
for panel in data.get("panels", []):
|
||||
@@ -438,15 +438,19 @@ def check_sub_panel_triggers(data: dict, result: ValidationResult) -> None:
|
||||
for sp in section.get("sub_panels", []):
|
||||
trigger = sp.get("trigger_key")
|
||||
if trigger and trigger not in panel_keys:
|
||||
errors.append(f"sub_panel '{sp.get('id', '?')}' trigger_key '{trigger}' "
|
||||
f"not found in panel '{pid}'")
|
||||
errors.append(
|
||||
f"sub_panel '{sp.get('id', '?')}' trigger_key '{trigger}'"
|
||||
+ f" not found in panel '{pid}'"
|
||||
)
|
||||
|
||||
# Check top-level sub_panels
|
||||
for sp in panel.get("sub_panels", []):
|
||||
trigger = sp.get("trigger_key")
|
||||
if trigger and trigger not in panel_keys:
|
||||
errors.append(f"sub_panel '{sp.get('id', '?')}' trigger_key '{trigger}' "
|
||||
f"not found in panel '{pid}'")
|
||||
errors.append(
|
||||
f"sub_panel '{sp.get('id', '?')}' trigger_key '{trigger}'"
|
||||
+ f" not found in panel '{pid}'"
|
||||
)
|
||||
|
||||
if errors:
|
||||
result.error("sub-panel triggers", "; ".join(errors))
|
||||
|
||||
Reference in New Issue
Block a user