Skip to content

feat(tui): implement comprehensive local configuration inspector and TUI#151

Merged
Kilo59 merged 36 commits intomainfrom
tui
Apr 5, 2026
Merged

feat(tui): implement comprehensive local configuration inspector and TUI#151
Kilo59 merged 36 commits intomainfrom
tui

Conversation

@Kilo59
Copy link
Copy Markdown
Owner

@Kilo59 Kilo59 commented Apr 4, 2026

Summary by Sourcery

Introduce a Ruff-Sync terminal UI (TUI) for inspecting Ruff configuration and rules, integrate it with the CLI, and add supporting system utilities, themes, and tests.

New Features:

  • Add an interactive Ruff-Sync terminal UI application with configuration tree navigation, rule tables, markdown inspector, omnibox search, and legend modal.
  • Expose a dedicated ruff-inspect console entry point and CLI subcommand wiring to launch the TUI.
  • Provide async system helpers to fetch Ruff rule and configuration documentation, list all rules and linters, and compute effective rule statuses from configuration.
  • Introduce custom TUI themes tailored for Ruff-Sync, including dark and light variants.

Enhancements:

  • Refactor Ruff subprocess execution into a shared async helper with improved logging and empty-output handling.
  • Extend design and internal agent documentation to cover TUI architecture, rule browsing behavior, and known documentation deployment issues.

Build:

  • Register the new ruff-inspect console script in project metadata and bump the project version to 0.1.5.dev3.

CI:

  • Add a CI job that installs the package without extras and runs the minimal-imports script to ensure optional dependencies are not required by default.

Documentation:

  • Document TUI requirements, design, and rule browsing flows in new internal design files, and record known issues and post-mortem notes for documentation versioning.

Tests:

  • Add comprehensive tests for the TUI app behavior, including mounting, node and row selection, legend and clipboard actions, and CLI entry integration.
  • Add tests for rule status computation logic across select/ignore/extend scenarios.
  • Add tests to verify custom themes registration and properties.
  • Add a shell-based minimal-environment test script to validate behavior without optional TUI dependencies.

Chores:

  • Add a placeholder module for the ruff_sync.tui package and internal constants used by the TUI.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 4, 2026

Reviewer's Guide

Implements a new Textual-based terminal UI for inspecting Ruff configuration and rules, wires it into the existing CLI via a new inspect entry point, and adds system helpers plus tests and CI coverage to support rule/config discovery without breaking installs lacking TUI dependencies.

Sequence diagram for launching and using the RuffSync TUI inspect command

sequenceDiagram
  actor User
  participant Shell
  participant Script as ruff-inspect
  participant CLI as ruff_sync.cli
  participant App as RuffSyncApp
  participant Cfg as load_local_ruff_config
  participant Sys as system helpers
  participant Ruff as ruff binary

  User->>Shell: run `ruff-inspect` or `ruff-sync inspect`
  Shell->>Script: execute console script
  Script->>CLI: inspect()
  CLI->>CLI: normalize sys.argv and insert inspect
  CLI->>CLI: main()
  CLI->>CLI: parse CLI arguments
  CLI->>CLI: _resolve_upstream(args)
  CLI-->>CLI: returns empty upstream tuple for inspect
  CLI->>App: create RuffSyncApp(args)
  CLI->>App: run()

  App->>App: on_mount()
  App->>Cfg: load_local_ruff_config(args.to)
  Cfg-->>App: local Ruff config dict
  App->>App: populate ConfigTree with config
  App->>App: register themes and set default
  App->>App: _prime_caches() (background)

  App->>Sys: get_all_ruff_rules()
  Sys->>Ruff: run `ruff rule --all --output-format json`
  Ruff-->>Sys: JSON rules
  Sys-->>App: list of rules

  App->>Sys: get_ruff_linters()
  Sys->>Ruff: run `ruff linter --output-format json`
  Ruff-->>Sys: JSON linters
  Sys-->>App: list of linters

  App->>Sys: compute_effective_rules(all_rules, config)
  Sys-->>App: rules with status
  App->>App: repopulate ConfigTree with linter groups

  User->>App: select node or row / open Omnibox
  App->>App: handle_node_selected / handle_row_selected / handle_omnibox_result
  App->>Sys: get_ruff_rule_markdown() or get_ruff_config_markdown()
  Sys->>Ruff: run `ruff rule` or `ruff config`
  Ruff-->>Sys: markdown text
  Sys-->>App: markdown
  App->>App: RuleInspector.fetch_and_display(markdown)
  App-->>User: render docs in TUI
