A major new capability: HARDFORMULA / SOFTFORMULA DQ rule types, backed by Handsontable's formulas plugin (HyperFormula).
Formula rules substitute column names with row-relative cell references and resolve DC.USER_NAME / DC.ORIG_VALUE to literal values; HARDFORMULA reuses the existing READONLY mechanism.
Live DC.ROW_STATUS via a hidden EDIT_STATUS column, so formulas can react to a row's edit state; the row-header +/-/~ indicator translates visual→physical row index so it stays correct when the grid is sorted.
Inserting rows uses hot.alter() so formula values realign instead of going stale.
UserService is now providedIn: 'root', fixing DC.USER_NAME (and other lazy-module readers) always seeing an unset user.
The column-info dropdown shows the applied formula.
Formula-overwritten cells are flagged and can be reverted; saveTable() submits the live computed value rather than the raw =... string.
Guards against EDIT_STATUS / dc.row_status colliding with a real column of the same name, and stops the client-only EDIT_STATUS column from being submitted to the backend during dynamic cell validation.
Cell revert
Revert one cells (or range of cells) to their original (current) value
Formatted / Unformatted toggle on the staging page
Mirrors the review page's toggle. getstagetable.sas now also returns SAS-formatted data (fmt_stagetable), and the UI picks between formatted and raw, falling back to raw when formatted data is absent. Added e2e coverage.
Viewbox resize on all edges
Viewboxes can now be dragged to resize from all four edges and corners (previously only the bottom-right corner). Resize handles sit above Handsontable's frozen header clones so clicks reach the handle rather than the table underneath.
Licence-key protocol mismatch warning
A licence key generated for one connection type (http/https) always fails to decrypt on the other. The mismatch is now detected client-side from the raw key text before submission, with a warning and the apply button disabled, instead of surfacing only as a generic "invalid key" error after a backend round trip.
Bug fixes
stage: ensure formats arrive from the backend in getstagetable.sas.
validations: bump core for the mp_validate fix.
CAS: support the REPLACE load type (plus docs).
startup: show the real startup-service response text when Viya returns a plain-text "Job error" body instead of JSON, instead of reporting every expected field as "not present".
editor: the column-info dropdown text can now be selected and copied.
editor: table-header buttons and title size to their content rather than fixed grid thirds, so the dataset name/row-count no longer wraps unnecessarily.
editor:dc.row_status / EDIT_STATUS is no longer submitted to the backend.
Dependencies / security
Resolved npm audit vulnerabilities in production dependencies (Angular HttpTransferCache cache-key ambiguity and i18n XSS advisories).
Aligned all @angular/* packages to the same lockstep version (20.3.27) so CI's strict npm ci passes.
Scoped the brace-expansion override to its actual vulnerable chain (fixes license-checker breakage).
Pinned babel-loader to resolve an unresolvable peer conflict; regenerated/patched the lockfile to fix npm ci sync errors.
Overrode nanoid to 3.3.18 in sas/ directly (rather than relying on @sasjs/cli's override, which doesn't propagate to consumers) so it survives future npm install runs.
## Highlights
### Formula-based data quality rules
A major new capability: `HARDFORMULA` / `SOFTFORMULA` DQ rule types, backed by Handsontable's formulas plugin (HyperFormula).
- Formula rules substitute column names with row-relative cell references and resolve `DC.USER_NAME` / `DC.ORIG_VALUE` to literal values; `HARDFORMULA` reuses the existing READONLY mechanism.
- Live `DC.ROW_STATUS` via a hidden `EDIT_STATUS` column, so formulas can react to a row's edit state; the row-header +/-/~ indicator translates visual→physical row index so it stays correct when the grid is sorted.
- Inserting rows uses `hot.alter()` so formula values realign instead of going stale.
- `UserService` is now `providedIn: 'root'`, fixing `DC.USER_NAME` (and other lazy-module readers) always seeing an unset user.
- The column-info dropdown shows the applied formula.
- Formula-overwritten cells are flagged and can be reverted; `saveTable()` submits the live computed value rather than the raw `=...` string.
- Guards against `EDIT_STATUS` / `dc.row_status` colliding with a real column of the same name, and stops the client-only `EDIT_STATUS` column from being submitted to the backend during dynamic cell validation.
### Cell revert
Revert one cells (or range of cells) to their original (current) value
### Formatted / Unformatted toggle on the staging page
Mirrors the review page's toggle. `getstagetable.sas` now also returns SAS-formatted data (`fmt_stagetable`), and the UI picks between formatted and raw, falling back to raw when formatted data is absent. Added e2e coverage.
### Viewbox resize on all edges
Viewboxes can now be dragged to resize from all four edges and corners (previously only the bottom-right corner). Resize handles sit above Handsontable's frozen header clones so clicks reach the handle rather than the table underneath.
### Licence-key protocol mismatch warning
A licence key generated for one connection type (http/https) always fails to decrypt on the other. The mismatch is now detected client-side from the raw key text before submission, with a warning and the apply button disabled, instead of surfacing only as a generic "invalid key" error after a backend round trip.
## Bug fixes
- **stage:** ensure formats arrive from the backend in `getstagetable.sas`.
- **validations:** bump core for the `mp_validate` fix.
- **CAS:** support the REPLACE load type (plus docs).
- **startup:** show the real startup-service response text when Viya returns a plain-text "Job error" body instead of JSON, instead of reporting every expected field as "not present".
- **editor:** the column-info dropdown text can now be selected and copied.
- **editor:** table-header buttons and title size to their content rather than fixed grid thirds, so the dataset name/row-count no longer wraps unnecessarily.
- **editor:** `dc.row_status` / `EDIT_STATUS` is no longer submitted to the backend.
## Dependencies / security
- Resolved npm audit vulnerabilities in production dependencies (Angular HttpTransferCache cache-key ambiguity and i18n XSS advisories).
- Aligned all `@angular/*` packages to the same lockstep version (20.3.27) so CI's strict `npm ci` passes.
- Scoped the `brace-expansion` override to its actual vulnerable chain (fixes license-checker breakage).
- Pinned `babel-loader` to resolve an unresolvable peer conflict; regenerated/patched the lockfile to fix `npm ci` sync errors.
- Overrode `nanoid` to 3.3.18 in `sas/` directly (rather than relying on `@sasjs/cli`'s override, which doesn't propagate to consumers) so it survives future `npm install` runs.
Adds HARDFORMULA/SOFTFORMULA DQ rule types, backed by Handsontable's
formulas plugin (gated per-table via hasFormulaRules). parseFormulaRule
substitutes column names with row-relative cell references (quote-aware,
blank-boundary matching per spec) and resolves DC.USER_NAME/DC.ORIG_VALUE
to literal values; applyFormulaRules injects the computed formula per row,
reusing the existing READONLY mechanism for HARDFORMULA.
DC.ROW_STATUS/EDIT_STATUS deliberately deferred - would require prepending
COLHEADERS, which headerColumns has undocumented positional coupling to
elsewhere in editor.component.ts.
- addRow()/insertRowAtPosition() use hot.alter() so HARDFORMULA/SOFTFORMULA
values realign on insert instead of going stale
- New hidden EDIT_STATUS column gives DC.ROW_STATUS a real, live cell
reference; row-header +/-/~ indicator now translates visual->physical
row index so it stays correct when the grid is sorted
- UserService is now providedIn: 'root' instead of module-scoped, fixing
DC.USER_NAME (and any other lazy-module reader) always seeing an unset user
- Column-info dropdown shows the applied formula ("√x=<formula>")
- Formulas' HyperFormula sync resolves a dotted `data` key differently
than getDataAtRowProp/datamap.get() - dataDotNotation: false is needed
for the renamed dc.row_status column to work as a live cell reference
- saveTable() now submits the live computed value for a formula cell, not
the raw '=...' string still sitting in dataSource
- Angular packages resolve within their existing ^20.3.26 range up to
20.3.27, which patches two advisories (HttpTransferCache cache-key
ambiguity, i18n XSS via event-handler attributes)
- brace-expansion (via exceljs -> archiver -> readdir-glob -> minimatch)
needed an override to ^5.0.9 - npm's resolver won't proactively bump a
nested transitive dep that already satisfies its declared semver range,
even when a patched version exists inside that same range
npm audit fix only bumped @angular/core/common/compiler to 20.3.27 (the
packages with a direct advisory), leaving animations/forms/platform-browser*/
router at 20.3.26 - each peer-depends on the others' exact version, so the
mismatch passed a lenient `npm install` locally but failed CI's strict
`npm ci`. All seven now resolve to 20.3.27.
- Angular packages (core/common/compiler/animations/forms/
platform-browser*/router/compiler-cli) all peer-depend on each other's
exact version - npm audit fix only bumped the three with a direct
advisory, leaving the rest mismatched; a plain `npm install` tolerated
it but CI's strict `npm ci` didn't. All now resolve to 20.3.27.
- undici/fast-uri updated via npm audit fix; brace-expansion pinned to
^5.0.9 via overrides (npm's resolver wasn't proactively bumping a
nested transitive dep that already satisfied its declared range)
Incremental `npm install <pkg>@version` patches left the lockfile
internally inconsistent enough that CI's `npm ci` rejected it as out of
sync with package.json (a long list of unrelated nested deps reported
missing). Regenerated from scratch instead of patching further; verified
with a clean `npm ci`, matching the resolved versions from the prior fix.
Fixed by patching only the 12 needed entries directly (9 @angular/*
packages to 20.3.27, brace-expansion to 5.0.9, fast-uri to 3.1.5, undici to 6.28.0) with exact registry resolved/integrity metadata, leaving the other 1925 packages untouched.
An unscoped "brace-expansion": "^5.0.9" override applies to every
occurrence in the tree, not just the vulnerable one - it was silently
replacing the 1.1.16 that glob's bundled minimatch@3.x actually needs,
breaking license-checker with "TypeError: expand is not a function".
Scoped it to the readdir-glob chain (exceljs -> archiver -> readdir-glob
-> minimatch@10.x), the same nested-override pattern already used here
for exceljs's own archiver/unzipper pins.
- Mirrors the review page's toggle: getstagetable.sas now also webouts
fmt_stagetable (SAS-formatted), selectFormattedRows() picks between it
and the raw stagetable, falling back to raw if fmt data is absent
- Adds stage.cy.ts e2e coverage and wires it into both build.yaml and
release.yaml's cypress --spec lists (previously out of sync with
each other on viewbox.cy.ts too)
The Filter/Edit/Upload buttons used btn-block (width: 100%), stretching
them wider than the Cancel/Add Row/Submit buttons shown during edit.
The back/viewboxes, title, and action-button columns were also locked to
equal 12-col grid thirds regardless of actual content, so the dataset
name/row-count wrapped even with visible free space on either side.
Switched the outer columns to size to their content (clr-col-*-auto) and
let the title column flex-grow into whatever space is left.
Handsontable's Menu widget closes on any mouseup inside an item and
unconditionally preventDefault()s contextmenu, even for the info item,
which has no callback and exists purely to show read-only column details.
That made its text impossible to select or right-click-copy. Marked the
item isCommand: false and stop mousedown/mouseup/contextmenu/selectstart
from bubbling past its rendered content to the menu's own listeners.
A misconfigured Viya computeTasks deployment makes the Compute service
return a plain-text "Job error" body instead of JSON. @sasjs/adapter can't
parse it and resolves with that raw text as adapterResponse, so both
startup and manual deploy validation reported every expected field as
"not present" without ever showing the actual cause. Added
getMalformedAdapterResponseMessage to detect a non-object adapterResponse
and surface its real text instead.
Adds right, left, bottom, and bottom-left-corner resize handles
alongside the existing bottom-right corner, and raises resize handle
z-index above Handsontable's frozen header clones so clicks reach the
handle instead of the table underneath it.
@sasjs/cli's own nanoid override doesn't propagate to consumers - npm
only reads overrides from the root project being installed. Adding it
here directly, rather than a raw lockfile patch, so it survives future
npm install runs instead of silently reverting again.
dynamicCellValidation() built its editors/getdynamiccolvals payload
from a raw dataSource row clone, which always carries the client-only
EDIT_STATUS column - unlike saveTable()'s submit path, nothing
stripped it before the request went out. Extracted the strip logic
(previously private to classifyRow) into a shared withoutEditStatus
util and applied it here too.
A key generated for one connection type (http/https) always fails to
decrypt on the other, surfacing only as a generic "invalid key" error
after a round trip to the backend. Detects the mismatch client-side
from the raw key text before the user submits, and disables the apply
button while it's showing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Highlights
Formula-based data quality rules
A major new capability:
HARDFORMULA/SOFTFORMULADQ rule types, backed by Handsontable's formulas plugin (HyperFormula).DC.USER_NAME/DC.ORIG_VALUEto literal values;HARDFORMULAreuses the existing READONLY mechanism.DC.ROW_STATUSvia a hiddenEDIT_STATUScolumn, so formulas can react to a row's edit state; the row-header +/-/~ indicator translates visual→physical row index so it stays correct when the grid is sorted.hot.alter()so formula values realign instead of going stale.UserServiceis nowprovidedIn: 'root', fixingDC.USER_NAME(and other lazy-module readers) always seeing an unset user.saveTable()submits the live computed value rather than the raw=...string.EDIT_STATUS/dc.row_statuscolliding with a real column of the same name, and stops the client-onlyEDIT_STATUScolumn from being submitted to the backend during dynamic cell validation.Cell revert
Revert one cells (or range of cells) to their original (current) value
Formatted / Unformatted toggle on the staging page
Mirrors the review page's toggle.
getstagetable.sasnow also returns SAS-formatted data (fmt_stagetable), and the UI picks between formatted and raw, falling back to raw when formatted data is absent. Added e2e coverage.Viewbox resize on all edges
Viewboxes can now be dragged to resize from all four edges and corners (previously only the bottom-right corner). Resize handles sit above Handsontable's frozen header clones so clicks reach the handle rather than the table underneath.
Licence-key protocol mismatch warning
A licence key generated for one connection type (http/https) always fails to decrypt on the other. The mismatch is now detected client-side from the raw key text before submission, with a warning and the apply button disabled, instead of surfacing only as a generic "invalid key" error after a backend round trip.
Bug fixes
getstagetable.sas.mp_validatefix.dc.row_status/EDIT_STATUSis no longer submitted to the backend.Dependencies / security
@angular/*packages to the same lockstep version (20.3.27) so CI's strictnpm cipasses.brace-expansionoverride to its actual vulnerable chain (fixes license-checker breakage).babel-loaderto resolve an unresolvable peer conflict; regenerated/patched the lockfile to fixnpm cisync errors.nanoidto 3.3.18 insas/directly (rather than relying on@sasjs/cli's override, which doesn't propagate to consumers) so it survives futurenpm installruns.- addRow()/insertRowAtPosition() use hot.alter() so HARDFORMULA/SOFTFORMULA values realign on insert instead of going stale - New hidden EDIT_STATUS column gives DC.ROW_STATUS a real, live cell reference; row-header +/-/~ indicator now translates visual->physical row index so it stays correct when the grid is sorted - UserService is now providedIn: 'root' instead of module-scoped, fixing DC.USER_NAME (and any other lazy-module reader) always seeing an unset user - Column-info dropdown shows the applied formula ("√x=<formula>")View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.