Skip to content

refactor(mcp): extract BOM table-merge helpers into shared bom_table module#857

Merged
dougborg merged 1 commit into
mainfrom
refactor/bom-table-helpers-850
May 27, 2026
Merged

refactor(mcp): extract BOM table-merge helpers into shared bom_table module#857
dougborg merged 1 commit into
mainfrom
refactor/bom-table-helpers-850

Conversation

@dougborg
Copy link
Copy Markdown
Owner

Closes #850.

Summary

What moved

  • _merge_bom_rows_for_modify_card and its row-shapers (_bom_existing_row_from_snapshot, _bom_synth_orphan_row, _bom_add_action_to_row, _apply_bom_update_to_row, _apply_bom_delete_to_row, _bom_change_lookup)
  • _prepare_bom_table_rows
  • _format_bom_quantity / _format_bom_quantity_diff
  • _BOM_ROW_STATUS_VARIANTS + _BomMergedRowKind
  • _summarize_apply_outcome and _derive_status_label (dict version)

What stayed in prefab_ui.py

  • build_bom_modify_ui + all rendering glue (public render contract).
  • _bom_row_summary — purely presentational summary line.
  • _BOM_MODIFY_COLUMNS + _resolve_bom_table_rows — UI-bound wiring.

Judgment calls

  • _summarize_apply_outcome: Cross-entity-generic, but only directly called by foundation/bom and prefab_ui.build_po_modify_ui. Housed in bom_table.py because that breaks the bom→prefab_ui underscore import without adding a second new module. If a third caller appears later, hoist to a dedicated shared-modify-helpers module — pragmatic call now to keep the diff focused.
  • _derive_status_label (dict version): Used by the BOM merge AND by _action_to_row in prefab_ui. Moved with the BOM merge so the new module is self-contained; prefab_ui imports it back. The ActionResult-typed version in _modification.py is untouched.

Test plan

  • uv run poe agent-check and uv run poe test (3647 passed, 7 skipped) both clean.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 27, 2026 02:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the BOM modify-card “table merge” logic out of the UI layer (prefab_ui.py) into a new shared, non-UI domain module (foundation/bom_table.py) so both the rendering path and the tool-implementation path can reuse the same row-shaping helpers without foundation/bom.py importing UI internals.

Changes:

  • Added katana_mcp.tools.foundation.bom_table to host BOM table-merge + status/outcome bucketing helpers with no Prefab/UI imports.
  • Updated prefab_ui.py to import the extracted helpers and removed the in-file implementations.
  • Updated foundation/bom.py (apply path) and test_prefab_ui.py to reference the new module (including logger name for the warning test).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
katana_mcp_server/src/katana_mcp/tools/foundation/bom_table.py New shared, non-UI module containing BOM row-merge + formatting + status/outcome helpers.
katana_mcp_server/src/katana_mcp/tools/prefab_ui.py Removes BOM merge helpers from UI module; imports them from foundation.bom_table and keeps only rendering glue.
katana_mcp_server/src/katana_mcp/tools/foundation/bom.py Switches apply-time helper imports from prefab_ui to foundation.bom_table to eliminate UI/impl coupling.
katana_mcp_server/tests/test_prefab_ui.py Updates caplog expectations to the new logger namespace emitted by the extracted module.

Copy link
Copy Markdown
Owner Author

@dougborg dougborg left a comment

Choose a reason for hiding this comment

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

Review Summary

Clean structural refactor that achieves its stated goal: BOM table-merge math is now in a self-contained domain module with no Prefab imports, and foundation/bom.py no longer reaches into prefab_ui for underscore helpers. All 258 tests survive unchanged. The behavior-fidelity check passes — every moved function is a verbatim copy. No generated files were touched.

Three issues found, none blocking.


BLOCKING (0)

None.


SUGGESTIONS (2)

1. katana_mcp_server/tests/test_prefab_ui.py:2264 and :3369-3372 — stale test imports of moved symbols still go through prefab_ui

These two call-sites import _summarize_apply_outcome, _merge_bom_rows_for_modify_card, _prepare_bom_table_rows from katana_mcp.tools.prefab_ui rather than from their new canonical home katana_mcp.tools.foundation.bom_table. They pass today only because prefab_ui.py incidentally re-exports all four symbols at module level (lines 111-115). If those top-level imports are ever narrowed to local/function scope (a routine refactoring step), the two test sites break silently without any indication the test was testing the wrong module.

The top-level test import at line 18 (from katana_mcp.tools.prefab_ui import _merge_bom_rows_for_modify_card) is used throughout TestMergeBomRowsForModifyCard — that class should be importing from bom_table given that the function now lives there.

Fix: update lines 2264 and 3369-3372 to from katana_mcp.tools.foundation.bom_table import .... Also update the top-level module import at line 18 to remove _merge_bom_rows_for_modify_card from the prefab_ui import block and import it from bom_table instead (either at the top of the file or at the test-class level).

2. katana_mcp_server/src/katana_mcp/tools/foundation/bom_table.py:82_BomMergedRowKind is defined but never referenced as a type annotation

The Literal type alias is moved over faithfully from prefab_ui.py but is unused within the new module: the row-shaper functions never annotate their kind writes with it (they use plain string literals). If ruff's F841 / PYI018 passes over this it may produce noise in future lint runs, and a reader scanning the module will reasonably wonder if it was meant to annotate something.

Fix: either annotate the "kind" dict values (e.g. declare a helper return type that includes kind: _BomMergedRowKind), or remove the dead alias and add it back when it is actually enforcing a type boundary.


NITPICKS (1)