Loading

Sequence diagram for in-TUI interactions between tree, table, inspector, and system helpers

sequenceDiagram
  participant Tree as ConfigTree
  participant Table as CategoryTable
  participant Insp as RuleInspector
  participant App as RuffSyncApp
  participant Sys as system helpers
  participant Ruff as ruff binary

  Tree->>App: NodeSelected(data="__rules__")
  App->>App: _display_effective_rules()
  App->>Sys: get_all_ruff_rules()
  Sys->>Ruff: ruff rule --all --output-format json
  Ruff-->>Sys: JSON rules
  Sys-->>App: list of rules
  App->>Sys: compute_effective_rules(all_rules, config)
  Sys-->>App: rules with status
  App->>Table: update_rules(effective_only)
  App->>Insp: update(effective rules explanation)

  Table->>App: RowSelected(rule row)
  App->>App: _inspect_rule(rule_code)
  App->>Sys: get_ruff_rule_markdown(rule_code)
  Sys->>Ruff: ruff rule CODE
  Ruff-->>Sys: markdown docs
  Sys-->>App: markdown
  App->>Insp: fetch_and_display(target=rule_code, is_rule=true, cached_content, rule_name, rule_status)

  Tree->>App: NodeSelected(config section)
  App->>Table: update_content(section data)
  App->>Insp: fetch_and_display(target=section_path, is_rule=false)
  App->>Sys: get_ruff_config_markdown(section_path)
  Sys->>Ruff: ruff config PATH
  Ruff-->>Sys: markdown docs
  Sys-->>App: markdown
  App->>Insp: fetch_and_display(target=section_path, is_rule=false)
Loading

Class diagram for Ruff TUI application, widgets, and screens

classDiagram
  class RuffSyncApp {
    +Arguments args
    +dict~str, Any~ config
    +list~dict~ all_rules
    +list~dict~ effective_rules
    +list~dict~ linters
    +compose() ComposeResult
    +on_mount() async
    +_prime_caches() async
    +_display_effective_rules() async
    +handle_node_selected(event)
    +handle_row_selected(event)
    +_inspect_rule(rule_code str)
    +_get_node_path(node) str
    +action_search() void
    +handle_omnibox_result(rule_code str|None) void
    +action_show_legend() void
    +action_copy_content() void
  }

  class ConfigTree {
    +populate(config dict~str, Any~, has_rules bool, linters list~dict~?, effective_rules list~dict~?) void
    +_is_linter_active(linter dict~str, Any~, effective_rules list~dict~) bool
    +_populate_linter_nodes(parent TreeNode, linters list~dict~, effective_rules list~dict~) void
    +_populate_node(parent TreeNode, data Any) void
    +_expand_if_fits() void
  }

  class CategoryTable {
    +on_mount() void
    +update_content(data Any) void
    +update_rules(rules list~dict~) void
  }

  class RuleInspector {
    +show_placeholder() async
    +show_context(path str, value Any) void
    +fetch_and_display(target str, is_rule bool, cached_content str|None, rule_name str|None, rule_status str|None) async
  }

  class OmniboxScreen {
    +list~dict~ all_rules
    +compose() ComposeResult
    +on_mount() void
    +handle_input_changed(event) void
    +handle_input_submitted() void
    +handle_option_selected(event) void
    +action_cancel() void
  }

  class LegendScreen {
    +compose() ComposeResult
  }

  class ThemeModule {
  }

  class SystemHelpers {
    +get_ruff_rule_markdown(rule_code str) async str|None
    +get_ruff_config_markdown(setting_path str) async str|None
    +get_all_ruff_rules() async list~dict~
    +get_ruff_linters() async list~dict~
    +compute_effective_rules(all_rules list~dict~, toml_config Mapping~str, Any~) list~dict~
  }

  RuffSyncApp --> ConfigTree : uses
  RuffSyncApp --> CategoryTable : uses
  RuffSyncApp --> RuleInspector : uses
  RuffSyncApp --> OmniboxScreen : opens modal
  RuffSyncApp --> LegendScreen : opens modal
  RuffSyncApp --> SystemHelpers : calls

  ConfigTree <|-- Tree
  CategoryTable <|-- DataTable
  RuleInspector <|-- Markdown
  OmniboxScreen <|-- ModalScreen
  LegendScreen <|-- ModalScreen

  ThemeModule ..> RuffSyncApp : provides themes
  SystemHelpers ..> RuffSyncApp : subprocess-based data
