adaptive-lighting/CLAUDE.md
Casey bba0ef31d8 Delete upstream docs apparatus; rewrite README for the fork
The CDiT fork is single-household and was carrying an entire upstream
docs/ mkdocs site documenting features the fork has removed (sleep mode,
take-over-control, brightness_mode selector, YAML config). The README
mostly auto-generated from those pages via markdown-code-runner.

What's gone:

- docs/ — all 11 pages + assets + run_markdown_code_runner.py
- .github/workflows/docs.yml — zensical+shinylive GitHub Pages build
- .github/workflows/markdown-code-runner.yml — auto-edit of README from docs/
- .github/update-services.py / update-strings.py — would clobber the
  hand-edited services.yaml / strings.json
- scripts/update-generated-content — orchestrator for the above
- custom_components/adaptive_lighting/_docs_helpers.py + docs_gen.py —
  only used by the deleted markdown-code-runner
- zensical.toml — docs-site config
- "docs" dependency group in pyproject.toml (markdown-code-runner,
  shinylive, zensical, etc.) + lock regenerated

What's new:

- README.md rewritten to describe the fork's actual surface — 3 switches,
  4 number entities, 3 sensor entities per profile, 2 services (apply +
  change_switch_settings), entry-only setup, UI tuning, sun source.
  Preserves the existing "What's new in 2.1" / "2.2" block quotes.
- webapp/README.md annotated to flag the Shiny simulator models the
  upstream curve (with brightness_mode, sleep mode, etc.) and does not
  reflect the fork.
- CLAUDE.md command table no longer references update-generated-content.

Sensor.py + test_sensor_platform.py: incidental black reformat from the
lint pass (multi-arg function calls split per line).

Tests still pass (123/123). Lint clean. Sensors implementation
unchanged.
2026-05-21 10:23:04 +02:00

5.9 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

A CDiT fork of basnijholt/adaptive-lighting — a Home Assistant custom component that adjusts light brightness and color temperature in sync with the sun curve. The fork is opinionated about CDiT's single-household setup and explicitly diverges from upstream on the integration's configuration surface.

Fork is not a drop-in replacement. Existing upstream config entries will fail to load (deliberate, see openspec/changes/cdit-config-redesign/design.md decision 4).

Remote topology

origin    git@github.com:CaseyRo/adaptive-lighting.git   (the fork, read-write)
upstream  https://github.com/basnijholt/adaptive-lighting.git   (read-only)

Sync upstream changes:

git fetch upstream
git merge upstream/main         # or rebase, depending on local policy
# resolve conflicts under custom_components/adaptive_lighting/

Commands

Python tooling is uv (not pip or poetry). All commands assume an activated venv from ./scripts/setup-devcontainer.

Task Command
Run the test suite uv run pytest
Run a single test uv run pytest tests/test_config_flow.py::test_options_flow_init
Lint everything (pre-commit) ./scripts/lint
Run HA locally with the custom component loaded ./scripts/develop (boots HA on localhost:8123 from ./config/)
Refresh test matrix ./scripts/update-test-matrix.py

Ruff config lives in .ruff.toml. Pre-commit hooks in .pre-commit-config.yaml. Both enforce on push via ./scripts/lint.

Architecture

This is a standard Home Assistant custom component packaged under custom_components/adaptive_lighting/. HA loads it by directory name when present in a HA config's custom_components/. The HACS integration provides distribution.

Big-picture layout:

custom_components/adaptive_lighting/
├── __init__.py             entry point: async_setup_entry, async_unload_entry,
│                           and the curve math (sun-position + brightness/CT calc)
├── config_flow.py          UI flow for setup and reconfiguration. Iterates
│                           VALIDATION_TUPLES from const.py to build the schema.
├── switch.py               the 4 switch entities per AL config (master, sleep,
│                           adapt_color, adapt_brightness). After
│                           cdit-config-redesign lands: 3 switches (no sleep).
├── const.py                CONF_/DEFAULT_ constants + VALIDATION_TUPLES — the
│                           single source for what fields exist in the schema.
├── color_and_brightness.py pure math: tanh, sun-elevation curve, color-temp
│                           interpolation.
├── adaptation_utils.py     per-light service-call shaping (intercept,
│                           skip_redundant_commands, split commands, etc.)
├── hass_utils.py           HA-specific helpers (entity registry lookups,
│                           timestamps, service-call dispatch).
├── manifest.json           integration metadata; `version` here gates
│                           the strict break in cdit-config-redesign.
└── strings.json            UI labels for the config flow, errors, abort reasons.

The CDiT fork is also planning two follow-on changes that layer cleanly on top:

  • add-runtime-range-controls — adds a number platform with live entities for the 4 brightness/CT ranges.
  • house-mode-modes — adds an input_select-driven behavior matrix that drives the 3 runtime switches on house-mode change.

Neither is implemented yet; both have proposal stubs in openspec/changes/.

Planning workflow — OpenSpec under opsx

All non-trivial changes go through OpenSpec before code lands. The experimental opsx schema (artifact-driven: proposal → design → specs → tasks) is configured. Drive it via .claude/commands/opsx/* slash commands or the openspec-* skills.

openspec/
├── config.yaml             (project context lives here once filled in)
├── specs/                  long-lived capability specs (empty until a change archives)
└── changes/
    ├── cdit-config-redesign/         active, 4/4 artifacts, strict-validate green
    │   ├── proposal.md     why + what changes + capabilities + impact
    │   ├── design.md       15 decisions with rejected alternatives, risks, migration
    │   ├── specs/options-flow/spec.md   9 requirements, 22 Given/When/Then scenarios
    │   └── tasks.md        9 task groups, 36 checkboxes with [R, D] annotations
    ├── add-runtime-range-controls/   stub, proposal only
    └── house-mode-modes/             stub, proposal only

Validate any active change with:

openspec validate <change-name> --strict

When implementing tasks for an active change, prefer /opsx:apply <change-name> over manual edits — it walks tasks.md checkbox by checkbox. /opsx:verify checks the resulting code against the spec's scenarios before archive.

Conventions worth preserving

  • Change names: kebab-case (e.g., cdit-config-redesign, add-runtime-range-controls).
  • Capability slugs: kebab-case, scoped tight (options-flow, runtime-range-controls, house-mode-binding) — not broad umbrellas.
  • Annotations in tasks.md: each task ends with [R…, D…] referencing requirements in the change's spec delta and decisions in design.md. Keep traceability.
  • No silent migrations: CDiT-fork breaking changes bump manifest.json major and reject incompatible config entries with a clear error (see cdit-config-redesign design decision 4).
  • openspec/config.yaml context: field: still unset. Fill in once CDiT tech-stack conventions stabilize — it propagates into every artifact prompt.