---
title: SAIF.Presentation.Web.Controls.V2 business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-08-17
library: SAIF.Presentation.Web.Controls.V2
reference_type: package
---

# SAIF.Presentation.Web.Controls.V2 business rules

This page captures business rules enforced by this library.

## BR-V2-001: Banner defaults require non-design mode

| Field | Value |
| --- | --- |
| **Statement** | Banner defaults are established only when the control is not in design mode. |
| **Trigger** | Evaluated on every `OnInit` call for `BannerControl`. |
| **Enforcement** | `Banner.vb` `OnInit` checks `IsDesign` before calling `SetBannerControlDefaults`. |
| **Outcome** | In the Visual Studio designer, defaults are skipped so the design surface never depends on a live `HttpContext`. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-002: Design-time banner renders a static placeholder

| Field | Value |
| --- | --- |
| **Statement** | With no HTTP context (design time) the banner renders a static placeholder `CustomerHeader` div. |
| **Trigger** | Evaluated at the start of `Render` whenever `Context Is Nothing`. |
| **Enforcement** | `Banner.vb` `Render` short-circuits to a static placeholder markup block. |
| **Outcome** | The designer shows a representative banner shape without requiring a running request. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-003: Missing banner definition suppresses all rendering

| Field | Value |
| --- | --- |
| **Statement** | If the banner definition file cannot be located, the banner renders nothing at all. |
| **Trigger** | Evaluated when the resolved banner-definition path is empty at render time. |
| **Enforcement** | `Banner.vb` `Render` and `SetBannerControlDefaults` clear the path and exit before writing output. |
| **Outcome** | Hosting applications missing `/Resources/SiteBanner.xml` see no banner rather than an error. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-004: Banner markup is XML-driven

| Field | Value |
| --- | --- |
| **Statement** | Banner markup is data-driven from `SiteBanner.xml`, not hard-coded. |
| **Trigger** | Evaluated every time the banner renders successfully. |
| **Enforcement** | `Banner.vb` `Render` loads the resolved path into an `XmlDataDocument` before emitting any markup. |
| **Outcome** | Banner content (links, labels, phone number) can change per hosting application without a code change. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-005: Print banner renders when a print node exists

| Field | Value |
| --- | --- |
| **Statement** | The print banner is rendered whenever a `banner_body/print` node exists. |
| **Trigger** | Evaluated after the XML document loads, when selecting `business_online_banner/banner_body/print`. |
| **Enforcement** | `Banner.vb` `Render` selects the node and writes the print banner if present. |
| **Outcome** | Print-specific banner markup is emitted only for XML files that define it. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-006: Title placeholder substitution

| Field | Value |
| --- | --- |
| **Statement** | The `[TITLE]` placeholder is replaced with `PrintPageTitle` in both print and display banners. |
| **Trigger** | Evaluated while rendering either the print or display banner body. |
| **Enforcement** | `Banner.vb` `Render` and `RenderPrintBanner` perform a string substitution before writing output. |
| **Outcome** | The page-supplied title appears in the rendered banner text without editing the XML. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-007: Display banner suppressed when internal visibility is off

| Field | Value |
| --- | --- |
| **Statement** | The display banner is suppressed when `ShowInternalDisplayBanner` is false. |
| **Trigger** | Evaluated immediately before rendering the display banner body. |
| **Enforcement** | `Banner.vb` `Render` exits before emitting the display banner when the property returns `False`. |
| **Outcome** | Internal clients without explicit opt-in never see the display banner. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-008: Links placeholder marks the dynamic link insertion point

| Field | Value |
| --- | --- |
| **Statement** | The `[LINKS]` placeholder marks the insertion point for dynamically rendered links, using `*` as the split delimiter. |
| **Trigger** | Evaluated while rendering the display banner body. |
| **Enforcement** | `Banner.vb` `Render` substitutes `[LINKS]` with `*` then splits the body string on `*`. |
| **Outcome** | The banner body is broken into ordered fragments that the rendered links are interleaved into. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-009: SAIF.com link renders first

| Field | Value |
| --- | --- |
| **Statement** | The `SAIF.com` link is rendered first among the banner links. |
| **Trigger** | Evaluated while writing the sequence of banner links after the body split. |
| **Enforcement** | `Banner.vb` `Render` writes the `SAIF.com` anchor before any other link fragment. |
| **Outcome** | The rendered banner has a fixed, predictable link ordering starting with the `SAIF.com` link. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-010: Logout link requires opt-in and non-login application

| Field | Value |
| --- | --- |
| **Statement** | The logout link is rendered only when `DisplayBannerLogout` is true and the user is not on the login application. |
| **Trigger** | Evaluated while writing the banner link sequence. |
| **Enforcement** | `Banner.vb` `Render` checks both `DisplayBannerLogout` and the internal `LoggingIn` flag before writing the logout link. |
| **Outcome** | Consumers hosting the login page never see a logout link, even if `DisplayBannerLogout` is set. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-011: Login application shows a plain feedback link

| Field | Value |
| --- | --- |
| **Statement** | Inside the login application, feedback is a plain XML-defined link rather than the pop-up feedback form. |
| **Trigger** | Evaluated while writing the feedback link when `LoggingIn` is true. |
| **Enforcement** | `Banner.vb` `Render` writes the XML-defined feedback link text/URL directly instead of the pop-up variant. |
| **Outcome** | The login page's feedback link behaves like a normal hyperlink instead of opening a pop-up window. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-012: Feedback opens a fixed-size pop-up outside the login application

| Field | Value |
| --- | --- |
| **Statement** | Outside the login application, feedback opens `/BusinessOnline/BusinessOnlineFeedback.aspx` in a fixed 641x505 pop-up window. |
| **Trigger** | Evaluated while writing the feedback link when `LoggingIn` is false. |
| **Enforcement** | `Banner.vb` and `BannerControlV2.vb` render a `window.open` call with the fixed 641x505 dimensions. |
| **Outcome** | Feedback is captured in a fixed-size pop-up window rather than navigating the current page. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-013: Banner elements are separated by the XML-defined separator

| Field | Value |
| --- | --- |
| **Statement** | Rendered banner elements are separated by the XML-defined `separator` label; no separator follows the final phone label. |
| **Trigger** | Evaluated between each rendered banner element (link/label). |
| **Enforcement** | `Banner.vb` `RenderSeparator` and `Render` write the separator between elements and omit it after the last one. |
| **Outcome** | The rendered banner reads as a single line of pipe- or dash-separated elements without a trailing separator. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-014: Links/labels render only with required XML attributes

| Field | Value |
| --- | --- |
| **Statement** | A link or label renders only when the XML supplies its required attributes (text, and a URL for anchors). |
| **Trigger** | Evaluated for each banner link/label as it is written. |
| **Enforcement** | `Banner.vb` `RenderBannerText` checks for the required attributes before emitting the element. |
| **Outcome** | Incomplete XML entries are silently skipped rather than rendering broken markup. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-015: Insider status is the negation of IsClientExternal