Loading

File-Level Changes

Change Details Files
Add asyncio-based system helpers for Ruff introspection and rule-status computation.
  • Refactor Ruff subprocess invocation into a shared async helper _run_ruff_command with improved logging and empty-output handling.
  • Add get_ruff_config_markdown, get_all_ruff_rules, and get_ruff_linters to fetch setting docs, all rules, and linter metadata via JSON output.
  • Introduce compute_effective_rules to derive Enabled/Ignored/Disabled status for each rule based on select/ignore and their extend-* variants with sensible defaults.
src/ruff_sync/system.py
tests/test_rule_logic.py
Introduce a Textual-based TUI application for browsing Ruff configuration, rules, and documentation.
  • Create RuffSyncApp that loads local Ruff config, builds a ConfigTree plus CategoryTable and RuleInspector, and wires event handlers between tree selections, table rows, and inspector updates.
  • Implement background workers to prefetch all rules/linters, compute effective rules, and populate an "Effective Rule Status" view with per-rule status and metadata-driven inspector headers.
  • Add a fuzzy-search OmniboxScreen modal, a LegendScreen modal, a rule-config-aware RuleInspector, and a ConfigTree/CategoryTable pair that understand linter grouping and rule-table vs key/value modes.
  • Provide custom TUI themes and theme bindings for the app, and register them during mount.
src/ruff_sync/tui/app.py
src/ruff_sync/tui/widgets.py
src/ruff_sync/tui/screens.py
src/ruff_sync/tui/themes.py
src/ruff_sync/tui/__init__.py
.agents/tui_design.md
.agents/tui_requirements.md
.agents/tui_rule_browsing_design.md
.agents/tui_rule_browsing.md
.agents/skills/textual/references/widgets.md
Wire the new TUI into the CLI, add a dedicated ruff-inspect entry point, and ensure graceful behavior when Textual is not installed.
  • Change _resolve_upstream to return early for the inspect command so it no longer errors on missing upstream config.
  • Replace the previous "not yet implemented" path in main() with launching RuffSyncApp(exec_args).run() when inspect is requested.
  • Add an inspect() helper that normalizes sys.argv and delegates to main() so ruff-inspect can be used directly.
  • Export inspect from the package and declare a ruff-inspect console script in pyproject.toml, bumping the project version accordingly.
src/ruff_sync/cli.py
src/ruff_sync/__init__.py
pyproject.toml
tests/conftest.py
tests/test_tui.py
tests/test_themes.py
Extend CI to validate behavior without optional TUI dependencies and add supporting docs about known issues.
  • Add a test-no-optional-deps GitHub Actions job that installs the base package (no extras) and runs a shell script to validate CLI commands and the failure path of inspect when Textual is missing.
  • Introduce tests/test_minimal_imports.sh to assert textual is absent, basic CLI subcommands work, and ruff-sync inspect fails with a clear guidance message about installing ruff-sync[tui].
  • Document a post-mortem and known issues around documentation versioning and mike/GitHub Pages behavior for future maintainers.
.github/workflows/ci.yaml
tests/test_minimal_imports.sh
.agents/skills/mike/SKILL.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In RuffSyncApp.on_mount you're catching a bare Exception and only showing a generic notification; consider at least logging the underlying exception (e.g. with LOGGER.exception) so configuration load failures are diagnosable.
  • The rule-code detection regex (^[A-Z]+[0-9]+$) is duplicated in both handle_node_selected and handle_row_selected; pulling this into a single compiled constant (and possibly tightening it to the exact expected rule format) would reduce drift and make the behavior easier to maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `RuffSyncApp.on_mount` you're catching a bare `Exception` and only showing a generic notification; consider at least logging the underlying exception (e.g. with `LOGGER.exception`) so configuration load failures are diagnosable.
