---
title: Message accumulation
summary: Builds user-facing message text by appending message lines while suppressing exact duplicates, and formats message lists into a single delimited string.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Application
reference_type: package
operation_id: OP-APP-04
trigger: A manager or helper method receives message text or a list of messages to append or format
---

# Message accumulation

`SAIF.Application` offers two related but independent message-building mechanisms: `AppManagerBase.AppendMessageLines(...)`, which grows a single manager-scoped `Message` string one line at a time while skipping duplicates, and the static `MessageFormatting` helpers, which turn an array of message strings into one concatenated string using a caller-chosen (or fixed) separator, also skipping duplicates but as a batch operation.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-APP-04 |
| **Trigger** | A manager calls `AppendMessageLines(...)` on itself, or any caller invokes a `MessageFormatting` shared method with a message array |
| **Inputs** | A single message string, an array of message strings, or an `IEnumerable(Of String)` of messages; an optional concatenation string for `GetMessagesUserDefined` |
| **Outputs** | Updated `AppManagerBase.Message` string (instance path); a single formatted message string (static `MessageFormatting` path) |
| **Primary components** | `AppManagerBase.AppendMessageLines` (three overloads), `AppManagerBase.Message`, `AppManagerBase.NewLineCharacter`, `MessageFormatting.GetMessagesForWindows`, `MessageFormatting.GetMessagesForWeb`, `MessageFormatting.GetMessagesUserDefined`, `MessageFormatting.BuildMessageString`, `MessageFormatting.AppendMessageString`, `MessageFormatting.RemoveReturns` |
| **Entry point** | `Application\SAIF.Application\AppManagerBase.vb`; `Application\SAIF.Application\MessageFormatting.vb` |

## Steps

### Instance path: `AppManagerBase.AppendMessageLines`

1. **Normalize the input shape.** The three protected overloads all converge on the single-string overload:
   - `AppendMessageLines(inMessages As IEnumerable(Of String))` calls `AppendMessageLines(inMessages.ToArray)`.
   - `AppendMessageLines(inMessage() As String)` loops the array and calls `AppendMessageLines(AMessage)` once per element.
   - `AppendMessageLines(inMessage As String)` is the actual append logic (step 2).
2. **Append with duplicate suppression.** For a single message string, the method appends it to `Message` only when both conditions hold: the string is non-empty (`inMessage.Length > 0`) and it is not already a substring of the current `Message` (`Not Message.Contains(inMessage)`) ([BR-002](../business-rules.md#br-002-duplicate-message-text-is-suppressed)).
   - If `Message` already has content: the configurable `NewLineCharacter` (default `<BR>`) is appended first as a separator before the new text.
   - If `Message` is empty: the new text becomes the entire `Message` with no leading separator.
   - If the message is empty or already contained: the call is a no-op — nothing is appended and `Message` is unchanged.
   - Because the duplicate check is `Message.Contains(inMessage)` rather than an exact per-line match, a new message that is a substring of already-accumulated text is also suppressed, even if it was never appended as its own line.*

### Static path: `MessageFormatting`

3. **Choose a formatting entry point.** Callers pick one of three static methods based on the desired separator: `GetMessagesForWindows` (`vbCrLf`), `GetMessagesForWeb` (`<BR>`), or `GetMessagesUserDefined` (caller-supplied `inConcatString`).
4. **Strip embedded line breaks from each message.** Each method iterates the input `inMessages` array and calls `RemoveReturns(Msg)` on every element, which replaces embedded `vbCrLf` or `<BR>` occurrences with an empty string so a single logical message cannot itself introduce extra line breaks into the joined result.
5. **De-duplicate at the batch level.** Each cleaned message is added to a local `Results` list only if it is not already present (`Not Results.Contains(temp)`) ([BR-002](../business-rules.md#br-002-duplicate-message-text-is-suppressed)) — this is an exact-match check on the cleaned text, unlike the substring check used by `AppManagerBase.AppendMessageLines`.
6. **Join the de-duplicated list.** `BuildMessageString(Results, separator)` concatenates every entry in `Results`, inserting the separator string after every entry except the last (compared by reference/value equality against `inMessages.Last`, which can misbehave if the list contains a repeated final value — see failure notes).
7. **Append to an existing message (alternate helper).** `AppendMessageString(ByRef inMessage, inMessages, inConcatString)` performs the same join as step 6 but appends the result onto an existing `inMessage` string, inserting a hard-coded `<br>` separator first when `inMessage` already has non-blank content — independent of any `NewLineCharacter` setting on a calling `AppManagerBase`.

## Process flow

The following diagram visualizes both the instance append path and the static batch-formatting path.

```mermaid
flowchart TD
	Start([Message text arrives]) --> Which{Instance or static path?}
	Which -->|AppManagerBase| Norm[Normalize to single-string calls]
	Norm --> Check{Non-empty and<br>not already in Message?}
	Check -->|No| Skip([No-op: Message unchanged])
	Check -->|Yes| Sep{Message already<br>has content?}
	Sep -->|Yes| AddSep[Append NewLineCharacter]
	Sep -->|No| AddDirect[No separator needed]
	AddSep --> Append[Append message text]
	AddDirect --> Append
	Which -->|MessageFormatting| Clean[RemoveReturns on each message]
	Clean --> Dedup[Add to Results if not<br>already present, exact match]
	Dedup --> Join[BuildMessageString joins<br>Results with chosen separator]
	Join --> Done([Return formatted string])
```

## Failure and edge-case behavior

- `AppManagerBase.AppendMessageLines` suppresses on a **substring** match (`Message.Contains(inMessage)`), while the static `MessageFormatting` methods suppress on an **exact** match (`Results.Contains(temp)`) — the two "duplicate suppression" behaviors covered by [BR-002](../business-rules.md#br-002-duplicate-message-text-is-suppressed) are not identical, and callers mixing both mechanisms on the same logical message list may see inconsistent results.*
- `BuildMessageString` and `AppendMessageString` compare `inMessages.Last <> Message` to decide whether to append the separator; if the same message string appears more than once at the end of a list that was not already de-duplicated by a caller, the separator logic can omit separators earlier than expected.*
- `RemoveReturns` only replaces the **first** kind of return sequence it detects (`vbCrLf` else `<BR>`) — a message containing both `vbCrLf` and `<BR>` only has the `vbCrLf` occurrences removed, since the method returns immediately after that branch.*
- `NewLineCharacter` on `AppManagerBase` defaults to `<BR>` (`DefaultNewLineCharacter`) and is settable per-instance, but the static `MessageFormatting.AppendMessageString` always uses a hard-coded `<br>` regardless of any `AppManagerBase.NewLineCharacter` setting, so the two paths can produce visually different separators in the same application.*

> **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

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources

- `Application\SAIF.Application\AppManagerBase.vb`
- `Application\SAIF.Application\MessageFormatting.vb`