| Field | Value |
| --- | --- |
| **Statement** | "Insider" status is the negation of `AppGlobal.EnvironmentService.IsClientExternal`. |
| **Trigger** | Evaluated during `SetBannerControlDefaults`, once per control instance per request. |
| **Enforcement** | `Banner.vb` and `BannerControlV2.vb` read `AppGlobal.EnvironmentService.IsClientExternal` and invert it. |
| **Outcome** | The banner's internal/external visibility decisions are all keyed off this single environment-service flag. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-016: Links default to external-only visibility

| Field | Value |
| --- | --- |
| **Statement** | Banner, logout and feedback links default to visible for external clients and hidden for internal clients. |
| **Trigger** | Evaluated when banner defaults are established, before any explicit property is set by the host page. |
| **Enforcement** | `Banner.vb` `SetBannerControlDefaults` assigns the default visibility flags from the insider/external determination. |
| **Outcome** | A hosting page that does not override the defaults gets the external-visible behavior automatically. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-017: Banner definition resolved from a fixed site path

| Field | Value |
| --- | --- |
| **Statement** | The banner definition is resolved from `/RESOURCES/SITEBANNER.XML` under the application root. |
| **Trigger** | Evaluated during `SetBannerControlDefaults` when an `HttpContext` exists. |
| **Enforcement** | `Banner.vb` `SetBannerControlDefaults` maps the fixed virtual path to a physical path via `MapPath`. |
| **Outcome** | Every hosting application must supply `SiteBanner.xml` at that exact path for the banner to render. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-018: Login application detected by template directory name

| Field | Value |
| --- | --- |
| **Statement** | The application is treated as the login application when the template source directory's last segment is `ONLINELOGIN`. |
| **Trigger** | Evaluated during `SetBannerControlDefaults` while deriving the current application name. |
| **Enforcement** | `Banner.vb` `SetBannerControlDefaults` upper-cases the last path segment and compares it to `ONLINELOGIN`. |
| **Outcome** | The `LoggingIn` flag, which changes feedback/logout rendering, is set purely from folder naming convention. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-019: External clients always see the internal banner as shown

| Field | Value |
| --- | --- |
| **Statement** | External clients always see the internal display banner setting as `True`. |
| **Trigger** | Evaluated whenever `ShowInternalDisplayBanner` is read. |
| **Enforcement** | `Banner.vb` and `BannerControlV2.vb` return `True` unconditionally for external clients. |
| **Outcome** | External clients cannot have the display banner hidden regardless of `ShowInternalBanner`. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-020: Internal clients require explicit banner opt-in

| Field | Value |
| --- | --- |
| **Statement** | For internal clients the banner is hidden unless `ShowInternalBanner` has been explicitly set. |
| **Trigger** | Evaluated whenever `ShowInternalDisplayBanner` is read for an internal client. |
| **Enforcement** | `Banner.vb` and `BannerControlV2.vb` return the `ShowInternalBanner` `ViewState` entry, defaulting to `False`. |
| **Outcome** | Internal users see no banner by default; the hosting page must opt in explicitly. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/Banner.vb` |

---

## BR-V2-021: ReadOnly persisted per-control in ViewState

| Field | Value |
| --- | --- |
| **Statement** | `ReadOnly` state is persisted in `ViewState` under a key prefixed by the control's `UniqueID`. |
| **Trigger** | Evaluated on every postback when the control reads its `ReadOnly` property. |
| **Enforcement** | `SAIFCheckBox.vb` and `SAIFDropDownList.vb` key the `ViewState` entry with `UniqueID & "ReadOnly"`. |
| **Outcome** | Multiple instances of the same control type on a page maintain independent read-only state. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFCheckBox.vb` |

---

## BR-V2-022: Non-read-only controls render as their stock base class

| Field | Value |
| --- | --- |
| **Statement** | When not read-only, the control renders exactly as its stock ASP.NET base class. |
| **Trigger** | Evaluated at the start of `Render` for `SAIFCheckBox`, `SAIFCheckBoxList`, `SAIFRadioButton`, `SAIFRadioButtonList`. |
| **Enforcement** | Each control calls `MyBase.Render(writer)` when `ReadOnly` is `False`. |
| **Outcome** | No behavioral change occurs for pages that never enable read-only mode. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFCheckBox.vb` |

---

## BR-V2-023: Read-only mode keeps the real input off-screen

| Field | Value |
| --- | --- |
| **Statement** | In read-only mode the real input is still rendered - disabled, off-screen and untabbable - so view state and posted values survive. |
| **Trigger** | Evaluated in `Render` when `ReadOnly` is `True`. |
| **Enforcement** | `SAIFCheckBox.vb` `Render` positions the disabled input at `left: -10000px; top: -10000px` with `TabIndex = -1`. |
| **Outcome** | The posted value and view state remain intact across postbacks even though the field is visually replaced. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFCheckBox.vb` |

---

## BR-V2-024: Read-only checkboxes use static images

| Field | Value |
| --- | --- |
| **Statement** | Read-only checkboxes display static images rather than a disabled input. |
| **Trigger** | Evaluated in `Render` when `ReadOnly` is `True` for `SAIFCheckBox` or `SAIFCheckBoxList`. |
| **Enforcement** | The control emits `/Resources/Images/Cb_checked.bmp` or `cb_unchecked.bmp` based on the checked state. |
| **Outcome** | Read-only checkboxes are visually consistent regardless of browser default checkbox rendering. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFCheckBox.vb` |

---

## BR-V2-025: Read-only radio buttons use static images

| Field | Value |
| --- | --- |
| **Statement** | Read-only radio buttons display `rb_checked.gif` / `rb_unchecked.gif`. |
| **Trigger** | Evaluated in `Render` when `ReadOnly` is `True` for `SAIFRadioButton` or `SAIFRadioButtonList`. |
| **Enforcement** | The control emits the corresponding static GIF from `/Resources/Images/` based on the checked state. |
| **Outcome** | Read-only radio buttons render consistently across browsers, matching the checkbox pattern. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFRadioButton.vb` |

---

## BR-V2-026: Read-only label placement follows TextAlign

| Field | Value |
| --- | --- |
| **Statement** | Label placement in read-only mode follows the control's `TextAlign` setting. |
| **Trigger** | Evaluated while composing the read-only markup in `Render`/`RenderLabel`. |
| **Enforcement** | `SAIFCheckBox.vb` `Render`/`RenderLabel` positions the label to the left or right of the image per `TextAlign`. |
| **Outcome** | Read-only rendering visually matches the interactive control's configured label position. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFCheckBox.vb` |

---

## BR-V2-027: Drop-down can bind directly to an enum

| Field | Value |
| --- | --- |
| **Statement** | A drop-down can be bound directly to an enumeration through `EnumDatasource`. |
| **Trigger** | Evaluated in `DataBind` when the `EnumDatasource` property is set. |
| **Enforcement** | `SAIFDropDownList.vb` `DataBind` calls `System.Enum.GetNames` on the supplied enum type. |
| **Outcome** | The drop-down's items come directly from an enum's member names without a separate lookup list. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-028: Underscore-to-space mapping for enum names