3. katana_mcp_server/src/katana_mcp/tools/foundation/bom_table.py:110-142_summarize_apply_outcome is cross-entity generic but housed in a BOM-specific module

The PR notes call this out and make a pragmatic deferred-work call. That reasoning is sound for now. To prevent the deferred work from disappearing, recommend filing a GitHub issue (or adding a # TODO(#NNN) comment inline) so the "hoist to shared-modify-helpers if a third caller appears" intent is tracked rather than staying only in the PR description.


What Looks Good

  • The new module has zero Prefab imports — verified with grep -E 'from prefab_ui|PrefabApp|Badge\b'. Goal achieved.
  • All seven moved functions are verbatim copies of their originals — no dropped None-guards, no missing or defaults, no behavioral drift on any path.
  • The caplog logger-name assertion in TestMergeBomRowsForModifyCard.test_unknown_operation_is_logged_and_dropped was correctly updated to katana_mcp.tools.foundation.bom_table (both the at_level guard and the rec.name filter).
  • The two _derive_status_label variants remain distinct: the dict-typed one is in bom_table, the ActionResult-typed one stays in _modification.py — no collapse.
  • foundation/bom.py lazy-import correctly re-targets bom_table with no residual prefab_ui underscore imports.
  • Test count is unchanged (258 → 258); no tests were silently deleted in the rename.

…module (closes #850)

#833 left ``foundation/bom.py`` importing private helpers from ``prefab_ui``
to precompute ``extras["applied_plan_rows"]`` on the apply path — Copilot
flagged this as UI/impl coupling. The "BOM actions → merged row list /
outcome summary" computation is domain logic, not rendering.

Move the table-merge helpers into ``katana_mcp/tools/foundation/bom_table.py``
(non-UI, no Prefab imports). The renderer (``prefab_ui.build_bom_modify_ui``)
and the tool-impl (``foundation/bom._modify_product_bom_impl``) both import
from the new module — no more underscore-helper crossing the UI/impl
boundary.

Moved:

- ``_merge_bom_rows_for_modify_card`` + row-shaping helpers
  (``_bom_existing_row_from_snapshot``, ``_bom_synth_orphan_row``,
  ``_bom_add_action_to_row``, ``_apply_bom_update_to_row``,
  ``_apply_bom_delete_to_row``, ``_bom_change_lookup``)
- ``_prepare_bom_table_rows``
- ``_format_bom_quantity`` / ``_format_bom_quantity_diff``
- ``_BOM_ROW_STATUS_VARIANTS`` + ``_BomMergedRowKind``
- ``_summarize_apply_outcome`` (cross-entity-generic, but only directly
  called by ``foundation/bom`` and ``prefab_ui.build_po_modify_ui``;
  housing it here lets ``bom.py`` import it without a UI dep — small
  pragmatic call, hoist to a dedicated shared-modify module later if a
  third caller appears)
- ``_derive_status_label`` (dict version; the ``ActionResult`` version
  in ``_modification.py`` is untouched)

Kept in ``prefab_ui.py``:

- ``build_bom_modify_ui`` and all rendering glue (public render contract)
- ``_bom_row_summary`` (compose the "+N added, ~M updated, -K deleted"
  display line — purely presentational)
- ``_BOM_MODIFY_COLUMNS`` + ``_resolve_bom_table_rows`` (UI-bound
  configuration / wiring)

Test impact: ``test_unknown_operation_is_logged_and_dropped`` updates the
caplog logger name from ``katana_mcp.tools.prefab_ui`` to
``katana_mcp.tools.foundation.bom_table`` (the logger followed the code).
All other tests pass unchanged — they import the moved symbols from
``prefab_ui`` which re-exports them transparently via its own import.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@dougborg dougborg force-pushed the refactor/bom-table-helpers-850 branch from 731afb0 to d3f9957 Compare May 27, 2026 03:04
@dougborg
Copy link
Copy Markdown
Owner Author

Addressed all three review findings in d3f9957:

Suggestion 1 — stale test imports of moved symbols (test_prefab_ui.py:18, 2264, 3369-3372): all four call-sites now import _merge_bom_rows_for_modify_card, _prepare_bom_table_rows, and _summarize_apply_outcome directly from katana_mcp.tools.foundation.bom_table. The top-level import block at line 18 was split so _merge_bom_rows_for_modify_card (used by TestMergeBomRowsForModifyCard) reads from its canonical home; the two local imports inside test bodies were retargeted likewise. Tests no longer depend on the prefab_ui incidental re-export.

Suggestion 2 — unused _BomMergedRowKind Literal alias (bom_table.py:82): the alias is now exercised as a type annotation on the kind local in each of the five row-shaper helpers (_bom_existing_row_from_snapshot, _bom_synth_orphan_row, _bom_add_action_to_row, _apply_bom_update_to_row, _apply_bom_delete_to_row). Pyright now enforces that any future kind write outside the existing|added|updated|deleted vocabulary is a type error — the alias carries the documentary intent the review flagged AND enforces the boundary.

Nitpick 3 — _summarize_apply_outcome cross-entity in BOM-named module: filed #859 to track the eventual hoist into a dedicated foundation/modify_outcome.py when a third caller appears (SO/MO modify cards or a generic modify-result renderer). Inline TODO(#859) comment added to the _summarize_apply_outcome docstring so the deferred work isn't only captured in this PR's description.

Verification: uv run poe agent-check and uv run poe test (3647 passed, 7 skipped) both clean.

@dougborg dougborg merged commit 4ac739a into main May 27, 2026
14 checks passed
@dougborg dougborg deleted the refactor/bom-table-helpers-850 branch May 27, 2026 03:19
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.

Refactor: extract BOM table-merge helpers into shared (non-UI) module

2 participants