The escaping covers \ (backreference) and $ (interpolation), but the prxchange substitution uses `
This target hardcodes an internal SAS demo host (nextviya.emea.sas.com) and a demo user path (/Users/viyademo18/dc). It's referenced by the nextviya npm script in sas/package.json. Other targets already hardcode sas.4gl.io, so this follows the existing pattern, but confirm this internal host is intended to ship in the public repo.
Calling getFilteredComputeContexts() directly in the @for expression means Angular re-invokes the method on every change-detection cycle (it can't know the return value is stable). For a small list this is harmless, but the idiomatic and cheaper approach is to bind to a cached array property (e.g. update this.filteredComputeContexts inside ensureSelectedComputeContext() / onShowAllContextsChange() and iterate over that). This avoids a fresh filter allocation on every CD tick.
Subtle: substitutions run sequentially over columnNames, and substituteBoundedToken's output is fed back into the next column's pass. If a real column were named like a produced cell ref (e.g. a column literally named B1), an earlier substitution's output could be re-matched and re-substituted by this later pass. substituteBoundedToken's surrounding-blank requirement makes this unlikely in practice, and parseFormulaRule has the same ordering — flagging only as an accepted shared edge case.
Shared limitation (identical to parseFormulaRule.ts:53): this quoted-span regex doesn't handle doubled-quote escaping ("" inside a quoted string). A pasted value like ="say ""PRICE"" " would mis-segment and could expose PRICE to substitution. Not a regression since parseFormulaRule has the same pattern, but worth a shared follow-up if pasted formulas may contain escaped embedded quotes.
This paste listener is attached once in initSetup (which runs once per table load, not per edit session — so the comment's intent is correct), but there's no matching removeEventListener in ngOnDestroy. This matches the existing mousedown listener at line 3269 (same no-cleanup pattern), and since hot.rootElement is part of the component DOM that Angular tears down, the listener is GC'd with the node — so it's consistent rather than a leak. Worth addressing together with the mousedown listener if you ever do a cleanup pass.
editor.row === null is the weaker guard here — getActiveEditor() can return a finished-state editor whose row isn't reliably null across Handsontable versions. The load-bearing check is event.target !== editor.TEXTAREA (the editor's TEXTAREA only matches the paste target while a cell is actively being edited). Functionally safe as written (both are ANDed), but consider leading with the event.target !== editor.TEXTAREA check for clarity, since that's the one that actually distinguishes grid-level paste from in-editor paste.
Test Coverage
**Services: 22/60 (37%)
Hermes Agent Code Review (new commits 45226e1, 003550b)
Critical: _debug=131 is still hard-enabled. The comment says it will be turned off before final release, but there is no guard (env flag, build conditional, or linked TODO) to ensure that happens. Please revert to ' ' (debug off) or gate behind environment.production ? ' ' : '&_debug=131' so debug output only appears in dev builds. This is the only blocking item.
Carried over (non-blocking): const anyDetails: any = details bypasses the ComputeContextDetails type to probe runAsUserId / environment.runAsUserId. If these fields appear on Viya responses, extend the model so the fallbacks are type-checked.
Carried from first review: symget('_contextname') is concatenated directly into the PRX replacement string. A context name containing $ or \ would be interpreted as a backreference. Practical risk is low (context names are plain identifiers), but confirm context names can never contain $/\, or escape the value before embedding.