| Field | Value |
| --- | --- |
| **Statement** | When `MapUnderscore` is true, underscores in enum member names are displayed as spaces. |
| **Trigger** | Evaluated in `DataBind` (list building) and `SelectedItemAsEnum` (reverse lookup). |
| **Enforcement** | `SAIFDropDownList.vb` replaces `_` with a space before assigning the base `DataSource`, and reverses it for lookups. |
| **Outcome** | Enum member names with underscores (e.g. `NOT_APPLICABLE`) display as readable phrases. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-029: Enum binding supplements normal binding

| Field | Value |
| --- | --- |
| **Statement** | Enum binding supplements rather than replaces normal data binding - the base `DataBind` always runs. |
| **Trigger** | Evaluated at the end of `DataBind` regardless of whether `EnumDatasource` was set. |
| **Enforcement** | `SAIFDropDownList.vb` `DataBind` always calls the base `DataBind` after any enum-source assignment. |
| **Outcome** | Standard `DataSource`/`DataBind` usage continues to work alongside the enum-binding convenience feature. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-030: Read-only drop-down renders as a styled textbox

| Field | Value |
| --- | --- |
| **Statement** | A read-only drop-down renders as a read-only textbox styled `disabledbackcolor`. |
| **Trigger** | Evaluated in `Render` when `ReadOnly` is `True`. |
| **Enforcement** | `SAIFDropDownList.vb` `Render` creates a substitute `TextBox` with the `disabledbackcolor` CSS class. |
| **Outcome** | Read-only drop-downs present as plain disabled-looking text instead of an interactive list. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-031: Read-only textbox is narrower than the drop-down

| Field | Value |
| --- | --- |
| **Statement** | The read-only textbox is 5 pixels narrower than the drop-down unless the width is under 5 units. |
| **Trigger** | Evaluated in `Render` while sizing the substitute textbox. |
| **Enforcement** | `SAIFDropDownList.vb` `Render` subtracts 5 pixels from the control's width when width is 5 or greater. |
| **Outcome** | The substitute textbox visually aligns with the original drop-down's border, accounting for the missing arrow. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-032: ValueText fallback when selection is blank

| Field | Value |
| --- | --- |
| **Statement** | When the selected value is blank but `ValueText` is populated, `ValueText` is displayed. |
| **Trigger** | Evaluated in `Render` while determining the read-only textbox's displayed text. |
| **Enforcement** | `SAIFDropDownList.vb` `Render` checks the selected item's value and falls back to `ValueText`. |
| **Outcome** | A drop-down with no matching selected value can still show a caller-supplied display string. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-033: Inline styles copied to the substitute textbox

| Field | Value |
| --- | --- |
| **Statement** | Inline styles are copied from the drop-down to the substituted textbox. |
| **Trigger** | Evaluated in `Render` while constructing the read-only textbox. |
| **Enforcement** | `SAIFDropDownList.vb` `Render` copies the drop-down's `Style` collection onto the new `TextBox`. |
| **Outcome** | Custom inline styling applied to the drop-down is preserved in read-only mode. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-034: Invisible drop-down renders nothing

| Field | Value |
| --- | --- |
| **Statement** | An invisible drop-down renders nothing. |
| **Trigger** | Evaluated at the start of `Render` when `Visible` is `False`. |
| **Enforcement** | `SAIFDropDownList.vb` `Render` exits before writing any markup. |
| **Outcome** | No empty or placeholder markup is emitted for a hidden control. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-035: Base render failure triggers one retry with selection cleared

| Field | Value |
| --- | --- |
| **Statement** | If base rendering fails (typically a multiple-selection error) the selection is cleared and rendering is retried once. |
| **Trigger** | Evaluated in `Render` when the call to the base `Render` throws. |
| **Enforcement** | `SAIFDropDownList.vb` `Render` catches the exception, clears the selected index, and calls the base render again. |
| **Outcome** | A stale or invalid selected value does not crash the page; the drop-down instead renders with no selection. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-036: SelectedItemAsEnum maps text and enum bidirectionally

| Field | Value |
| --- | --- |
| **Statement** | `SelectedItemAsEnum` maps between displayed item text and the enum value in both directions. |
| **Trigger** | Evaluated whenever the host page reads or sets `SelectedItemAsEnum`. |
| **Enforcement** | `SAIFDropDownList.vb` implements the property get/set applying the same underscore mapping used in `DataBind`. |
| **Outcome** | Host pages can work with strongly typed enum values instead of raw list item strings. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb` |

---

## BR-V2-037: Message.js included once per page

| Field | Value |
| --- | --- |
| **Statement** | `Message.js` is included once per page regardless of the number of message controls. |
| **Trigger** | Evaluated in `Init` for every `SAIFMessage` instance on the page. |
| **Enforcement** | `SAIFMessage.vb` `RegisterClientScripts` checks `IsClientScriptIncludeRegistered` before adding the include. |
| **Outcome** | Pages with several message controls emit exactly one `Message.js` script tag. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-038: Message control is always image plus label

| Field | Value |
| --- | --- |
| **Statement** | The message control is always composed of an image plus a label styled `saifmsg`. |
| **Trigger** | Evaluated in `CreateChildControls` for every `SAIFMessage` instance. |
| **Enforcement** | `SAIFMessage.vb` `CreateChildControls` adds `SAIFMessageImage` and `lblSAIFMessage` (CSS class `saifmsg`). |
| **Outcome** | Every message control has a consistent icon-plus-text structure regardless of message content. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-039: Message image excluded from bulk enable/disable

| Field | Value |
| --- | --- |
| **Statement** | The message image is excluded from bulk enable/disable by the control manager. |
| **Trigger** | Evaluated in `CreateChildControls` when the message image control is created. |
| **Enforcement** | `SAIFMessage.vb` `CreateChildControls` calls `Utilities.SetControlManagerEnableable` with `enableable = False` on the image. |
| **Outcome** | Bulk form-disable operations (see `Utilities.mdlUtilities`) never hide or disable the status icon. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-040: Design mode always shows the complete icon

| Field | Value |
| --- | --- |
| **Statement** | In design mode the message control always shows the "complete" icon. |
| **Trigger** | Evaluated in `RenderControl` when the control is in design mode. |
| **Enforcement** | `SAIFMessage.vb` `RenderControl` forces the complete icon regardless of the configured `Text`. |
| **Outcome** | The designer surface always shows a representative, successful-looking message state. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-041: Empty message hides and flags IsImageSet=false

| Field | Value |
| --- | --- |
| **Statement** | An empty message renders hidden and flags `IsImageSet=false` so the client script can set the icon. |
| **Trigger** | Evaluated in `RenderControl` at run time when `Text` is empty. |
| **Enforcement** | `SAIFMessage.vb` `RenderControl` sets a hidden display style and the `IsImageSet=false` attribute. |
| **Outcome** | Client script can detect an unset message and apply an icon dynamically without a server round-trip. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-042: Error keyword selects the error icon

| Field | Value |
| --- | --- |
| **Statement** | Message text containing `error:` (case-insensitive) displays the error icon. |
| **Trigger** | Evaluated in `SetImage` while classifying the message text. |
| **Enforcement** | `SAIFMessage.vb` `SetImage` performs a case-insensitive search for `error:` and selects `MsgError.png`. |
| **Outcome** | Callers can trigger the error icon simply by prefixing their message text with `error:`. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-043: Warning keyword or unmatched text selects the warning icon

| Field | Value |
| --- | --- |
| **Statement** | Message text containing `warning:` displays the warning icon; text matching none of the keywords also defaults to the warning icon. |
| **Trigger** | Evaluated in `SetImage` after the error/complete keyword checks fail to match. |
| **Enforcement** | `SAIFMessage.vb` `SetImage` falls back to `MsgWarning.png` as the default classification. |
| **Outcome** | Any message text without a recognized keyword is treated, by default, as a warning. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-044: Complete keyword or fixed phrase selects the complete icon

| Field | Value |
| --- | --- |
| **Statement** | Message text containing `complete:`, or exactly `changes saved.`, displays the complete icon. |
| **Trigger** | Evaluated in `SetImage` while classifying the message text. |
| **Enforcement** | `SAIFMessage.vb` `SetImage` matches either the `complete:` keyword or the literal phrase `changes saved.`. |
| **Outcome** | Both a generic success prefix and one specific legacy phrase resolve to the same success icon. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-045: Complete keyword stripped from displayed text

| Field | Value |
| --- | --- |
| **Statement** | All `complete:` occurrences are stripped from the displayed message text. |
| **Trigger** | Evaluated in `SetImage` after icon selection. |
| **Enforcement** | `SAIFMessage.vb` `SetImage` removes every `complete:` substring from the text before it is rendered. |
| **Outcome** | The keyword used to trigger the icon never appears in the visible message to the user. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-046: Explicit DisplayImage skips keyword classification

| Field | Value |
| --- | --- |
| **Statement** | Keyword-based icon selection is skipped when `DisplayImage` has been explicitly overridden. |
| **Trigger** | Evaluated at the start of `SetImage`. |
| **Enforcement** | `SAIFMessage.vb` `SetImage` checks whether `DisplayImage` differs from its default before running keyword matching. |
| **Outcome** | Callers can force a specific icon without their message text being reinterpreted by the keyword rules. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb` |

