/**
* Deny-by-default masking for PostHog session replay.
*
* Everything is masked unless it is app chrome (founder-approved 2026-08-17,
* supersedes the 2026-08-06 pattern-based default where user content was
* visible). Replays show layout, clicks and static UI text: headers, nav,
* form labels, placeholders, buttons. They never show user data: not what a
* user typed (input values are masked wholesale by rrweb, see
* instrumentation-client.ts) and not user content rendered as text
* (counterparty names, descriptions, amounts, identity numbers).
*
* What counts as chrome, i.e. renders readable in a replay:
* - Any subtree tagged `data-ph-unmask`. Tags live on the shared UI
* primitives (nav, PageHeader, Label, Button, tabs, dialog/sheet titles,
* card titles, badges, tooltips, empty states), so page code gets readable
* chrome without per-page tagging.
* - `
` elements with NO explicit tag anywhere above them: table column
* headers are static chrome, but the page-level dry-table pattern writes
* raw ` | ` per page, so there is no shared
* component to tag. An explicit data-ph-mask (on the th or any ancestor)
* always wins over this fallback.
*
* Chrome is still pattern-scrubbed (belt and braces): an i18n string that
* interpolates an amount or a person-/organisationsnummer into a title or
* button label gets that span masked even inside an unmasked subtree.
*
* `data-ph-mask` force-masks a subtree and wins over `data-ph-unmask`: the
* NEAREST tagged ancestor decides, and mask wins when both attributes land
* on the same element. Use it where user data flows into a chrome primitive
* (e.g. a Label interpolating the user's email, a dialog title carrying a
* counterparty name).
*
* Untagged text is fully masked, so the failure mode for new UI is
* over-masking (asterisks where chrome should be readable), never leaking
* a user's books.
*
* Known limit: rrweb masks text nodes and input values, not ATTRIBUTES.
* posthog-js exposes no attribute mask hook, so a title/aria-label/
* placeholder attribute is recorded as-is. An element whose attributes
* carry user data (e.g. a placeholder prefilled with an effective value)
* must carry the `ph-no-capture` class instead: rrweb's blockClass removes
* the whole element from the recording while the app UX is untouched. Do
* not put user data in title or aria-label attributes.
*/
/**
* Currency-shaped text: optional sign (Intl sv-SE renders negative amounts
* with U+2212, hand-written strings use '-'), digits with space/nbsp grouping
* and a decimal part, then a currency marker. The trailing lookahead rejects
* letter continuations so "10 kronor" or "SEKTION" never match.
*/
const AMOUNT_PATTERN = new RegExp(
// − is the Unicode minus sign Intl sv-SE emits for negative amounts.
String.raw`[-−]?\d(?:[\d\s]|[.,](?=\d))*\s?(?:kr|sek|eur|usd|nok|dkk|gbp|chf|us\$|\$|€|£)(?![\p{L}\d])`,
'giu',
)
/**
* Person-/organisationsnummer rendered as text: 6 or 8 digits, separator,
* 4 digits ("556677-8899", "19850101-1234", "850101+1234"). The digit
* lookarounds keep bankgiro ("5050-1055"), phone numbers and dates out.
*/
const IDENTITY_TEXT_PATTERN = /(? |