feat(#84): IMP-84 u1~u3 silent automation policy enforcement (FramePanel reject confirm + slide_base provisional badge/outline + IMP-30 visual assertions inverted)
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 21s

- u1 FramePanel.tsx: extract `applyFrameSelection(candidate, onFrameSelect)`
  pure helper; collapse `handleFrameSelect` to direct onFrameSelect for every
  V4 label; drop `window.confirm` reject popup (IMP-47B u11 regression noise
  per `feedback_auto_pipeline_first`). New vitest pin `imp84_framepanel_reject_silent.test.ts`
  covers helper invocation across all 4 V4 labels + source-presence pins.
- u2 templates/phase_z2/slide_base.html: delete `.zone--provisional` CSS,
  `.zone__needs-adaptation-badge` CSS, the zone--provisional class fragment
  in the zone div, and the badge `<span>` render at the provisional zone.
  Preserve `data-provisional="1"` attribute as silent telemetry. New pytest
  `tests/phase_z2/test_imp84_provisional_silent_render.py` pins the silent
  contract independently of the IMP-30 first-render file.
- u3 tests/test_phase_z2_imp30_first_render.py: invert the three IMP-30 u5
  positive provisional-visual assertions to IMP-84 silent-contract negatives
  (no class, no badge, no CSS selectors); preserve positive `data-provisional`
  telemetry assertions. Docstrings updated to IMP-84 silent contract.

Out of scope (Round #4 + #92 contract): Home.tsx `toast.error(aiReviewMsg)`
call line, designAgentApi.ts `api_error_kinds`/`api_error_kind` schema and
operational-only formatter, FramePanel reject badge/tooltip read-only labels
(L102/L147/L156), and backend `zone.provisional` flag emission.

Stage 4 PASS: u1 vitest 10/10, u2 pytest 5/5, u3 pytest 29/29 (incl. 3
IMP-84 inverted assertions: `test_imp84_provisional_zone_silent_no_class_no_badge`,
`test_imp84_provisional_badge_never_rendered_in_mixed_zones`,
`test_imp84_slide_base_css_strips_provisional_visual_selectors`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 14:15:02 +09:00
parent f0d4494409
commit b9747c2f4a
5 changed files with 449 additions and 89 deletions

View File

@@ -676,12 +676,20 @@ def test_u5_zone_without_provisional_key_treated_as_non_provisional():
# ─── u5 case 2 : provisional zone renders class + badge + data attr ───
def test_u5_provisional_zone_renders_class_and_badge():
"""Opt-in path. zones[i].provisional=True must:
1. Append `zone--provisional` class to the zone div.
2. Set `data-provisional="1"` data attribute (for downstream selectors).
3. Render a `<span class="zone__needs-adaptation-badge">` element with
the literal text "needs adaptation" (aria-label included for a11y).
def test_imp84_provisional_zone_silent_no_class_no_badge():
"""IMP-84 silent-automation inversion of the prior IMP-30 u5 contract.
Under the silent contract, zones[i].provisional=True must:
1. NOT append `zone--provisional` class to the zone div (no user-visible
outline / striped wash).
2. Still set `data-provisional="1"` data attribute as silent telemetry
for downstream selectors / inspection.
3. NOT render any `<span class="zone__needs-adaptation-badge">` element
and NOT surface the literal text "needs adaptation" or its
aria-label (no user-facing badge).
Scope: assertions target the zone div body. The CSS <style> block must
likewise not carry the removed visual selectors — that surface is pinned
in `test_imp84_slide_base_css_strips_provisional_visual_selectors` below.
"""
zones = [
{
@@ -694,21 +702,26 @@ def test_u5_provisional_zone_renders_class_and_badge():
}
]
html = _render_slide_base(zones)
# zone--provisional class must appear on the zone div for position=single.
assert "zone--provisional" in html
# data-provisional="1" attribute must be present.
assert 'data-provisional="1"' in html
# Badge element with the required label text.
assert 'class="zone__needs-adaptation-badge"' in html
assert "needs adaptation" in html
assert 'aria-label="needs user or AI adaptation"' in html
zone_divs = _all_zone_div_openings(html)
assert len(zone_divs) == 1
# No zone--provisional class on the zone div (visual removed).
assert "zone--provisional" not in zone_divs[0]
# data-provisional="1" attribute still present as silent telemetry.
assert 'data-provisional="1"' in zone_divs[0]
# No badge <span> element and no badge label text anywhere in the body.
assert _all_badge_spans(html) == []
assert "needs adaptation" not in html
assert 'aria-label="needs user or AI adaptation"' not in html
def test_u5_provisional_badge_appears_inside_provisional_zone_only():
"""Mixed-zone slide: one provisional zone + one normal zone. The badge
+ class must appear ONLY in the provisional zone, not bleed into the
normal one (CSS-level isolation should already prevent this, but the
template must not emit the badge for both)."""
def test_imp84_provisional_badge_never_rendered_in_mixed_zones():
"""IMP-84 silent-automation inversion of the prior IMP-30 u5 mixed-zone
contract. Mixed-zone slide: one provisional zone + one normal zone. The
silent contract requires that NO badge span and NO `zone--provisional`
class be emitted on either zone div. The provisional zone is identifiable
only through the silent `data-provisional="1"` telemetry attribute, which
must be scoped to the provisional zone alone (no bleed onto the normal
zone)."""
zones = [
{
"position": "top",
@@ -735,21 +748,21 @@ def test_u5_provisional_badge_appears_inside_provisional_zone_only():
html = _render_slide_base(
zones, layout_preset="vertical-2", layout_css=layout_css
)
# Exactly one badge span element should be present in the rendered body
# (CSS selector in <style> excluded by the helper).
assert len(_all_badge_spans(html)) == 1
# zone--provisional must appear on exactly one zone div (CSS selector
# in <style> excluded by the helper).
# No badge <span> element should be rendered anywhere in the body
# (silent-automation policy).
assert _all_badge_spans(html) == []
# No zone div should carry the zone--provisional class (visual removed).
zone_divs = _all_zone_div_openings(html)
assert len(zone_divs) == 2
provisional_zone_divs = [d for d in zone_divs if "zone--provisional" in d]
assert len(provisional_zone_divs) == 1
# The provisional class must be associated with the bottom zone.
assert all("zone--provisional" not in d for d in zone_divs)
# data-provisional="1" telemetry must be present on the bottom (provisional)
# zone only — never on the top (non-provisional) zone.
bottom_zone_open = _zone_div_for_position(html, "bottom")
assert "zone--provisional" in bottom_zone_open
assert "zone__needs-adaptation-badge" in bottom_zone_open
# The top zone must NOT carry the provisional class.
assert 'data-provisional="1"' in bottom_zone_open
assert "zone--provisional" not in bottom_zone_open
assert "zone__needs-adaptation-badge" not in bottom_zone_open
top_zone_open = _zone_div_for_position(html, "top")
assert 'data-provisional="1"' not in top_zone_open
assert "zone--provisional" not in top_zone_open
assert "zone__needs-adaptation-badge" not in top_zone_open
@@ -779,15 +792,19 @@ def test_u5_zones_data_provisional_field_defaults_false_in_template():
assert _all_badge_spans(html) == []
def test_u5_slide_base_css_carries_provisional_marker_styles():
"""The provisional visual contract (dashed outline + striped wash + badge)
is defined in slide_base.html <style>. Pin that the relevant CSS class
selectors exist in the rendered HTML so a refactor that removes them
breaks this test rather than silently rendering an unstyled badge.
def test_imp84_slide_base_css_strips_provisional_visual_selectors():
"""IMP-84 silent-automation inversion of the prior IMP-30 u5 CSS-presence
contract. The provisional visual treatment (dashed outline + striped wash
+ badge) was deleted from `slide_base.html <style>` by IMP-84 u2. Pin
that the CSS class selectors `.zone--provisional` and
`.zone__needs-adaptation-badge` no longer appear in the rendered HTML —
a refactor that re-introduces them must break this test rather than
silently re-surfacing the removed visual signal.
This is a class-selector existence check; it does not validate the
specific color / dash pattern, which is a design decision intentionally
left malleable (e.g., palette swap for a different theme)."""
Scope: the assertion targets the entire rendered HTML (style block plus
body). Since the body badge span is also gone (covered separately above),
any occurrence of these strings in the rendered output would only come
from a regressed style block."""
zones = [
{
"position": "single",
@@ -799,9 +816,9 @@ def test_u5_slide_base_css_carries_provisional_marker_styles():
}
]
html = _render_slide_base(zones)
# Style block must define .zone--provisional and the badge selector.
assert ".zone--provisional" in html
assert ".zone__needs-adaptation-badge" in html
# Style block must NOT define .zone--provisional or the badge selector.
assert ".zone--provisional" not in html
assert ".zone__needs-adaptation-badge" not in html
# ════════════════════════════════════════════════════════════════════════