---

## BR-V2-047: Every SAIFPanel carries a fixed CSS class and script

| Field | Value |
| --- | --- |
| **Statement** | Every `SAIFPanel` instance carries CSS class `SAIFPanel` and pulls in `Panel.js`. |
| **Trigger** | Evaluated on construction of every `SAIFPanel` instance. |
| **Enforcement** | `SAIFPanel.vb` sets the `CssClass` and registers `/Resources/Scripts/Controls/Panel.js` in the constructor. |
| **Outcome** | All panels on a page share consistent styling and the same client-side collapse/expand behavior. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFPanel.vb` |

---

## BR-V2-048: Panel structure is fixed

| Field | Value |
| --- | --- |
| **Statement** | Panel structure is fixed: header (title + image holder), state hidden field, content area. |
| **Trigger** | Evaluated in `CreateChildControls` for every `SAIFPanel` instance. |
| **Enforcement** | `SAIFPanel.vb` `CreateChildControls` builds the `Header`/`Title`/`ImageHolder`, `HiddenField`, and `Content` panel. |
| **Outcome** | Every panel has an identical, predictable DOM shape regardless of its content. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFPanel.vb` |

---

## BR-V2-049: Collapsed state hides content via inline style

| Field | Value |
| --- | --- |
| **Statement** | A collapsed panel hides its content with `display: none;`; an expanded panel uses `display: block;`. |
| **Trigger** | Evaluated whenever the panel's state is applied (initial render and postback). |
| **Enforcement** | `SAIFPanel.vb` sets the content panel's inline `style` attribute based on `PanelState`. |
| **Outcome** | The panel's visible/hidden state is driven entirely by a single inline CSS property. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFPanel.vb` |

---

## BR-V2-050: Collapse/expand URLs and state control exposed to client

| Field | Value |
| --- | --- |
| **Statement** | Collapse/expand image URLs and the state control's client ID are exposed as attributes for the client script. |
| **Trigger** | Evaluated in `CreateChildControls` while composing the panel markup. |
| **Enforcement** | `SAIFPanel.vb` publishes `CollapseImgUrl` (`/Resources/Images/Up.png`), `ExpandImgUrl` (`/Resources/Images/Down.png`), and `StateControl`. |
| **Outcome** | `Panel.js` can toggle the panel and swap the collapse/expand icon purely from these published attributes. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFPanel.vb` |

---

## BR-V2-051: Panel state survives postback

| Field | Value |
| --- | --- |
| **Statement** | Panel state survives postback through the hidden state field. |
| **Trigger** | Evaluated on every postback that involves a `SAIFPanel`. |
| **Enforcement** | `SAIFPanel.vb` reads the hidden field value and re-applies the corresponding display style. |
| **Outcome** | A user's collapse/expand choice is preserved across form submissions without server-side session state. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFPanel.vb` |

---

## BR-V2-052: Conditions grid always titled and panel-hosted

| Field | Value |
| --- | --- |
| **Statement** | The conditions grid is always titled `Medical Conditions` and hosted inside a `SAIFPanel`. |
| **Trigger** | Evaluated in `CreateChildControls` the first time the control has no children. |
| **Enforcement** | `ConditionsGrid.vb` `CreateChildControls` creates `pnlConditions` with the fixed title `Medical Conditions`. |
| **Outcome** | Every consumer gets the same panel title and structure without configuring it themselves. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb` |

---

## BR-V2-053: Grid columns are never auto-generated

| Field | Value |
| --- | --- |
| **Statement** | The inner grid never auto-generates columns; its column set is built explicitly. |
| **Trigger** | Evaluated in `CreateChildControls` while configuring the inner `GridView` (`dgConditions`). |
| **Enforcement** | `ConditionsGrid.vb` `CreateChildControls` sets `AutoGenerateColumns = False` and adds explicit column definitions. |
| **Outcome** | The grid's rendered columns are fixed and predictable regardless of the bound collection's shape. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb` |

---

## BR-V2-054: Conditions grid fill sorts the caller-supplied collection

