You are testing one browser, and the page says which
A regular expression is not one language. JavaScript, Python, Java, .NET, PCRE, Perl, PHP, Ruby, grep, Go’s RE2 and Rust’s regex crate all implement their own pattern syntax, and the differences are not cosmetic: some constructs exist in one and not another (atomic groups, possessive quantifiers, `\K`, `[[:alpha:]]`), and some constructs mean different things in two of them (`$` before a trailing newline, `\w` for accented letters, what a lookaround is allowed to see).
Your pattern runs on this browser’s own JavaScript regular-expression support, in your own tab, so a pattern that works here works the same way in `test`, `match`, `replace`, `split` and `search` in your own code. Nothing is simulated or translated, and the report says which features this browser supports, so a bug report can name the browser it came from.
Where the browser cannot help is the other direction. The panel names the constructs that another flavor has, and says which flavor has them, so a pattern ported from Python or written against PCRE documentation stops being a mystery. That is the part a tester usually leaves out, and it is the part that costs an afternoon.
The flags, and the three that surprise people
The letters after the closing slash are not decoration; they change what a pattern means. `g` decides whether you get one match or all of them, and without it `replace` changes one occurrence - the most common surprise in JavaScript strings. `i` folds case, using Unicode *simple* folding, which is why `ß` still does not match `ss` and a class written as `[a-z]` is still only 26 ASCII letters. `m` makes `^` and `$` match line by line instead of at the two ends of the whole string, and `s` lets `.` match a line break - without it `.` stops at a newline, which is the reason a pattern that works on one line matches nothing in a file.
The three that surprise people most are about characters rather than flags. First: `\w`, `\d` and `\b` are ASCII in every mode, so `\w+` matches `café` as `caf`, and `\bété\b` matches nothing at all, because é is not a word character and `\b` wants a word character on both sides. `\p{Letter}` with `u` is the Unicode-aware version, and Python 3 is the other way round by default, which is where the confusion comes from.
Second: `$` without `m` matches only at the very end of the string, so `/abc$/` does not match "abc\n" - Python and Perl both match there. Third: `y` (sticky) is not "start at position 0 and then search on"; it matches at `lastIndex` and nowhere else, which on a fresh pattern is a single position. That flag is usually what somebody wants when a `^` inside a loop behaves strangely.
Groups, positions, and the replacement syntax
A group captures what it matched, and carries a name if you give it one: `(?<user>[\w.]+)` is group 1 and also `user`. The `d` flag adds `match.indices`, the start and end of the whole match and of every group - which is how you learn *where* a group matched rather than only what it captured, and it is the flag to turn on when the right text matches in the wrong place. Positions here are UTF-16 offsets, the same numbers `match.index` gives you, so an emoji counts as two units and every number transfers straight into code.
The replacement string has its own small language, and it is where two habits collide. In JavaScript `$1` is group 1, `$&` is the whole match, `$<name>` is a named group, `$$` is a literal dollar, `` $` `` is everything before the match and `$'` is everything after it. In sed, grep and Perl a backreference is `\1` and the whole match is `&`; in .NET the whole match is `$0`. JavaScript leaves all of those alone, so a replacement copied from another language produces literal text and no error at all.
One distinction is worth more than the rest because it looks like nothing: a reference to a group that does not exist is copied into the output as text (`$2` stays `$2`), while a reference to a group that exists but did not take part in this particular match becomes an empty string. Two replacement strings that differ by one character can therefore behave completely differently, and neither of them throws.
Why a pattern can hang the tab, and what this page does instead
A regular expression is a program, and a badly written one is slow in a way that surprises people who are used to loops. `(a+)+$` against a long run of `a` followed by one character that does not match makes the engine try every way of splitting that run in two, then in three, then in four: each extra character doubles the work, so a thirty-character input is about a billion attempts. Nothing is broken and there is no error - the tab simply stops responding.
The engines that avoid this are the ones that gave up features to do it. RE2, Go’s `regexp` and Rust’s regex crate match in time proportional to the input by refusing backreferences and lookarounds, which is why the same pattern can be linear in Go and catastrophic in JavaScript. In a backtracking engine the fix is to write the pattern so that every repetition consumes a character it cannot give back - matching a run once and testing what follows it, rather than nesting one quantifier inside another.
JavaScript cannot interrupt a running match: there is no timeout to set and no way to abort a `RegExp` mid-flight, so "run it and see" answers the wrong question. This page therefore refuses the one shape it can recognize with certainty - a quantifier applied to a group that already contains an unbounded quantifier - points the caret at it, explains it, and offers a switch for the case where your input really is a few dozen characters. It also says out loud what a static check cannot see: `(a|aa)+` is just as expensive and looks perfectly innocent.