Skip to content

Commit 224162e

Browse files
dougborgclaude
andcommitted
feat(mcp): per-entity PO modify card with field-level diff overlay — #722
Foundation + first migration for the #721 modify-card umbrella. Replaces the today's ``ActionResult``-shaped modify card (which showed "Update Header / 2 field(s) changed / Operation #1, Target #2641831" — internal model abstractions the user can't action on) with a per-entity entry that mirrors the create-card layout and decorates the changing fields with ``before → after``. What lands: - ``FieldChangeView`` + ``_index_changes_by_field`` + ``_render_field_diff_line`` — renderer-facing field-diff projection, shared by every future modify card. Flattens ``ActionResult.changes`` across all actions into a single field-name keyed map so the entity view's render code can do simple ``changes.get("expected_arrival_date")`` lookups. - ``_render_po_entity_view(entity, *, changes=None)`` — the PO entity-view body, extracted from the old inlined ``build_po_create_ui`` Tier 2/3 content. Called by BOTH the create card (``changes=None``) and the new modify card. Single source of truth for "what fields show in a PO card"; the create-modify pair can't drift over time. - ``build_po_modify_ui`` — the new entrypoint. Handles ``modify_purchase_order`` / ``delete_purchase_order`` / ``correct_purchase_order`` (verb from ``confirm_tool``). Renders the entity view with the diff overlay; the leading ``✗`` glyph + inline Muted error line appears only on failed actions (the agreed hybrid status approach — card-level header Badge carries the all/most-case status, per-field decoration only when it carries information). - Dispatch in ``_modification.to_tool_result`` — switches on ``response.entity_type``. PO routes to the new entrypoint; other entities fall through to the legacy ``build_modification_preview_ui`` / ``build_modification_result_ui`` pair until their child PRs (#723 SO, #724 MO, #725 stock-transfer, #726 item) ship. The legacy builders delete once every entity migrates. Tests: - ``TestFieldDiffIndex`` — pins ``_index_changes_by_field`` projection (multi-action flattening, added/unchanged kinds, failed-action error propagation). - ``TestBuildPOModifyUI`` — smoke + diff-decoration + supplier composite diff + failed-action ``✗`` glyph + delete verb + partial-failure header badge + ``state.result`` seeding on applied path. - ``TestPOEntityViewSharedBetweenCreateAndModify`` — pins the dual-call contract so a future refactor of ``_render_po_entity_view`` can't silently drift the create card away from modify. What this PR does NOT touch (deferred to follow-ups): - Line-item / additional-cost adds/removes (the ``add_row`` / ``delete_row`` / ``add_additional_cost`` operations) — the entity view doesn't yet render those rows. The current PO create card doesn't render them either, so this is consistent with #728's create-card scope, not a regression. Adding the nested-rows section is its own follow-up (probably the next PO modify PR or rolled into the SO migration where the entity-view component for line-items first matters). - Other entities (SO, MO, stock_transfer, item) — child PRs #723-#726. Closes #722 Refs #721 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent aa79747 commit 224162e

4 files changed

Lines changed: 827 additions & 41 deletions

File tree

katana_mcp_server/src/katana_mcp/tools/_modification.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -554,25 +554,40 @@ def to_tool_result(
554554
) -> ToolResult:
555555
"""Build a :class:`ToolResult` with a Prefab UI from a ModificationResponse.
556556
557-
Preview branch: emits ``build_modification_preview_ui`` with the
558-
direct-apply rail (Confirm fires ``tools/call`` directly + iframe
559-
pushes the structured result back via ``ui/update-model-context``).
560-
Non-preview branch: emits ``build_modification_result_ui`` summarizing
561-
each action's terminal status.
562-
563-
``confirm_request`` is the original Pydantic request (its ``preview``
564-
field flips to ``False`` when the iframe re-issues for apply);
565-
``confirm_tool`` is the registered MCP tool name to re-call. The
566-
preview branch wires both into the Confirm-button CallTool; the
567-
result branch uses ``confirm_tool`` to derive the title verb so a
568-
delete reads "Product Delete" instead of "Product Modification".
557+
Dispatches by ``response.entity_type`` to a per-entity builder that
558+
handles BOTH preview and applied states (one entrypoint per entity,
559+
matching the create-card pattern in #728 and the modify-card design
560+
in #721). Each per-entity builder shares its entity-view renderer
561+
with its create-card sibling (e.g. ``_render_po_entity_view``) —
562+
create cards call it with no diff overlay; modify cards pass the
563+
per-field diff lookup built from ``response.actions[*].changes``.
564+
565+
Entities not yet migrated fall back to ``build_modification_preview_ui``
566+
/ ``build_modification_result_ui`` — the legacy single-entrypoint pair
567+
today's modify/delete/correct tools render through. Removed once
568+
every #721 child PR has shipped.
569569
"""
570570
from katana_mcp.tools.prefab_ui import (
571571
build_modification_preview_ui,
572572
build_modification_result_ui,
573+
build_po_modify_ui,
573574
)
574575

575576
response_dict = response.model_dump()
577+
578+
# Per-entity dispatch — PO migrated in #722; remaining entities
579+
# (SO, MO, stock_transfer, item) fall through to the legacy
580+
# builders until their respective child PRs land.
581+
if response.entity_type == "purchase_order":
582+
ui = build_po_modify_ui(
583+
response_dict,
584+
confirm_request=confirm_request,
585+
confirm_tool=confirm_tool,
586+
)
587+
return make_tool_result(response, ui=ui)
588+
589+
# Legacy path — preserves today's behavior for not-yet-migrated
590+
# entity types so #722 can land without cross-entity test churn.
576591
if response.is_preview:
577592
ui = build_modification_preview_ui(
578593
response_dict,

0 commit comments

Comments
 (0)