| Field | Value |
| --- | --- |
| **Statement** | The conditions grid is filled from a caller-supplied `CollectionBase` and sorted after binding. |
| **Trigger** | Evaluated when the host page calls `Fill(inDataSource As CollectionBase)`. |
| **Enforcement** | `ConditionsGrid.vb` `Fill` locates the panel and grid, assigns the collection as the data source, binds, then sorts. |
| **Outcome** | The caller supplies an already-materialized business collection; the control does no data access of its own. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb` |

---

## BR-V2-055: Grid ID rewritten for jQuery compatibility

| Field | Value |
| --- | --- |
| **Statement** | The grid's rendered `ID` attribute is rewritten to the fully qualified client ID for jQuery compatibility. |
| **Trigger** | Evaluated in `OnPreRender` on every render. |
| **Enforcement** | `ConditionsGrid.vb` `OnPreRender` overwrites the grid's `ID` attribute with `ClientID_panelID_gridID`. |
| **Outcome** | Client-side jQuery selectors can find the grid by a stable, fully qualified identifier. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb` |

---

## BR-V2-056: Scroll region capped once the grid has four or more rows

| Field | Value |
| --- | --- |
| **Statement** | The scroll region is capped at 110 pixels once the grid has four or more rows. |
| **Trigger** | Evaluated in `OnPreRender` after the grid has been data-bound. |
| **Enforcement** | `ConditionsGrid.vb` `OnPreRender` sets the scroll panel's height to 110 pixels when the row count reaches four. |
| **Outcome** | Grids with many rows scroll within a fixed-height region instead of expanding the page indefinitely. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb` |

---

## BR-V2-057: Construction and fill failures are swallowed silently

| Field | Value |
| --- | --- |
| **Statement** | Conditions-grid construction and fill failures are swallowed silently rather than surfaced. |
| **Trigger** | Evaluated whenever `CreateChildControls` or `Fill` raises an exception. |
| **Enforcement** | `ConditionsGrid.vb` wraps both method bodies in a `Try`/`Catch` block with empty catch handlers. |
| **Outcome** | A failure in building or filling the grid produces no visible error and no logged diagnostic. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb` |

---

## BR-V2-058: One-click submit always guards the client click

| Field | Value |
| --- | --- |
| **Statement** | A one-click submit button always routes its client click through `OneClickOnly()`. |
| **Trigger** | Evaluated in `OnLoad` for every `OneClickSubmit` instance. |
| **Enforcement** | `OneClickSubmitControl.vb` `InitializeControls` sets `OnClientClick = "return OneClickOnly()"`. |
| **Outcome** | Every `OneClickSubmit` button is protected against duplicate submission without per-instance configuration. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/OneClickSubmitControl.vb` |

---

## BR-V2-059: Duplicate-click guard script registered once per page

| Field | Value |
| --- | --- |
| **Statement** | The duplicate-click guard variable and function are registered once per page. |
| **Trigger** | Evaluated for every `OneClickSubmit` instance during script registration. |
| **Enforcement** | `OneClickSubmitControl.vb` `BuildClientOneClickJavascript` guards both script blocks with a registered-once check. |
| **Outcome** | Pages with multiple one-click buttons emit exactly one copy of the guard variable and function. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/OneClickSubmitControl.vb` |

---

## BR-V2-060: Default CSS class is NormalButton

| Field | Value |
| --- | --- |
| **Statement** | A one-click submit button with no explicit CSS class defaults to `NormalButton`. |
| **Trigger** | Evaluated in `Render` when no `CssClass` has been assigned. |
| **Enforcement** | `OneClickSubmitControl.vb` `Render` applies `NormalButton` before delegating to the base button rendering. |
| **Outcome** | Buttons that do not set a CSS class still receive consistent SAIF button styling. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/OneClickSubmitControl.vb` |

---

## BR-V2-061: Multi-select list composed of textbox, image button and checkbox list

| Field | Value |
| --- | --- |
| **Statement** | The multi-select list is composed of a textbox, an arrow image button and a checkbox list. |
| **Trigger** | Evaluated in `CreateChildControls` for every `MultiListControl` instance. |
| **Enforcement** | `MultiListControl.vb` `CreateChildControls` composes the three constituent controls. |
| **Outcome** | The rendered control always has the same three-part composite structure. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/MultiListControl.vb` |

---

## BR-V2-062: Text portion editability controlled by IsTextEditable

| Field | Value |
| --- | --- |
| **Statement** | The text portion is editable only when `IsTextEditable` is set. |
| **Trigger** | Evaluated in `CreateChildControls` while configuring the textbox. |
| **Enforcement** | `MultiListControl.vb` sets the textbox's `ReadOnly`/enabled state from `IsTextEditable`. |
| **Outcome** | Consumers can choose between a free-text-editable or a display-only text portion for the same control. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/MultiListControl.vb` |

---

## BR-V2-063: List auto-closes on focus loss

| Field | Value |
| --- | --- |
| **Statement** | The list closes automatically when the user clicks elsewhere on the form. |
| **Trigger** | Evaluated on the client whenever the page form receives an `onmousedown` outside the control. |
| **Enforcement** | `MultiListControl.vb` `CreateChildControls` wires the page form's `onmousedown` to `MultListLostFocus(event)`. |
| **Outcome** | Users do not need to click the arrow again to dismiss the open checkbox list. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/MultiListControl.vb` |

---

## BR-V2-064: Multi-list assets ship as embedded web resources

| Field | Value |
| --- | --- |
| **Statement** | Multi-list assets ship inside the assembly as web resources rather than site files. |
| **Trigger** | Evaluated at assembly load time (attribute declaration) and at render time (URL resolution). |
| **Enforcement** | `MultiListControl.vb` declares an assembly-level `WebResource` attribute for `MultiList.js` and `MultiComboArrow.gif`. |
| **Outcome** | The control works without the consuming application deploying these two specific asset files. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/MultiListControl.vb` |

---

## BR-V2-065: WildCardItem designates the all-values entry

| Field | Value |
| --- | --- |
| **Statement** | `WildCardItem` designates the list entry that represents "all values".* |
| **Trigger** | Evaluated by host-page logic that inspects the checked items against `WildCardItem`. |
| **Enforcement** | `MultiListControl.vb` exposes `WildCardItem` as a designated special list-entry value. |
| **Outcome** | Host pages can special-case the "select all" entry when interpreting the checked selection.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2/MultiListControl.vb` |

---

## BR-V2-066: Tabs identified by client ID and content div ID

| Field | Value |
| --- | --- |
| **Statement** | Tabs are identified by client ID and paired with a content `div` ID. |
| **Trigger** | Evaluated when the host page calls `AddTab` / `InsertTab`. |
| **Enforcement** | `SAIFTabs.vb` `AddTab`/`InsertTab` create a `TabSettings` entry carrying both identifiers. |
| **Outcome** | Each tab has a stable pairing between its clickable element and the content region it reveals. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb` |

---

## BR-V2-067: Tab index assigned from current tab count

| Field | Value |
| --- | --- |
| **Statement** | A tab's index is assigned from the current tab count at the time it is added. |
| **Trigger** | Evaluated in `AddTab` for every new tab. |
| **Enforcement** | `SAIFTabs.vb` `AddTab` reads the tab list's current length and assigns it as the new tab's index. |
| **Outcome** | Tabs are numbered in the order they are added, without requiring the caller to track indices. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb` |

