CMakeLists.txt              (project root -- required by ESP-IDF)
sdkconfig.defaults
partitions.csv
README.md
TODO.md
main/
├── CMakeLists.txt          (the only component registration in the project)
├── idf_component.yml
├── app_config.h.example    (template; copy to app_config.h)
├── app_config.h            (gitignored -- credentials)
├── main.c
├── wifi/
│   ├── wifi_manager.h
│   └── wifi_manager.c
├── drivers/
│   ├── i2c_bus.h           (shared bus: STC8H1K28 + GT911)
│   ├── i2c_bus.c
│   ├── backlight/
│   │   ├── backlight.h     (STC8H1K28 -- also gates GT911 reset)
│   │   └── backlight.c
│   ├── display/
│   │   ├── display_rgb.h
│   │   └── display_rgb.c
│   └── touch/
│       ├── touch_gt911.h
│       └── touch_gt911.c
├── ui/
│   ├── lvgl_port.h         (tick, service task, locking, pause/resume)
│   ├── lvgl_port.c
│   ├── ui_manager.h
│   └── ui_manager.c
└── api/
    ├── ha_config.h         (entity IDs -- tracked, not secret)
    ├── ha_template.h       (Jinja template, built from the HA_LIGHT_LIST X-macro)
    ├── ha_data.h
    ├── ha_data.c           (parsing + presentation -- SHARED with sim/)
    ├── ha_client.h
    ├── ha_client.c         (esp_http_client transport -- device only)
    ├── ambient_weather.h
    └── ambient_weather.c   (cloud fallback, used only when HA is unreachable)

sim/                        (desktop simulator -- NOT part of the IDF build)
├── CMakeLists.txt          (own cmake project; idf.py never looks in here)
├── lv_conf.h               (host LVGL config; font list must match sdkconfig.defaults)
├── sim_main.c              (SDL window / headless PNG capture, mock data)
├── lvgl_port_sim.c         (host implementation of main/ui/lvgl_port.h)
├── README.md
├── snapshots/              (captured HA responses, used as render fixtures)
└── compat/                 (ESP-IDF header stand-ins, so main/ui/*.c compiles as-is)
    ├── esp_err.h
    ├── esp_log.h
    ├── esp_check.h
    └── esp_compat.c

tools/
├── ha_discover.py          (list Home Assistant entity IDs; reads $HA_TOKEN)
└── ha_snapshot.py          (capture HA responses into sim/snapshots/)

Note: main/ subdirectories are plain source folders, not ESP-IDF components.
IDF only auto-discovers components under a top-level components/ directory,
so all sources are registered by main/CMakeLists.txt.

The simulator compiles main/ui/ui_manager.c unmodified against desktop LVGL,
which is why that file must stay free of hardware access. See sim/README.md.
