一键导入
redesign-validation-tripwires
Test tripwires for mortgage analysis UI redesign — preserves logic integrity through visual changes
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Test tripwires for mortgage analysis UI redesign — preserves logic integrity through visual changes
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Pilot a community Spec Kit extension without pretending it is trusted, official, or a hard gate
Lock what an analytical financial redesign may beautify, but never hide, reorder, or distort
Choose Spec Kit extensions that reinforce real repo gaps without duplicating existing orchestration
Run Spec Kit through the repo-pinned official CLI and keep the official git extension as the branch workflow source of truth
Freeze cross-cutting contracts before parallel implementation starts
Design financial result flows that explain cost, quality, and provenance before persuasion
| name | redesign-validation-tripwires |
| description | Test tripwires for mortgage analysis UI redesign — preserves logic integrity through visual changes |
| domain | quality, design-qa |
| confidence | high |
| source | earned — mortgage comparator MVP redesign validation research (2026-04-24) |
Design-heavy UI changes (visual refresh, layout reorganization) can accidentally break financial logic without code changes. This pattern locks the behavioral contracts before visual redesign work begins, ensuring style changes don't degrade honesty or explainability.
Redesign can silently break these without changing a single line of domain code:
| Fragility | Bad Redesign Move | Why It Breaks | Impact |
|---|---|---|---|
| Bonus trap ranking | Highlight installment before total cost | User sees lower installment, ignores ranking | Wrong mortgage choice |
| Scenario count | Hard-code 3-column table for bonus variant | Shows empty bonus column when user omits bonus | Confusion, data dishonesty |
| Data quality visibility | Collapse conditional_estimate banner | User reads false certainty; acts on degraded data | Bad decision on uncertain inputs |
| Affordability prerequisite | Move affordability form above comparison | Form shows target scenario without calculation | Calculates against wrong scenario |
| Fallback honesty | Remove stage badge when API unavailable | User thinks fallback result is real API | False confidence in local preview |
| Retention metadata | Hide session expiry on mobile view | User doesn't know when data expires | Privacy violation, surprise purge |
Write these tests against the current implementation as a green baseline. They will guide the redesign and catch regressions.
1. Scenario Count Tripwire (ScenarioComparisonTable.test.tsx)
it('renders exactly 2 rows when user omits bonus variant', () => {
const comparison = noBonusComparisonFixture.comparison;
render(<ScenarioComparisonTable comparison={comparison} />);
// Hard contract: no third row invented
expect(screen.getAllByRole('row')).toHaveLength(3); // thead + 2 tbody
expect(screen.queryByText('Oferta con bonificaciones')).not.toBeInTheDocument();
expect(screen.getByText('No hay escenario con bonificaciones porque la oferta no lo definía.')).toBeInTheDocument();
});
it('renders exactly 3 rows when user includes bonus variant', () => {
const comparison = bonusTrapComparisonFixture.comparison;
render(<ScenarioComparisonTable comparison={comparison} />);
expect(screen.getAllByRole('row')).toHaveLength(4); // thead + 3 tbody
expect(screen.getByText('Oferta sin bonificaciones')).toBeInTheDocument();
expect(screen.getByText('Oferta con bonificaciones')).toBeInTheDocument();
});
2. Ranking Order Tripwire
it('ranks by total real cost, not installment', () => {
const comparison = bonusTrapComparisonFixture.comparison;
render(<ScenarioComparisonTable comparison={comparison} />);
const rows = screen.getAllByRole('row').slice(1); // skip thead
// First row = alternative_without_bonus (lowest total cost)
expect(rows[0]).toHaveTextContent('Oferta sin bonificaciones');
expect(rows[0]).toHaveTextContent('€23,000.00'); // total cost
// Second row = alternative_with_bonus (higher total, lower installment trap)
expect(rows[1]).toHaveTextContent('Oferta con bonificaciones');
expect(rows[1]).toHaveTextContent('€23,850.00'); // higher total
expect(rows[1]).toHaveTextContent('€890.00'); // lower installment (the trap)
});
3. Data Quality Degradation Tripwire (DataQualityBanner.test.tsx)
it('shows conditional_estimate banner when data quality degraded', () => {
const missingData = ['Cuota actual declarada'];
const assumptions = ['Cuota estimada por diferencia'];
render(
<DataQualityBanner
status="conditional_estimate"
missingData={missingData}
assumptions={assumptions}
/>
);
expect(screen.getByRole('heading', { name: 'Estimación condicionada' })).toBeInTheDocument();
expect(screen.getByText('Cuota actual declarada')).toBeInTheDocument();
expect(screen.getByText('Cuota estimada por diferencia')).toBeInTheDocument();
});
it('hides data quality banner when status is complete', () => {
const { container } = render(
<DataQualityBanner status="complete" missingData={[]} assumptions={[]} />
);
expect(container.querySelector('.quality-banner--complete')).toBeInTheDocument();
expect(container.querySelector('ul')).not.toBeInTheDocument(); // no lists
});
4. Affordability Prerequisite Tripwire (AffordabilityPanel.test.tsx)
it('returns UnavailablePanel when affordability is undefined', () => {
render(
<AffordabilityPanel affordability={undefined} stage={{ label: 'Asequibilidad', detail: 'Pendiente' }} />
);
expect(screen.getByText('Asequibilidad pendiente')).toBeInTheDocument();
expect(screen.queryByText('Ratio de endeudamiento')).not.toBeInTheDocument();
});
it('only calculates for recommendation target scenario', () => {
const affordability = affordabilityFixture.affordability;
render(<AffordabilityPanel affordability={affordability} stage={...} />);
// UI must show which scenario was evaluated, not allow user override
expect(screen.getByText('Oferta sin bonificaciones')).toBeInTheDocument();
expect(screen.getByText('31.08%')).toBeInTheDocument(); // ratio
});
5. Fallback Honesty Tripwire (E2E in mortgage-comparison.spec.ts)
test('marks fallback stages with local_preview source, not api', async ({ page }) => {
// Mock API returning 501 Not Implemented
await page.route('**/api/v1/analyses/*/compare', async (route) => {
await route.fulfill({ status: 501, body: JSON.stringify({ code: 'feature_not_implemented' }) });
});
await page.goto('/');
await page.getByRole('button', { name: 'Ver coste real' }).click();
// Stage badge must show "local_preview", not "api"
const stageBadge = page.locator('.stage-badge--local_preview');
await expect(stageBadge).toBeVisible();
// Notice must explain fallback
const notice = page.getByText('Comparación aún no soportada en el backend');
await expect(notice).toBeVisible();
});
6. Retention Visibility Tripwire (Component + E2E)
it('shows expiry and purge dates in OwnershipPanel', () => {
const analysis = createdAnalysisFixture.analysis;
render(<OwnershipPanel analysis={analysis} experience={...} />);
expect(screen.getByText('Caduca')).toBeInTheDocument();
expect(screen.getByText('2026-04-24 01:00')).toBeInTheDocument();
expect(screen.getByText('Purga después')).toBeInTheDocument();
expect(screen.getByText('2026-04-24 01:15')).toBeInTheDocument();
});
Each visual commit must pass:
# Component tests (unit)
corepack pnpm --filter @batalla-ias/web test -- --ui
# E2E tests (integration)
corepack pnpm --filter @batalla-ias/web exec playwright test --ui
# Lighthouse (accessibility)
corepack pnpm --filter @batalla-ias/web exec playwright test --grep="accessibility"
Before merge approval, Hicks runs:
# Full repo validation
corepack pnpm build
corepack pnpm --filter @batalla-ias/domain test
corepack pnpm --filter @batalla-ias/api test
corepack pnpm --filter @batalla-ias/web test
corepack pnpm --filter @batalla-ias/web exec playwright test mortgage-comparison.spec.ts
# Verify tripwire tests all green
corepack pnpm --filter @batalla-ias/web test -- --reporter=verbose
✅ Good Redesign:
❌ Bad Redesign (Caught by Tripwires):
This pattern pairs with .squad/skills/integration-finish-gate/SKILL.md:
Any UI redesign touching:
Should adopt this tripwire pattern before visual work begins.