---

## BR-V2-068: Adding a selected tab raises SelectedIndexChanged

| Field | Value |
| --- | --- |
| **Statement** | Adding a selected tab raises `SelectedIndexChanged` carrying both the new and the prior tab. |
| **Trigger** | Evaluated in `AddTab` when the newly added tab's `Selected` flag is `True`. |
| **Enforcement** | `SAIFTabs.vb` `AddTab` captures the prior selected tab and raises the event with a `SAIFTabEventArgs`. |
| **Outcome** | Host pages can react to a tab becoming selected during page construction, not just on user click. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb` |

---

## BR-V2-069: Selected tab is the first tab with Selected set

| Field | Value |
| --- | --- |
| **Statement** | The selected tab is the first tab whose `Selected` flag is set. |
| **Trigger** | Evaluated whenever `SelectedTab` is read. |
| **Enforcement** | `SAIFTabs.vb` `SelectedTab` scans the tab list and returns the first match. |
| **Outcome** | If more than one tab were marked selected, only the first one in list order is treated as active. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb` |

---

## BR-V2-070: No selection returns an empty TabSettings, not Nothing

| Field | Value |
| --- | --- |
| **Statement** | When no tab is selected, an empty `TabSettings` instance is returned rather than `Nothing`. |
| **Trigger** | Evaluated in `SelectedTab` when no tab's `Selected` flag is set. |
| **Enforcement** | `SAIFTabs.vb` `SelectedTab` returns a newly constructed, empty `TabSettings` object. |
| **Outcome** | Callers of `SelectedTab`, `SelectedTabId`, `SelectedTabIndex`, and `SelectedTabText` never need a null check. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb` |

---

## BR-V2-071: Design mode detected by Page.User throwing

| Field | Value |
| --- | --- |
| **Statement** | Design mode is detected by the absence of a page principal (`Page.User` throwing). |
| **Trigger** | Evaluated whenever `IsDesign` is read. |
| **Enforcement** | `SAIFTabs.vb` `IsDesign` wraps a read of `Page.User` in a `Try`/`Catch` block and returns `True` on exception. |
| **Outcome** | Run-time-only behavior is suppressed on the Visual Studio design surface without a dedicated design-mode API call. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb` |

---

## BR-V2-072: Tab bar styling differs by mode

| Field | Value |
| --- | --- |
| **Statement** | Tab bar styling differs between normal and wizard modes (`SAIFTabBar` versus `SAIFWizardTabBar`). |
| **Trigger** | Evaluated in `CreateChildControls` based on the `Mode` property. |
| **Enforcement** | `SAIFTabBar.vb` applies the `SAIFTabBar` or `SAIFWizardTabBar` CSS class according to `TabBarMode`. |
| **Outcome** | The same control produces two visually distinct navigation styles depending on configuration. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb` |

---

## BR-V2-073: Tab after the selected tab is styled SelectedSibling

| Field | Value |
| --- | --- |
| **Statement** | The tab following the selected tab is styled `SelectedSibling` so the two render contiguously. |
| **Trigger** | Evaluated in `CreateChildControls` while assigning CSS classes to each tab's `LinkButton`. |
| **Enforcement** | `SAIFTabBar.vb` compares each tab's position to the selected tab's index and applies `SelectedSibling`. |
| **Outcome** | The selected tab and its neighbor render with a continuous visual border, matching the intended tab-bar look. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb` |

---

## BR-V2-074: Disabled tabs render without postback

| Field | Value |
| --- | --- |
| **Statement** | Disabled tabs render with `Disabled` / `DisabledSelected` styling and do not post back on click. |
| **Trigger** | Evaluated in `CreateChildControls` for tabs whose `Enabled` flag is `False`. |
| **Enforcement** | `SAIFTabBar.vb` applies the disabled CSS classes and omits the postback wiring for those `LinkButton`s. |
| **Outcome** | Users see a visually disabled tab that has no server-side click behavior. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb` |

---

## BR-V2-075: Tab state persisted through custom view-state methods

| Field | Value |
| --- | --- |
| **Statement** | Tab state is persisted across postbacks through custom `SaveViewState` / `LoadViewState`. |
| **Trigger** | Evaluated on every postback that involves a `SAIFTabBar`. |
| **Enforcement** | `SAIFTabBar.vb` overrides `SaveViewState`/`LoadViewState` to serialize the tab collection. |
| **Outcome** | The tab bar's configured tabs, order, and enabled/visible state all survive postback without re-registration. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb` |

---

## BR-V2-076: Bulk tab state operations exposed

| Field | Value |
| --- | --- |
| **Statement** | The tab bar exposes bulk enable, disable, show and hide operations over all tabs. |
| **Trigger** | Evaluated when the host page calls `EnableAllTabs`, `DisableAllTabs`, `ShowAllTabs`, or `HideAllTabs`. |
| **Enforcement** | `SAIFTabBar.vb` iterates its `Tabs` collection, updating each tab's flag. |
| **Outcome** | Host pages can change every tab's state in one call instead of iterating the collection themselves. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb` |

---

## BR-V2-077: Tabs sortable by any named property

| Field | Value |
| --- | --- |
| **Statement** | Tabs can be sorted by any property name using the reflection-based comparer. |
| **Trigger** | Evaluated when `SAIFTabBarTabCollection.Sort` is called with a property name. |
| **Enforcement** | `SAIFTabBar.vb` `Sorter(Of T)` uses reflection to read the named property on each `SAIFTabBarTab` for comparison. |
| **Outcome** | The tab collection can be reordered by any public property (for example `Order` or `Text`) without dedicated comparer classes. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb` |

---

## BR-V2-078: Enableability expressed as a control attribute

| Field | Value |
| --- | --- |
| **Statement** | Enableability is expressed as the control attribute `ControlManagerEnableable`. |
| **Trigger** | Evaluated whenever `SetControlManagerEnableable` is called for a control. |
| **Enforcement** | `mdlUtilities.vb` `SetControlManagerEnableable` adds the string attribute with the Boolean's string form. |
| **Outcome** | Any control can be flagged enableable/non-enableable without introducing a dedicated interface or base class. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/mdlUtilities.vb` |

---

## BR-V2-079: No attribute means enableable by default

| Field | Value |
| --- | --- |
| **Statement** | A control with no `ControlManagerEnableable` attribute is treated as enableable. |
| **Trigger** | Evaluated whenever `CanControlManagerEnableControl` is called. |
| **Enforcement** | `mdlUtilities.vb` `CanControlManagerEnableControl` returns `True` when the attribute is absent. |
| **Outcome** | Controls that were never explicitly opted out of bulk management remain manageable by default. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/mdlUtilities.vb` |

---

## BR-V2-080: Repeated Set calls may add duplicate attributes

| Field | Value |
| --- | --- |
| **Statement** | Repeated calls to `SetControlManagerEnableable` may add duplicate attributes - the behaviour is flagged as untested in the source. |
| **Trigger** | Evaluated whenever `SetControlManagerEnableable` is invoked more than once for the same control. |
| **Enforcement** | `mdlUtilities.vb` `SetControlManagerEnableable` adds the attribute without first checking for an existing entry. |
| **Outcome** | The control's attribute collection may accumulate duplicate `ControlManagerEnableable` entries; the resulting read behavior is unverified.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2/mdlUtilities.vb` |