- The rule-code detection regex (`^[A-Z]+[0-9]+$`) is duplicated in both `handle_node_selected` and `handle_row_selected`; pulling this into a single compiled constant (and possibly tightening it to the exact expected rule format) would reduce drift and make the behavior easier to maintain.

## Individual Comments

### Comment 1
<location path="src/ruff_sync/tui/widgets.py" line_range="74-83" />
<code_context>
+            self.add_row("Value", str(data))
+
+
+class RuleInspector(Markdown):
+    """A markdown widget for inspecting Ruff rules."""
+
+    @work(thread=True)
+    async def fetch_and_display(self, rule_code: str) -> None:
+        """Fetch and display the documentation for a rule.
+
+        Args:
+            rule_code: The Ruff rule code to fetch documentation for.
+        """
+        # Set a loading message
+        self.update(f"## Inspecting {rule_code}...\n\nFetching documentation from `ruff rule`...")
+
+        content = await get_ruff_rule_markdown(rule_code)
+
+        if content:
+            self.update(content)
+        else:
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid updating the widget directly from a threaded `@work` task and reconsider mixing `thread=True` with an async function.

Textual expects UI updates from worker threads to go via `call_from_thread` (or similar), so updating the widget directly from this worker is not thread-safe. Since `get_ruff_rule_markdown` is already async and I/O-bound, the extra thread is likely unnecessary. Either:

- make this a normal async coroutine (no `thread=True`), or
- make `fetch_and_display` synchronous with `@work(thread=True)` and use `call_from_thread` for UI updates.

Both options avoid subtle concurrency issues and better match Textual’s model.
</issue_to_address>

