npm brs-engine 2.3.0
v2.3.0 - SG Node Complete and CLI task threads

5 hours ago

This release brings 160+ improvements and fixes, it completes the SceneGraph node set — every concrete node documented by Roku is now implemented, including support for Component Libraries. This version also rewrites the CLI on a worker-thread architecture where the app and each SceneGraph Task run on their own thread, adding keyboard control, terminal image rendering (--image), log redirection and screen snapshots. The core engine gains a production mode that removes all debug instrumentation by default, a texture memory registry exposed through the query/r2d2-bitmaps ECP endpoint, full .bpk encryption covering SceneGraph components and the package container, and several Roku OS 15.2/15.3 APIs. Read the full release notes below for more details.

brs-engine-2.3.0-scenegraph-demo.mov

Release Changes

Core Engine

  • (brs) Updated setHttpVersion() signature to accept a version string instead of a boolean by @lvcabral in #920
  • (draw2d) Fixed the 9-patch corner insets to be parsed from the marker start and end by @lvcabral in #933
  • (draw2d) Fixed blendColor to apply the RGB tint at full strength regardless of alpha by @lvcabral in #935
  • (draw2d) Clamp the 9-patch render size to the fixed corners by @lvcabral in #950
  • (brs) Added mocks for roAudioGuide, roTextToSpeech, roMicrophone, roDSA and roRSA by @lvcabral in #956
  • (brs) Made bs_const lookups case-insensitive in #if/#elseif conditions by @markwpearce in #962
  • (brs) Added a global texture memory registry, exposed via the query/r2d2-bitmaps ECP endpoint and the API requestBitmaps() method, and introduced the production mode that gates all debug instrumentation behind debugOnCrash by @lvcabral in #967
  • (brs) Propagate the optional state through dot-bracket chaining (?.[) by @markwpearce in #969
  • (brs) Properly escape strings in FormatJSON() by @lvcabral in #973
  • (brs) Matched Roku's type() results for String vs roString by @lvcabral in #977
  • (brs) Fixed an edge case for numeric values in Type() by @lvcabral in #986
  • (brs) Added the Roku OS 15.3 roUtils predicates isNumber(), isString() and isFloatingPoint() by @lvcabral in #988
  • (brs) Added the Roku OS 15.3 roAssociativeArray.values() method by @lvcabral in #989
  • (brs) Added the Roku OS 15.3 roTimeSpan methods totalMillisecondsLong() and totalMicrosecondsLong() by @lvcabral in #990
  • (brs) Fixed anonymous functions not being resolvable by the $anon_N name reported by toStr(), so observers registered by function name (e.g. by the promises library used in Rooibos node suites) work as on a real device by @lvcabral in #993
  • (brs) Added the Roku OS 15.2 roUtils.hasComponent() method by @lvcabral in #994
  • (brs) Added the roDeviceInfo methods isHDMIConnected() and isPassthruCodecActive() by @lvcabral in #995
  • (brs) Keep roAssociativeArray enumeration stable when replacing an existing key's value by @lvcabral in #997
  • (brs) Resolve node methods in call position when a field shadows the method name by @lvcabral in #998
  • (brs) Allow any numeric type on the index type check when setting array values by @lvcabral in #1012
  • (brs) roTextureManager only warns once for a repeatedly-failing local texture by @markwpearce in #1016
  • (draw2d) Accept single-axis 9-patch images (stretch marker on only one axis) by @lvcabral in #1024
  • (brs) Properly handle boxed String in roArray, roAudioPlayer and roVideoPlayer by @lvcabral in #1050
  • (brs) Fixed the Micro Debugger to halt all threads while a debug session is active, so threads blocked in a cross-thread rendezvous no longer time out with a spurious "Rendezvous timeout" by @lvcabral in #1054
  • (brs) Handle boxed numbers in for loop bounds and if/while conditions by @lvcabral in #1058
  • (brs) Fixed roAssociativeArray behavior when handling keys vs interface methods by @lvcabral in #1071
  • (brs) Validate string values in roRegistrySection.writeMulti() by @lvcabral in #1072
  • (brs) Percent-encode reserved URI characters in roUrlTransfer escape() and urlEncode() by @markwpearce in #1073
  • (brs) Handle an empty POST body in roUrlTransfer.postFromStringAsync() by @lvcabral in #1074
  • (brs) Follow HTTP redirects in the synchronous download path by @lvcabral in #1082

Browser API

  • (api) Enable the SceneGraph extension when running a single .brs file that uses roSGNode or roSGScreen by @lvcabral in #980
  • (api) Enforce a minimum video buffering floor before playback starts by @lvcabral in #1033
  • (api) Reliable media event and buffering-progress delivery for preview playback by @lvcabral in #1041
  • (api) Prevent the space key from triggering page down in browsers by @lvcabral in #1084
  • (api) Prevent the browser default action on held keys by @lvcabral in #1085

Node.js Library and CLI

  • (cli) Run a folder app from --root alone, loading source/ and serving the components/ tree from the mounted pkg:/ volume by @lvcabral in #960
  • (cli) Encrypt SceneGraph component files (.brs and .xml, including inline <script> blocks) into .bpk packages, prune the emptied components/ tree, and wrap the whole package container in AES-256-CTR so plaintext assets are unreadable at rest by @lvcabral in #964
  • (cli) Moved to node-canvas v4 with a worker-thread architecture (the app and every SceneGraph Task run on their own worker) and added terminal frame rendering with the --image mode (iTerm2/Kitty protocols with ANSI fallback), plus --log and --snapshot by @lvcabral in #1083

SceneGraph Extension (brs-scenegraph release v0.3.0)

  • (rsg) Added width/height fields to the Video node and suppress its UI when windowed by @lvcabral in #928
  • (rsg) Index RowList per-row arrays by the absolute row index by @lvcabral in #930
  • (rsg) Implemented MaskGroup alpha-mask compositing by @lvcabral in #931
  • (rsg) Apply the uri_resolution_autosub manifest config more robustly by @lvcabral in #932
  • (rsg) Update the global focus pointer before clearing the old focus chain by @lvcabral in #934
  • (rsg) Implemented support for Component Libraries by @lvcabral in #936
  • (rsg) Added the allowBackgroundTask field to Scene with a warning for unsupported Instant Resume by @lvcabral in #937
  • (rsg) Load Component Libraries created at runtime by @lvcabral in #938
  • (rsg) Hardened the cross-thread rendezvous (fidelity and reliability) by @lvcabral in #939
  • (rsg) Rendezvous Phase 2 — roRenderThreadQueue and device-accurate timeouts by @lvcabral in #940
  • (rsg) Rendezvous Phase 3a — direct render→task responses (feature-flagged) by @lvcabral in #941
  • (rsg) Rendezvous Phase 3 — finalized the direct render→task responses by @lvcabral in #942
  • (rsg) Restored synchronous (depth-first) observer dispatch with a per-field re-entrancy guard, fixing blank button labels by @lvcabral in #943
  • (rsg) Implemented the Standard Dialog Framework nodes by @lvcabral in #944
  • (rsg) Implemented the SimpleLabel node by @lvcabral in #945
  • (rsg) Implemented the MultiStyleLabel and StdDlgMultiStyleTextItem nodes by @lvcabral in #947
  • (rsg) Implemented the MonospaceLabel node by @lvcabral in #948
  • (rsg) Implemented the TimeGrid node (EPG) by @lvcabral in #949
  • (rsg) Implemented the TargetSet, TargetGroup and TargetList nodes by @lvcabral in #951
  • (rsg) Implemented the Dynamic voice keyboard nodes by @lvcabral in #952
  • (rsg) Use the Dynamic keyboards in the Standard dialog framework by @lvcabral in #953
  • (rsg) Implemented the roRenderThreadQueue component (OS 15) by @lvcabral in #954
  • (rsg) Prevent a stack overflow from re-entrant render and the ContentNode parentField cascade by @lvcabral in #959
  • (rsg) Trim script.val in getScripts() to ignore whitespace-only bodies by @markwpearce in #961
  • (rsg) Added support for associativearray in FieldKind and the value type checks by @lvcabral in #963
  • (rsg) Give focus to a custom StandardDialog's buttons by @lvcabral in #968
  • (rsg) Corrected UserSelect focus traversal, button centering and grid focus feedback by @lvcabral in #970
  • (rsg) Attach a list item component to its parent before init() by @lvcabral in #971
  • (rsg) Implemented the Video node bufferingBar and retrievingBar fields by @lvcabral in #972
  • (rsg) Hug the LabelList focus frame instead of overflowing rows by @lvcabral in #976
  • (rsg) Only prevent duplicate fields when those are defined in XML by @lvcabral in #978
  • (rsg) Box AA/Array node-field content the way Roku does (literal-aware) by @lvcabral in #979
  • (rsg) Fixed issues rendering Task-loaded content in grids by @lvcabral in #982
  • (rsg) Cut per-node memory for large content trees with lazy fields and lazy method Callables by @lvcabral in #983
  • (rsg) SceneGraph list, animation and format fixes for the hero-grid and living-room samples by @lvcabral in #984
  • (rsg) RowList rendering fixes — item sizing, counter, spacing, labels, wrap and clipping by @lvcabral in #985
  • (rsg) Added the Roku OS 15.3 Video.captionRenderArea field with render-thread caption positioning; captions now only render during full-screen playback, as on a device by @lvcabral in #991
  • (rsg) Added regression coverage for double SceneGraph fields (OS 15.3) by @lvcabral in #992
  • (rsg) Resolve cross-thread nodes by address via a registry by @lvcabral in #996
  • (rsg) Added the ifSGNodeBoundingRect sub-part methods by @lvcabral in #999
  • (rsg) Guard the bounding-rect refresh renders against re-entrancy by @lvcabral in #1000
  • (rsg) Mirror a remote field add/remove on the local node copy by @lvcabral in #1001
  • (rsg) Added the scrollingStatus field to the ArrayGrid based nodes by @lvcabral in #1002
  • (rsg) Compute bounding rects for nodes under invisible ancestors by @lvcabral in #1003
  • (rsg) A failed interface field alias no longer aborts the component's remaining fields by @lvcabral in #1004
  • (rsg) Skip grid slots whose item component failed to create by @lvcabral in #1005
  • (rsg) findNode() searches breadth-first per the ifSGNodeDict spec by @lvcabral in #1006
  • (rsg) The media-node serialization proxy no longer hijacks the singleton player by @lvcabral in #1007
  • (rsg) Video node prebuffer, configurable autoplay, buffering-step delivery and active-player routing by @lvcabral in #1008
  • (rsg) Poster commits the uri field before its synchronous load and notification by @lvcabral in #1010
  • (rsg) RowList keeps currFocusRow/currFocusColumn in sync with the focused item by @lvcabral in #1011
  • (rsg) Implemented the ifSGNodeBoundingRect method ancestorSubBoundingRect() by @lvcabral in #1013
  • (rsg) DynamicKeyboard palette support and shared palette resolution by @lvcabral in #1014
  • (rsg) Corrected button and list layout — 9-patch width, variable-width rows, icon measure and item focus by @lvcabral in #1015
  • (rsg) Reliable PanelSet next-panel creation and focusable-based navigation by @lvcabral in #1017
  • (rsg) Re-apply the grid item size each frame and fixed WebP images with dropped bottom rows by @lvcabral in #1019
  • (rsg) update() converts a nested {subtype:...} AA to a node on any field, not just children[] by @markwpearce in #1020
  • (rsg) Include the inter-item spacing in the grid boundingRect() extent by @lvcabral in #1021
  • (rsg) Honor clippingRect to limit node and children rendering by @lvcabral in #1022
  • (rsg) Re-measure a degenerate bounding rect (either dimension zero) mid-render by @lvcabral in #1023
  • (rsg) Silence the local mirror of a rendezvous field add so observers fire once by @lvcabral in #1025
  • (rsg) Don't apply Label vertAlign when wrapped text has no explicit height by @lvcabral in #1026
  • (rsg) Apply an interface field's default value through its alias targets by @lvcabral in #1027
  • (rsg) Emit vertFocusDirection with settle-last focus order and honor Video.asyncStopSemantics by @lvcabral in #1028
  • (rsg) Resolve the RowList/ZoomRowList focused-item subBoundingRect() and corrected the vertical outset by @lvcabral in #1029
  • (rsg) Span the focus-feedback footprint in a RowList focused item's subBoundingRect() by @lvcabral in #1030
  • (rsg) Defer reentrant field observers and refresh subBoundingRect() on a pending focus change by @lvcabral in #1032
  • (rsg) Honor vertAlign in ScrollingLabel by centering against the node height by @lvcabral in #1034
  • (rsg) Honor numeric-string writes to ArrayGrid numColumns/numRows by @lvcabral in #1035
  • (rsg) Honor descending key arrays in the FieldInterpolator segment resolution by @lvcabral in #1036
  • (rsg) Fire the initial itemFocused when list/grid content is populated after assignment by @lvcabral in #1037
  • (rsg) Clip ArrayGrid/PosterGrid items to their cell so overflow is hidden by @lvcabral in #1038
  • (rsg) Fire itemFocused only when the list/grid is in the focus chain by @lvcabral in #1039
  • (rsg) Fixed the regression caused by #1037 affecting ArrayGrid by @lvcabral in #1040
  • (rsg) Only defer reentrant observers for engine-initiated field emissions by @lvcabral in #1042
  • (rsg) Honor Library statements in component scripts and completed the RAF mock by @lvcabral in #1043
  • (rsg) Detach a node from its previous parent when it is attached elsewhere by @lvcabral in #1044
  • (rsg) ButtonBar sample — RowList fit check, LayoutGroup re-alignment and single-row grid key propagation by @lvcabral in #1045
  • (rsg) ZoomRowList vertical-wrap flag and row-title/poster spacing by @lvcabral in #1046
  • (rsg) Only the owning, presenting Video node renders the video plane by @lvcabral in #1047
  • (rsg) Added the missing secondaryTitle field to ContentNode by @lvcabral in #1048
  • (rsg) Changed the ContentNode.subtitleTracks type from assocarray to array by @lvcabral in #1051
  • (rsg) Properly handle boxed String in PosterGrid, ScrollableText and ZoomRowList by @lvcabral in #1052
  • (rsg) Position the PanelSet right panel using the left panel's leftPosition by @lvcabral in #1053
  • (rsg) Guard the cross-thread serialization against circular container references by @lvcabral in #1055
  • (rsg) Paint a black plane while a full-screen Video with UI enabled is buffering by @lvcabral in #1056
  • (rsg) Restore function values across Task threads from the component AST by @lvcabral in #1057
  • (rsg) Keep a Video's internal children off the cross-thread serialization by @lvcabral in #1059
  • (rsg) Keep ButtonGroup from managing custom (non-Button) Group children by @lvcabral in #1060
  • (rsg) Restore the m context on rebuilt custom nodes and fixed the quit debugger loop by @lvcabral in #1061
  • (rsg) Honor the app-managed visibility/opacity of a dynamic keyboard's textEditBox by @lvcabral in #1062
  • (rsg) Resolve the fixed vertFocusAnimationStyle to the non-wrapping fixedFocus by @lvcabral in #1063
  • (rsg) Return the measured size from a detached Label's boundingRect() mid-render by @lvcabral in #1064
  • (rsg) Move the live focus when a managed ButtonGroup's focusButton changes by @lvcabral in #1065
  • (rsg) Measure hidden grids, flush MarkupGrid rects and reorder on re-append by @lvcabral in #1066
  • (rsg) Keep the video plane presenting through a mid-stream re-buffer by @lvcabral in #1067
  • (rsg) PanelSet full-screen preview position, focus handling and back-navigation panel retention by @lvcabral in #1068
  • (rsg) PanelSet sets the attached Panel height per the Roku spec by @lvcabral in #1069
  • (rsg) Let single-row MarkupGrid vertical keys bubble and honor horizFocusAnimationStyle fixedFocus by @lvcabral in #1070
  • (rsg) Decouple the Timer polling from the frame-rate-limiting busy-wait by @markwpearce in #1076
  • (rsg) parentSubtype() returns Invalid instead of "" at the hierarchy root by @markwpearce in #1077
  • (rsg) Reset the key buffer when roSGScreen closes, to prevent a stale key leaking into the next screen by @markwpearce in #1081

Chores, Build and Dependencies

  • Bump follow-redirects from 1.15.11 to 1.16.0 by @dependabot[bot] in #917
  • Bump uuid from 9.0.1 to 14.0.0 by @dependabot[bot] in #918
  • Bump fast-uri from 3.1.0 to 3.1.2 by @dependabot[bot] in #921
  • Bump webpack-dev-server from 5.2.3 to 5.2.4 by @dependabot[bot] in #923
  • Bump ws from 8.18.3 to 8.20.1 by @dependabot[bot] in #924
  • Bump qs and express by @dependabot[bot] in #925
  • (chore) Added CLAUDE.md guidance for Claude Code by @lvcabral in #927
  • (chore) Added the Roku reference docs (dev-doc submodule) and the brs-reference skill by @lvcabral in #929
  • Bump shell-quote from 1.8.3 to 1.8.4 by @dependabot[bot] in #946
  • Bump launch-editor from 2.12.0 to 2.14.1 by @dependabot[bot] in #955
  • Bump ws by @dependabot[bot] in #957
  • Bump webpack-dev-server from 5.2.4 to 5.2.5 by @dependabot[bot] in #958
  • (test) Exercise a different case in the negated bsConst test by @lvcabral in #965
  • (chore) Enforce no-case-declarations and brace switch cases that declare bindings by @lvcabral in #966
  • (chore) Added new ESLint rules and fixed all instances by @lvcabral in #974
  • (chore) Enhanced the ESLint rules and refactored code for consistency by @lvcabral in #975
  • (chore) TypeScript 7 readiness — bundler moduleResolution, explicit types, drop baseUrl by @lvcabral in #981
  • (chore) Updated the dev-doc submodule to the latest v2.0 and added the docs:update script by @lvcabral in #987
  • (docs) Simplified and shortened CLAUDE.md by @lvcabral in #1018
  • Bump websocket-driver from 0.7.4 to 0.7.5 by @dependabot[bot] in #1031
  • Bump brace-expansion from 5.0.6 to 5.0.7 by @dependabot[bot] in #1075
  • Bump shell-quote from 1.8.4 to 1.10.0 by @dependabot[bot] in #1078
  • Bump fast-uri from 3.1.2 to 3.1.4 by @dependabot[bot] in #1079
  • Bump uuid and webpack-dev-server by @dependabot[bot] in #1080

Full Changelog: v2.2.0...v2.3.0

Don't miss a new brs-engine release

NewReleases is sending notifications on new releases.