---

## BR-V2-081: Every SAIFGrid carries the SAIFGrid CSS class

| Field | Value |
| --- | --- |
| **Statement** | Every `SAIFGrid` carries CSS class `SAIFGrid`. |
| **Trigger** | Evaluated on construction of every `SAIFGrid` instance. |
| **Enforcement** | `SAIFGrid.vb` sets `CssClass = "SAIFGrid"` in the constructor. |
| **Outcome** | All grids on a page share consistent baseline styling regardless of host-page configuration. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFGrid.vb` |

---

## BR-V2-082: Grid scripts registered once per page

| Field | Value |
| --- | --- |
| **Statement** | `SAIF.Grid.js` and the grid startup script are each registered once per page. |
| **Trigger** | Evaluated in `Init` for every `SAIFGrid` instance. |
| **Enforcement** | `SAIFGrid.vb` `RegisterClientScripts` guards both registrations with a registered-once check. |
| **Outcome** | Pages with multiple grids emit exactly one copy of the script include and the startup script block. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFGrid.vb` |

---

## BR-V2-083: Row-click wiring requires a populated DataKeyField

| Field | Value |
| --- | --- |
| **Statement** | Client-side row clicks are only wired up when `DataKeyField` is non-empty. |
| **Trigger** | Evaluated in `RowDataBound` for each data row. |
| **Enforcement** | `SAIFGrid.vb` `RowDataBound` checks `DataKeyField` before emitting the row-click attribute. |
| **Outcome** | Grids that do not configure `DataKeyField` render without row-click behavior, avoiding broken client script. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFGrid.vb` |

---

## BR-V2-084: Row key value read by reflection

| Field | Value |
| --- | --- |
| **Statement** | The key value is read from the bound data item by reflection and emitted as a row attribute. |
| **Trigger** | Evaluated in `RowDataBound` for each data row when `DataKeyField` is populated. |
| **Enforcement** | `SAIFGrid.vb` `RowDataBound` reflects the named property off the bound item and writes `dataKeyField`. |
| **Outcome** | Any bound object exposing the named property works, without the grid needing a typed interface. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFGrid.vb` |

---

## BR-V2-085: Grids with rows get an accessible header

| Field | Value |
| --- | --- |
| **Statement** | Grids with rows render an accessible `<thead>` header section. |
| **Trigger** | Evaluated in `PreRender` when the grid has one or more rows. |
| **Enforcement** | `SAIFGrid.vb` `PreRender` enables the accessible header and moves the header row into `<thead>`. |
| **Outcome** | Screen readers and assistive technology can correctly associate column headers with grid data. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFGrid.vb` |

---

## BR-V2-086: Focus is set only when the target control exists

| Field | Value |
| --- | --- |
| **Statement** | Focus is set only when the bound control actually exists on the page. |
| **Trigger** | Evaluated in `Render` when `BoundControlId` is set. |
| **Enforcement** | `FocusManager.vb` `Render` checks that the page contains a control with the given ID before registering the script. |
| **Outcome** | A `FocusManager` referencing a missing or removed control has no effect instead of raising a client error. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/FocusManager.vb` |

---

## BR-V2-087: Focus script wrapped in try/catch

| Field | Value |
| --- | --- |
| **Statement** | The focus script is wrapped in a `try`/`catch` block so a missing client element cannot raise a script error. |
| **Trigger** | Evaluated in `Render` while generating the startup script. |
| **Enforcement** | `FocusManager.vb` `Render` emits the `.focus()` call inside a JavaScript `try`/`catch` block. |
| **Outcome** | Client-side script errors are suppressed even if the DOM element cannot be found at execution time. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/FocusManager.vb` |

---

## BR-V2-088: Unresolvable BoundControlId produces no output

| Field | Value |
| --- | --- |
| **Statement** | An unresolvable `BoundControlId` produces no output. |
| **Trigger** | Evaluated in `Render` when the referenced control cannot be found. |
| **Enforcement** | `FocusManager.vb` `Render` renders nothing when the lookup for `BoundControlId` fails. |
| **Outcome** | The page renders normally, with no focus behavior, when the configured target control is missing. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/FocusManager.vb` |

---

## BR-V2-089: Alphanumeric bar always renders the full A-Z range

| Field | Value |
| --- | --- |
| **Statement** | The alphanumeric bar always renders the full A-Z range from ASCII 65 to 90. |
| **Trigger** | Evaluated in `CreateChildControls` for every `SAIFAlphaNumericLinkControl` instance. |
| **Enforcement** | `SAIFAlphaNumericLinkControl.vb` `CreateChildControls` loops ASCII codes 65-90, creating one `SAIFLinkButton` per code. |
| **Outcome** | Consumers always get the complete alphabet filter bar without configuring the letter range. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFAlphaNumericLinkControl.vb` |

---

## BR-V2-090: A single Number link represents all numeric values

| Field | Value |
| --- | --- |
| **Statement** | A single `Number` link represents all numeric values. |
| **Trigger** | Evaluated in `CreateChildControls` after the A-Z loop completes. |
| **Enforcement** | `SAIFAlphaNumericLinkControl.vb` appends one additional link with `AlphaNumericCharacter.Number`. |
| **Outcome** | Numeric-leading entries are filterable through a single dedicated link rather than one link per digit. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFAlphaNumericLinkControl.vb` |

---

## BR-V2-091: Clicked character delivered via LinkClicked event

| Field | Value |
| --- | --- |
| **Statement** | The clicked character is delivered to the host page through the `LinkClicked` event. |
| **Trigger** | Evaluated when the user clicks any letter or the `Number` link. |
| **Enforcement** | `SAIFAlphaNumericLinkControl.vb` raises `LinkClicked` carrying the clicked link's `AlphaNumericCharacter` value. |
| **Outcome** | Host pages filter their data set purely by handling this single event. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFAlphaNumericLinkControl.vb` |

---

## BR-V2-092: Activity indicator switches to the Indicator CSS class

| Field | Value |
| --- | --- |
| **Statement** | Enabling the activity indicator switches the control to an `Indicator`-suffixed CSS class (for example `SAIFButtonIndicator`). |
| **Trigger** | Evaluated when `EnableIndicator` is set on `SAIFButton`, `SAIFComboBox`, or `SAIFListBox`. |
| **Enforcement** | Each control swaps its base CSS class (`SAIFButton`, `SAIFComboBox`, `SAIFListBox`) for the matching `Indicator`-suffixed variant. |
| **Outcome** | Consumers opt into a visual busy/activity indicator purely by toggling one property. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFButton.vb` |

