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.
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 anumberplatform with live entities for the 4 brightness/CT ranges.house-mode-modes— adds aninput_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 indesign.md. Keep traceability. - No silent migrations: CDiT-fork breaking changes bump
manifest.jsonmajor and reject incompatible config entries with a clear error (seecdit-config-redesigndesign decision 4). openspec/config.yamlcontext:field: still unset. Fill in once CDiT tech-stack conventions stabilize — it propagates into every artifact prompt.