Add Astral's ty type checker (written in Rust, 10-100x faster than mypy)
as a second type checking layer. Both run in pre-commit and CI.
Fixed type issues caught by ty:
- config.py: explicit Host constructor to avoid dict unpacking issues
- executor.py: wrap subprocess.run in closure for asyncio.to_thread
- api.py: use getattr for Jinja TemplateModule macro access
- test files: fix playwright driver_path tuple handling, pytest rootpath typing
- Add pull_request trigger with same path filters
- Skip Pages setup and artifact upload on PRs (only build)
- Skip deploy job entirely on PRs
- Update concurrency to include ref for parallel PR builds
- Add Web UI page to navigation in zensical.toml
- Use absolute paths for video assets in web-ui.md
- Add web-workflow demo video to homepage Quick Demo section
Relative paths like `assets/install.webm` resolved incorrectly on
subpages (e.g., /getting-started/assets/install.webm instead of
/assets/install.webm), causing 404 errors for videos on those pages.
- Delete unused add_service_to_host/remove_service_from_host from state.py
(42 lines of dead code never called anywhere)
- Extract _stream_output_lines helper in executor.py to deduplicate
identical read_stream functions in _run_local_command and _run_ssh_command
- Simplify unique-list logic in compose.py using dict.fromkeys()
instead of manual seen/unique set/list pattern
Total: -67 lines
- Remove dead code: `run_host_operation` in cli/common.py (never called)
- Inline `_report_*` helpers in lifecycle.py (each called once)
- Merge `validate_host` into `validate_hosts` with flexible str|list param
- Merge `_report_no_config_found` and `_report_config_path_not_exists`
into single `_report_missing_config` function
- Simplify `_get_editor` from 18 lines to 6 using walrus operator
- Extract `COMPOSE_FILENAMES` constant to avoid duplication in config.py
- Extract `_stream_subprocess` helper to reduce duplication in streaming.py
Net reduction: ~130 lines of code with no functionality changes.
* feat(web): Show (local) label in sidebar host selector
Add local host detection to sidebar partial, matching the console page
behavior where the current machine is labeled with "(local)" in the
host dropdown.
* refactor: Extract get_local_host() helper to deps.py
DRY up the local host detection logic that was duplicated between
console and sidebar_partial routes.
* revert
One-shot containers (like CLI tools) were showing a perpetual loading
spinner because they weren't in `docker compose ps` output. Now we:
- Use `ps -a` to include stopped/exited containers
- Display exit code: neutral badge for clean exit (0), error badge for failures
- Show "created" state for containers that were never started
500% background-size showed too much of the gradient at rest,
revealing green (#bfff80) at the button edge. 650% shows ~15%
of the gradient, landing safely on white while still improving
color visibility during animation.
The 900% background-size meant only ~11% of the gradient was visible
at any time. On smaller screens, the rainbow colors would flash by
too quickly during the intro animation, appearing mostly white.
Use a CSS variable for background-size and reduce it to 500% on
mobile (<768px), showing ~20% of the gradient for a more visible
rainbow effect.
After a self-update, the browser tries to reconnect to the old task_id
but the in-memory task registry is empty (new container). Show a
helpful message instead of a scary "Error" message.
* fix(web): Ensure URL updates after HTMX navigation in command palette
Use history.pushState() after HTMX swap completes to ensure
window.location.pathname is correct when rebuilding commands.
* docs: Add rule about unchecked checklists in PR descriptions
* fix(web): Add PATH for self-update SSH command
Non-interactive SSH sessions don't source profile files, so `cf` isn't
found when installed in ~/.local/bin. Prepend common install locations
to PATH before running the remote command.
* fix(web): Enable TTY for self-update SSH to show progress bars
Non-interactive SSH sessions don't source profile files, so `cf` isn't
found when installed in ~/.local/bin. Prepend common install locations
to PATH before running the remote command.
* refactor: Store SSH keys in subdirectory for cleaner volume mounting
Change SSH key location from ~/.ssh/compose-farm (file) to
~/.ssh/compose-farm/id_ed25519 (file in directory).
This allows docker-compose to mount just the compose-farm directory
to /root/.ssh without exposing all host SSH keys to the container.
Also make host path the default option in docker-compose.yml with
clearer comments about the two options.
* docs: Update README for new SSH key directory structure
* docs: Clarify cf ssh setup must run inside container
Use run_parallel_with_progress for visual feedback during host checks.
Results are now sorted alphabetically for consistent output.
Also adds code style rule to CLAUDE.md about keeping imports at top level.
When triggering Apply or Refresh from the command palette on a non-dashboard
page, navigate to the dashboard first and then execute the action, opening
the terminal output.