---

## BR-V2-093: Each interactive control registers its own script

| Field | Value |
| --- | --- |
| **Statement** | Each interactive control registers its own dedicated client script include. |
| **Trigger** | Evaluated in `Init`/`Load` for each control instance. |
| **Enforcement** | `SAIFButton.vb`, `SAIFComboBox.vb`, `SAIFListBox.vb`, `SAIFTextBox.vb`, `SAIFHoverHelp.vb`, `SAIFGrid.vb`, `SAIFPanel.vb`, and `SAIFMessage.vb` each register their own matching file under `/Resources/Scripts/`. |
| **Outcome** | Every control type's client behavior is delivered by its own script file, loaded only when that control is used. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFButton.vb` |

---

## BR-V2-094: Form controls can be disabled on click/change

| Field | Value |
| --- | --- |
| **Statement** | Form controls can be disabled on click/change to prevent interaction during postback.* |
| **Trigger** | Evaluated when the host page enables `DisableFormControlsOnClick` / `DisableFormControlsOnChange`. |
| **Enforcement** | `SAIFButton.vb`, `SAIFComboBox.vb`, and `SAIFListBox.vb` signal the client script to disable remaining form controls. |
| **Outcome** | Users cannot double-submit or change other fields while a postback triggered by the control is in flight.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFButton.vb` |

---

## BR-V2-095: TextBox.js included once per page

| Field | Value |
| --- | --- |
| **Statement** | `TextBox.js` is included once per page. |
| **Trigger** | Evaluated in `Init` for every `SAIFTextBox` instance. |
| **Enforcement** | `SAIFTextBox.vb` `RegisterClientScripts` guards the include with `IsClientScriptIncludeRegistered`. |
| **Outcome** | Pages with multiple `SAIFTextBox` instances emit exactly one `TextBox.js` script tag. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTextBox.vb` |

---

## BR-V2-096: Character counter requires multi-line, positive MaxLength, and opt-in

| Field | Value |
| --- | --- |
| **Statement** | The character counter is created only for multi-line textboxes with a positive `MaxLength` and the counter enabled. |
| **Trigger** | Evaluated in `CreateChildControls` for every `SAIFTextBox` instance. |
| **Enforcement** | `SAIFTextBox.vb` `CreateChildControls` checks `TextMode = MultiLine`, `MaxLength > 0`, and `ShowCharacterCounter`. |
| **Outcome** | Single-line textboxes and textboxes without a length limit never receive the extra counter panel. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTextBox.vb` |

---

## BR-V2-097: Maximum length published as a client attribute

| Field | Value |
| --- | --- |
| **Statement** | The maximum length is published to the client as the `SaifMaxLength` attribute. |
| **Trigger** | Evaluated in `RenderControl` for multi-line textboxes with a maximum length. |
| **Enforcement** | `SAIFTextBox.vb` `RenderControl` writes the `SaifMaxLength` attribute before rendering the base textbox. |
| **Outcome** | Client script (`TextBox.js`) can enforce and display the remaining-character count from this attribute. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTextBox.vb` |

---

## BR-V2-098: Invisible textbox renders nothing

| Field | Value |
| --- | --- |
| **Statement** | An invisible textbox renders nothing. |
| **Trigger** | Evaluated at the start of `RenderControl` when `Visible` is `False`. |
| **Enforcement** | `SAIFTextBox.vb` `RenderControl` exits before writing any markup. |
| **Outcome** | Hidden textboxes produce no DOM element, consistent with the other controls' invisible-render behavior. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTextBox.vb` |

---

## BR-V2-099: Counter panel renders immediately after the textbox

| Field | Value |
| --- | --- |
| **Statement** | The counter panel is rendered immediately after the textbox and matches its width. |
| **Trigger** | Evaluated in `RenderControl` when the counter is enabled. |
| **Enforcement** | `SAIFTextBox.vb` `RenderControl` renders the base textbox, then the `msg` counter panel sized to match. |
| **Outcome** | The character count indicator appears directly below/after the textbox at the same visual width. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFTextBox.vb` |

---

## BR-V2-100: Hover help uses a fixed CSS class and script

| Field | Value |
| --- | --- |
| **Statement** | Hover help always uses CSS class `HoverHelpV2` and `HoverHelp.js`. |
| **Trigger** | Evaluated on construction and script registration for every `SAIFHoverHelp` instance. |
| **Enforcement** | `SAIFHoverHelp.vb` sets `CssClass = "HoverHelpV2"` and registers `/Resources/Scripts/Controls/HoverHelp.js`. |
| **Outcome** | Every hover-help icon shares consistent styling and client behavior. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFHoverHelp.vb` |

---

## BR-V2-101: Default help icon is a fixed image

| Field | Value |
| --- | --- |
| **Statement** | The default help icon is `/Resources/Images/DefaultHelpImage.gif`. |
| **Trigger** | Evaluated when rendering the icon for a `SAIFHoverHelp` instance. |
| **Enforcement** | `SAIFHoverHelp.vb` renders the fixed image path unless a different icon is configured. |
| **Outcome** | Consumers get a consistent default help icon without configuring an image path themselves. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFHoverHelp.vb` |

---

## BR-V2-102: Pop-up size and placement controlled by properties

| Field | Value |
| --- | --- |
| **Statement** | Pop-up width and placement are controlled by `PanelWidth` and `PopupPosition`. |
| **Trigger** | Evaluated when rendering the pop-up panel for a `SAIFHoverHelp` instance. |
| **Enforcement** | `SAIFHoverHelp.vb` sizes the pop-up panel from `PanelWidth` and positions it per the `HoverHelpPopupPosition` enum. |
| **Outcome** | Consumers can control the pop-up's size and screen position per instance. |
| **Source** | `SAIF.Presentation.Web.Controls.V2/SAIFHoverHelp.vb` |

---

## Notes
- Rule IDs use this library's `V2` prefix followed by a zero-padded sequence number, per the Stage 1 analysis numbering convention.
- Mark inferred statements with `*` and unknown statements with `**`. Do not use bold inline confidence labels.

> **Note:** Statements marked with `*` were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Control catalog](control-catalog.md)
- [API reference](api/index.md)

## Sources
- `SAIF.Presentation.Web.Controls.V2/Banner.vb`
- `SAIF.Presentation.Web.Controls.V2/ConditionsGrid.vb`
- `SAIF.Presentation.Web.Controls.V2/FocusManager.vb`
- `SAIF.Presentation.Web.Controls.V2/MultiListControl.vb`
- `SAIF.Presentation.Web.Controls.V2/OneClickSubmitControl.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFAlphaNumericLinkControl.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFButton.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFCheckBox.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFDropDownList.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFGrid.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFHoverHelp.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFMessage.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFPanel.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFRadioButton.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFTabBar.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFTabs.vb`
- `SAIF.Presentation.Web.Controls.V2/SAIFTextBox.vb`
- `SAIF.Presentation.Web.Controls.V2/mdlUtilities.vb`