### Comment 2
<location path="tests/test_tui.py" line_range="33-34" />
<code_context>
+    assert app.config == {}
+
+
+@pytest.mark.asyncio
+async def test_ruff_sync_app_mount(mock_args: Arguments, tmp_path: pathlib.Path) -> None:
+    pyproject = tmp_path / "pyproject.toml"
+    pyproject.write_text(
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test for the failure path in `on_mount` when loading the Ruff config fails.

Since `on_mount` has explicit error handling, please add a test that patches `load_local_ruff_config` to raise, then asserts that `app.config` is `{}`, the `ConfigTree` is present but effectively empty, and (if supported by `run_test`) that an `error`-severity notification is emitted. This will verify the TUI behavior when the config file is missing or invalid.

Suggested implementation:

```python
    )

    app = RuffSyncApp(mock_args)


@pytest.mark.asyncio
async def test_ruff_sync_app_mount_load_config_failure(
    mock_args: Arguments,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """on_mount should handle a failure to load the Ruff config gracefully."""

    # Force load_local_ruff_config to fail to exercise the error-handling path.
    monkeypatch.setattr(
        "ruff_sync.tui.load_local_ruff_config",
        side_effect=RuntimeError("boom"),
    )

    app = RuffSyncApp(mock_args)

    # Run the app so that on_mount is invoked.
    async with app.run_test() as _pilot:
        # Let the app process startup/mount events.
        # Depending on how run_test is implemented, this may be unnecessary,
        # but it's harmless and keeps the test robust.
        await _pilot.pause()

    # When config loading fails, the app should keep the default (empty) config.
    assert app.config == {}

```

To fully implement the suggested test behavior, you will likely need to:

1. **Patch the correct symbol path** for `load_local_ruff_config`:
   - If `RuffSyncApp` lives in a different module, update the `monkeypatch.setattr` target accordingly, e.g.:
     - `"ruff_sync.app.load_local_ruff_config"` or
     - `"ruff_sync.tui.app.load_local_ruff_config"`,
     whichever matches where `RuffSyncApp` references it.

2. **Assert that the ConfigTree is present and effectively empty**:
   - If the app exposes the tree as a widget, add assertions inside the `async with app.run_test()` block, for example:
     ```python
     config_tree = app.query_one(ConfigTree)
     assert not list(config_tree.root.children)
     ```
   - Or, if the tree is stored as an attribute (e.g. `app.config_tree`), assert on its internal state:
     ```python
     assert hasattr(app, "config_tree")
     assert not app.config_tree.data  # or the appropriate “empty” check
     ```
   - Adjust these checks to match the actual API of your `ConfigTree` widget.

3. **Assert that an error-severity notification is emitted**, if `run_test` or the app exposes notifications:
   - For example, if `RuffSyncApp` stores notifications on `app.notifications`:
     ```python
     errors = [n for n in app.notifications if n.severity == "error"]
     assert errors
     ```
   - Or, if `pilot` has helpers to inspect notifications/logs, use those instead.
   - Update the test body in the `async with app.run_test()` block to perform these assertions right after `await _pilot.pause()`.

These additional adjustments will depend on the exact interfaces of `RuffSyncApp`, `ConfigTree`, and your notification system.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Kilo59 added 4 commits April 4, 2026 16:24
- Centralize Ruff rule detection regex in `src/ruff_sync/tui/constants.py` to remove duplication.
- Fix thread-safety in `RuleInspector` by using non-threaded async workers for UI updates.
- Improve diagnosability of configuration failures by adding `LOGGER.exception` to `on_mount`.
- Add `test_ruff_sync_app_mount_load_config_failure` to verify robust error handling.
- Resolve multiple mypy type errors and ruff linting issues in TUI modules and tests.
- Fix missing imports in `tests/conftest.py` that were blocking test execution.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 4, 2026

Codecov Report

❌ Patch coverage is 88.42105% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.96%. Comparing base (47ce3ac) to head (5469149).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/ruff_sync/system.py 83.82% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #151      +/-   ##
==========================================
+ Coverage   93.68%   93.96%   +0.28%     
==========================================
  Files           9        9              
  Lines        1266     1343      +77     
==========================================
+ Hits         1186     1262      +76     
- Misses         80       81       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Kilo59 added 21 commits April 4, 2026 16:51
Expand the rule inspector to fill the available space and hide the data table when a rule is selected.
- Implement logic to compute rule status (Enabled/Ignored/Disabled) based on user configuration and Ruff defaults.
- Add an Omnibox modal for global fuzzy-searching of all Ruff rules.
- Add an "Effective Active Rules" node to the configuration tree to view the status and fix availability of every rule in a formatted table.
- Cache rule metadata in the background to improve TUI responsiveness.
Use row-level color coding to indicate rule status in tables and add metadata headers with status icons to the rule inspector. Centralizes rule inspection logic and adds safety checks for Ruff CLI output parsing.
@Kilo59
Copy link
Copy Markdown
Owner Author

Kilo59 commented Apr 5, 2026

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The new inspect CLI path unconditionally imports RuffSyncApp, so in environments without the optional textual dependency this will raise a ModuleNotFoundError rather than failing with the friendly message expected by tests/test_minimal_imports.sh; consider wrapping the import in a try/except and emitting the intended guidance about installing ruff-sync[tui] before returning a non‑zero exit code.
  • There is a ("t", "change_theme", "Theme Picker") binding defined in RuffSyncApp.BINDINGS but no corresponding action_change_theme implementation, which will result in a runtime error when the key is pressed; either implement the theme‑change handler or remove the binding.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `inspect` CLI path unconditionally imports `RuffSyncApp`, so in environments without the optional `textual` dependency this will raise a `ModuleNotFoundError` rather than failing with the friendly message expected by `tests/test_minimal_imports.sh`; consider wrapping the import in a `try/except` and emitting the intended guidance about installing `ruff-sync[tui]` before returning a non‑zero exit code.
- There is a `("t", "change_theme", "Theme Picker")` binding defined in `RuffSyncApp.BINDINGS` but no corresponding `action_change_theme` implementation, which will result in a runtime error when the key is pressed; either implement the theme‑change handler or remove the binding.

## Individual Comments

### Comment 1
<location path="src/ruff_sync/system.py" line_range="89-98" />
<code_context>
+    return []
+
+
+def compute_effective_rules(
+    all_rules: list[dict[str, Any]], toml_config: Mapping[str, Any]
+) -> list[dict[str, Any]]:
+    """Determine the status (Enabled, Ignored, Disabled) for each rule.
+
+    Args:
+        all_rules: The list of all supported rules.
+        toml_config: The local configuration dictionary.
+
+    Returns:
+        The list of rules enriched with a 'status' key.
+    """
+    lint = toml_config.get("tool", {}).get("ruff", {}).get("lint", {})
+
+    select = set(lint.get("select", [])) | set(lint.get("extend-select", []))
</code_context>
<issue_to_address>
**issue (bug_risk):** The expected shape of `toml_config` here may be inconsistent with how the TUI passes configuration around.

This function assumes a top-level `tool.ruff.lint` table. However, `ConfigTree.populate`’s docstring describes its `config` argument as an already “unwrapped” Ruff config, and that same object is passed to `compute_effective_rules` from `RuffSyncApp._prime_caches`. If `load_local_ruff_config` returns the unwrapped `[tool.ruff]` table, `toml_config.get("tool", {})` will always be `{}`, so `lint` is `{}` and the computed rule statuses won’t match the real config. Please confirm the return shape of `load_local_ruff_config` and either adjust this function to accept the unwrapped config (e.g., `toml_config["lint"]`) or re-wrap it before calling, so the tree and rules view stay consistent.
</issue_to_address>

### Comment 2
<location path="tests/test_tui.py" line_range="211-207" />
<code_context>
+        mock_run.assert_called_once()
+
+
+def test_cli_ruff_inspect_entry_point(
+    cli_run: CLIRunner, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+    """Test that the 'ruff-inspect' entry point attempts to run the app."""
+    # Mock load_local_ruff_config where it's used in RuffSyncApp.on_mount
+    monkeypatch.setattr("ruff_sync.tui.app.load_local_ruff_config", lambda _: {})
+
+    with patch("ruff_sync.tui.app.RuffSyncApp.run", return_value=0) as mock_run:
+        # Program name 'ruff-inspect' should trigger the inspect logic
+        exit_code, _out, _err = cli_run(["--to", str(tmp_path)], entry_point="ruff-inspect")
+        assert exit_code == 0
+        mock_run.assert_called_once()
+
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a negative/argument-variation test for the `ruff-inspect` entry point.

The current test only covers the happy path where `ruff-inspect --to ...` runs the TUI. Since the entry point also rewrites args (inserting the `inspect` subcommand when the first arg isn’t a known command, and passing through `-h/--help/--version`), a small parametrized test that exercises:

- `entry_point="ruff-inspect"` with no args
- `entry_point="ruff-inspect"` with `['--help']`
- `entry_point="ruff-inspect"` with another subcommand, e.g. `['check', '--to', ...]`

would validate the argument rewriting and guard against regressions in this glue logic.
</issue_to_address>

### Comment 3
<location path="tests/test_themes.py" line_range="25-34" />
<code_context>
+        assert str(theme.background).upper() == "#FAFAFA"
+
+
+@pytest.mark.asyncio
+async def test_theme_picker_binding(mock_args: Arguments) -> None:
+    # Create mock config so on_mount doesn't fail
+    pyproject = mock_args.to / "pyproject.toml"
</code_context>
<issue_to_address>
**suggestion (testing):** Extend theme tests to exercise the `change_theme` action behavior, not just the binding’s presence.

Currently this only checks that `t` is bound to `change_theme`. Please also simulate the keypress via the pilot (e.g. `await pilot.press("t")`, possibly multiple times) and assert that `app.theme` actually cycles through the expected registered theme names. That way the test will catch regressions in `action_change_theme`, not just changes to the key binding.

Suggested implementation:

```python
        # Check default theme

@pytest.mark.asyncio
async def test_theme_picker_binding(mock_args: Arguments) -> None:
    # Create mock config so on_mount doesn't fail
    pyproject = mock_args.to / "pyproject.toml"
    pyproject.write_text("[tool.ruff]\n", encoding="utf-8")

    app = RuffSyncApp(mock_args)

    async with app.run_test() as pilot:
        # Capture the initial theme name and its index in the registered themes.
        initial_theme_name = app.theme.name
        assert initial_theme_name in app.available_themes

        initial_index = app.available_themes.index(initial_theme_name)

        # Press "t" once and ensure we advanced to the next theme.
        await pilot.press("t")
        first_theme_name = app.theme.name
        first_index = app.available_themes.index(first_theme_name)

        assert first_index == (initial_index + 1) % len(app.available_themes)

        # Press "t" again and ensure we advanced one more step.
        await pilot.press("t")
        second_theme_name = app.theme.name
        second_index = app.available_themes.index(second_theme_name)

        assert second_index == (first_index + 1) % len(app.available_themes)

        # Now press "t" enough times to wrap all the way back to the initial theme.
        remaining_presses = (len(app.available_themes) - second_index + initial_index) % len(app.available_themes)
        for _ in range(remaining_presses):
            await pilot.press("t")

        assert app.theme.name == initial_theme_name

```

If `app.theme` does not expose a `.name` attribute, replace `app.theme.name` with the appropriate way of getting the current theme's name (for example, `str(app.theme)` or a property your app defines). If your key binding is not literally `"t"` or is defined via a Textual `Binding` collection that behaves differently, adjust the pressed key in `pilot.press("t")` to match the actual binding.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Implement theme cycling action and verify with tests.
- Improve dependency checking and lazy loading for the TUI to provide better error messages.
- Fix rule computation to support both wrapped and unwrapped Ruff configurations.
- Enhance CLI argument handling and rewriting for the ruff-inspect entry point.
@Kilo59 Kilo59 self-assigned this Apr 5, 2026
- Use a custom `DependencyError` instead of direct `sys.exit` calls in utility functions.
- Centralize error logging and exit code handling in the CLI entry point.
- Improve testability of dependency checks through dependency injection.
@Kilo59 Kilo59 enabled auto-merge (squash) April 5, 2026 03:02
@Kilo59 Kilo59 disabled auto-merge April 5, 2026 03:02
@Kilo59 Kilo59 enabled auto-merge (squash) April 5, 2026 03:05
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

╒═══════════════╤══════════════╤═════════════════╤════════════╤═════════════════╕
│ File          │ Cyclomatic   │ Maintainabili   │ Unique     │ Lines of Code   │
│               │ Complexity   │ ty Index        │ Operands   │                 │
╞═══════════════╪══════════════╪═════════════════╪════════════╪═════════════════╡
│ tests/conftes │ 8 -> 12      │ 82.0156 ->      │ 0 -> 2     │ 64 -> 123       │
│ t.py          │              │ 72.7049         │            │                 │
├───────────────┼──────────────┼─────────────────┼────────────┼─────────────────┤
│ tests/test_ru │ - -> 18      │ - -> 62.35551   │ - -> 1     │ - -> 99         │
│ le_logic.py   │              │ 314408544       │            │                 │
├───────────────┼──────────────┼─────────────────┼────────────┼─────────────────┤
│ tests/test_th │ - -> 2       │ - -> 76.16248   │ - -> 4     │ - -> 44         │
│ emes.py       │              │ 53382119        │            │                 │
├───────────────┼──────────────┼─────────────────┼────────────┼─────────────────┤
│ tests/test_de │ 4 -> 5       │ 72.488 ->       │ 2 -> 2     │ 35 -> 59        │
│ pendencies.py │              │ 72.8286         │            │                 │
├───────────────┼──────────────┼─────────────────┼────────────┼─────────────────┤
│ tests/test_tu │ - -> 33      │ - -> 46.22914   │ - -> 7     │ - -> 299        │
│ i.py          │              │ 715641225       │            │                 │
╘═══════════════╧══════════════╧═════════════════╧════════════╧═════════════════╛

@Kilo59 Kilo59 disabled auto-merge April 5, 2026 03:06
@Kilo59 Kilo59 changed the title Inspector TUI feat(tui): implement comprehensive local configuration inspector and TUI Apr 5, 2026
@Kilo59 Kilo59 enabled auto-merge (squash) April 5, 2026 03:07
@Kilo59 Kilo59 merged commit f92fbb8 into main Apr 5, 2026
16 of 17 checks passed
@Kilo59 Kilo59 deleted the tui branch April 5, 2026 03:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant