# Rules: Cta Box

**Tier:** 2
**Source of truth:** recipe.md (locked 2026-08-04 a452 Phase 5)

## 1. Schema stability

- `props` shape is locked per the recipe (recipe.md §B Inputs).
- Adding a new optional prop is allowed if it is non-breaking (has a default that matches the legacy behaviour).
- Removing or renaming a prop is a breaking change and requires a new `module.json` version.

## 2. Rendering

- The block renders to the host region's `target` element. Do not create new top-level DOM nodes outside `target`.
- CSS is scoped via a class prefix `.fvcms-cta-box-*`. No global CSS.
- Do not set inline styles unless required (e.g. dynamic sizes from user input).

## 3. State

- Local state lives in the block's closure (no globals).
- Shared state goes through the freshvibe-cms runtime store. Use the block's `instanceId` as the key prefix.
- Never mutate props after the block is mounted. If a prop must change, the host region should unmount and re-mount.

## 4. Events

- All events are emitted through the runtime's pub-sub, not via direct DOM events.
- Event names are namespaced as `fvcms:cta-box:<event>`.
- No direct coupling to other blocks — communicate through the store or via events.

## 5. Error handling

- A failed render must NOT crash the host region. Fall back to a placeholder.
- Async operations (if any) must timeout after 5s and surface a non-blocking error.

## 6. Accessibility

- All interactive elements must have an `aria-label` or associated text.
- Keyboard navigation must work (Tab + Enter or Space to activate).
- Focus rings must remain visible (do not use `outline: none` without an alternative).

## 7. Tier 2 license posture

- **Tier 2**: see module-meta.json for license posture.

## 8. Constitutional fragments

This block participates in the following FreshVibe Way v8 fragments:

- fvcms.pact.section.1
- fvcms.pact.section.3
- fvcms.pact.anti-drift.001
- fvcms.pact.invariants.001
