@fressh/react-native-terminal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +182 -0
  2. package/ReactNativeTerminal.podspec +82 -0
  3. package/android/CMakeLists.txt +76 -0
  4. package/android/build.gradle +121 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/src/main/assets/fonts/DejaVuSansMono.ttf +0 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +180 -0
  8. package/android/src/main/java/com/margelo/nitro/fressh/HybridTerminal.kt +146 -0
  9. package/android/src/main/java/com/margelo/nitro/fressh/ReactNativeTerminalModule.kt +43 -0
  10. package/android/src/main/java/com/margelo/nitro/fressh/ReactNativeTerminalPackage.kt +32 -0
  11. package/android/src/main/jniLibs/arm64-v8a/libshim_uniffi.so +0 -0
  12. package/android/src/main/jniLibs/x86_64/libshim_uniffi.so +0 -0
  13. package/babel.config.js +12 -0
  14. package/cpp/README.md +14 -0
  15. package/cpp/generated/shim_uniffi.cpp +4246 -0
  16. package/cpp/generated/shim_uniffi.hpp +364 -0
  17. package/ios/FresshTerminalRenderABI.h +46 -0
  18. package/ios/HybridTerminal.swift +144 -0
  19. package/ios/ReactNativeTerminalUniffi.mm +77 -0
  20. package/ios/fetch-angle.sh +39 -0
  21. package/ios/fonts/DejaVuSansMono.ttf +0 -0
  22. package/libEGL.xcframework/Info.plist +44 -0
  23. package/libEGL.xcframework/ios-arm64/libEGL.framework/Info.plist +0 -0
  24. package/libEGL.xcframework/ios-arm64/libEGL.framework/libEGL +0 -0
  25. package/libEGL.xcframework/ios-arm64_x86_64-simulator/libEGL.framework/Info.plist +0 -0
  26. package/libEGL.xcframework/ios-arm64_x86_64-simulator/libEGL.framework/libEGL +0 -0
  27. package/libGLESv2.xcframework/Info.plist +44 -0
  28. package/libGLESv2.xcframework/ios-arm64/libGLESv2.framework/Info.plist +0 -0
  29. package/libGLESv2.xcframework/ios-arm64/libGLESv2.framework/libGLESv2 +0 -0
  30. package/libGLESv2.xcframework/ios-arm64_x86_64-simulator/libGLESv2.framework/Info.plist +0 -0
  31. package/libGLESv2.xcframework/ios-arm64_x86_64-simulator/libGLESv2.framework/libGLESv2 +0 -0
  32. package/nitro/Terminal.nitro.ts +40 -0
  33. package/nitrogen/generated/.gitattributes +1 -0
  34. package/nitrogen/generated/android/ReactNativeTerminal+autolinking.cmake +83 -0
  35. package/nitrogen/generated/android/ReactNativeTerminal+autolinking.gradle +27 -0
  36. package/nitrogen/generated/android/ReactNativeTerminalOnLoad.cpp +56 -0
  37. package/nitrogen/generated/android/ReactNativeTerminalOnLoad.hpp +34 -0
  38. package/nitrogen/generated/android/c++/JHybridTerminalSpec.cpp +76 -0
  39. package/nitrogen/generated/android/c++/JHybridTerminalSpec.hpp +68 -0
  40. package/nitrogen/generated/android/c++/views/JHybridTerminalStateUpdater.cpp +64 -0
  41. package/nitrogen/generated/android/c++/views/JHybridTerminalStateUpdater.hpp +49 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/HybridTerminalSpec.kt +69 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/ReactNativeTerminalOnLoad.kt +35 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/views/HybridTerminalManager.kt +80 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/views/HybridTerminalStateUpdater.kt +23 -0
  46. package/nitrogen/generated/ios/ReactNativeTerminal+autolinking.rb +62 -0
  47. package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Bridge.cpp +33 -0
  48. package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Bridge.hpp +57 -0
  49. package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Umbrella.hpp +43 -0
  50. package/nitrogen/generated/ios/ReactNativeTerminalAutolinking.mm +33 -0
  51. package/nitrogen/generated/ios/ReactNativeTerminalAutolinking.swift +26 -0
  52. package/nitrogen/generated/ios/c++/HybridTerminalSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridTerminalSpecSwift.hpp +96 -0
  54. package/nitrogen/generated/ios/c++/views/HybridTerminalComponent.mm +132 -0
  55. package/nitrogen/generated/ios/swift/HybridTerminalSpec.swift +57 -0
  56. package/nitrogen/generated/ios/swift/HybridTerminalSpec_cxx.swift +204 -0
  57. package/nitrogen/generated/shared/c++/HybridTerminalSpec.cpp +26 -0
  58. package/nitrogen/generated/shared/c++/HybridTerminalSpec.hpp +68 -0
  59. package/nitrogen/generated/shared/c++/views/HybridTerminalComponent.cpp +110 -0
  60. package/nitrogen/generated/shared/c++/views/HybridTerminalComponent.hpp +113 -0
  61. package/nitrogen/generated/shared/json/TerminalConfig.json +12 -0
  62. package/package.json +97 -0
  63. package/react-native.config.js +12 -0
  64. package/shim_uniffi.xcframework/Info.plist +44 -0
  65. package/shim_uniffi.xcframework/ios-arm64/libshim_uniffi.a +0 -0
  66. package/shim_uniffi.xcframework/ios-arm64_x86_64-simulator/libshim_uniffi.a +0 -0
  67. package/src/Terminal.tsx +135 -0
  68. package/src/generated/shim_uniffi-ffi.ts +320 -0
  69. package/src/generated/shim_uniffi.ts +2527 -0
  70. package/src/index.ts +52 -0
  71. package/src/ssh.ts +239 -0
  72. package/tsconfig.json +31 -0
  73. package/ubrn.config.yaml +24 -0
@@ -0,0 +1,113 @@
1
+ ///
2
+ /// HybridTerminalComponent.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #include <optional>
11
+ #include <NitroModules/NitroDefines.hpp>
12
+ #include <NitroModules/NitroHash.hpp>
13
+ #include <NitroModules/CachedProp.hpp>
14
+ #include <react/renderer/core/ConcreteComponentDescriptor.h>
15
+ #include <react/renderer/core/PropsParserContext.h>
16
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
17
+ #include <react/renderer/components/view/ViewProps.h>
18
+
19
+ #include <string>
20
+ #include <optional>
21
+ #include <memory>
22
+ #include "HybridTerminalSpec.hpp"
23
+ #include <functional>
24
+
25
+ namespace margelo::nitro::fressh::views {
26
+
27
+ using namespace facebook;
28
+
29
+ /**
30
+ * The name of the actual native View.
31
+ */
32
+ extern const char HybridTerminalComponentName[];
33
+
34
+ /**
35
+ * Props for the "Terminal" View.
36
+ */
37
+ class HybridTerminalProps final: public react::ViewProps {
38
+ public:
39
+ HybridTerminalProps() = default;
40
+ HybridTerminalProps(const react::PropsParserContext& context,
41
+ const HybridTerminalProps& sourceProps,
42
+ const react::RawProps& rawProps);
43
+
44
+ public:
45
+ CachedProp<std::string> fontPath;
46
+ CachedProp<std::optional<std::string>> configJson;
47
+ CachedProp<std::optional<std::string>> shellId;
48
+ CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridTerminalSpec>& /* ref */)>>> hybridRef;
49
+
50
+ private:
51
+ static bool filterObjectKeys(const std::string& propName);
52
+ };
53
+
54
+ /**
55
+ * State for the "Terminal" View.
56
+ */
57
+ class HybridTerminalState final {
58
+ public:
59
+ HybridTerminalState() = default;
60
+ explicit HybridTerminalState(const std::shared_ptr<HybridTerminalProps>& props):
61
+ _props(props) {}
62
+
63
+ public:
64
+ [[nodiscard]]
65
+ const std::shared_ptr<HybridTerminalProps>& getProps() const {
66
+ return _props;
67
+ }
68
+
69
+ public:
70
+ #ifdef ANDROID
71
+ HybridTerminalState(const HybridTerminalState& /* previousState */, folly::dynamic /* data */) {}
72
+ folly::dynamic getDynamic() const {
73
+ throw std::runtime_error("HybridTerminalState does not support folly!");
74
+ }
75
+ react::MapBuffer getMapBuffer() const {
76
+ throw std::runtime_error("HybridTerminalState does not support MapBuffer!");
77
+ };
78
+ #endif
79
+
80
+ private:
81
+ std::shared_ptr<HybridTerminalProps> _props;
82
+ };
83
+
84
+ /**
85
+ * The Shadow Node for the "Terminal" View.
86
+ */
87
+ using HybridTerminalShadowNode = react::ConcreteViewShadowNode<HybridTerminalComponentName /* "HybridTerminal" */,
88
+ HybridTerminalProps /* custom props */,
89
+ react::ViewEventEmitter /* default */,
90
+ HybridTerminalState /* custom state */>;
91
+
92
+ /**
93
+ * The Component Descriptor for the "Terminal" View.
94
+ */
95
+ class HybridTerminalComponentDescriptor final: public react::ConcreteComponentDescriptor<HybridTerminalShadowNode> {
96
+ public:
97
+ explicit HybridTerminalComponentDescriptor(const react::ComponentDescriptorParameters& parameters);
98
+
99
+ public:
100
+ /**
101
+ * A faster path for cloning props - reuses the caching logic from `HybridTerminalProps`.
102
+ */
103
+ std::shared_ptr<const react::Props> cloneProps(const react::PropsParserContext& context,
104
+ const std::shared_ptr<const react::Props>& props,
105
+ react::RawProps rawProps) const override;
106
+ #ifdef ANDROID
107
+ void adopt(react::ShadowNode& shadowNode) const override;
108
+ #endif
109
+ };
110
+
111
+ /* The actual view for "Terminal" needs to be implemented in platform-specific code. */
112
+
113
+ } // namespace margelo::nitro::fressh::views
@@ -0,0 +1,12 @@
1
+ {
2
+ "uiViewClassName": "Terminal",
3
+ "supportsRawText": false,
4
+ "bubblingEventTypes": {},
5
+ "directEventTypes": {},
6
+ "validAttributes": {
7
+ "fontPath": true,
8
+ "configJson": true,
9
+ "shellId": true,
10
+ "hybridRef": true
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@fressh/react-native-terminal",
3
+ "version": "0.1.0",
4
+ "description": "Native SSH terminal for React Native: russh + alacritty_terminal engine + native GLES renderer, in one package.",
5
+ "keywords": [
6
+ "alacritty",
7
+ "android",
8
+ "expo",
9
+ "ios",
10
+ "react-native",
11
+ "russh",
12
+ "rust",
13
+ "ssh",
14
+ "terminal"
15
+ ],
16
+ "homepage": "https://github.com/EthanShoeDev/fressh#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/EthanShoeDev/fressh/issues"
19
+ },
20
+ "license": "MIT",
21
+ "author": "EthanShoeDev <13422990+EthanShoeDev@users.noreply.github.com> (https://github.com/EthanShoeDev)",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/EthanShoeDev/fressh.git"
25
+ },
26
+ "source": "./src/index.ts",
27
+ "files": [
28
+ "android/build.gradle",
29
+ "android/CMakeLists.txt",
30
+ "android/fix-prefab.gradle",
31
+ "android/src",
32
+ "ios",
33
+ "cpp",
34
+ "nitro",
35
+ "nitrogen",
36
+ "src",
37
+ "*.podspec",
38
+ "*.xcframework/**",
39
+ "react-native.config.js",
40
+ "*.md",
41
+ "*.config.*",
42
+ "tsconfig*.json",
43
+ "LICENSE"
44
+ ],
45
+ "type": "module",
46
+ "main": "./src/index.ts",
47
+ "types": "./src/index.ts",
48
+ "react-native": "./src/index.ts",
49
+ "exports": {
50
+ ".": {
51
+ "types": "./src/index.ts",
52
+ "react-native": "./src/index.ts",
53
+ "default": "./src/index.ts"
54
+ },
55
+ "./package.json": "./package.json"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
60
+ "scripts": {
61
+ "tsgo:check": "tsgo --noEmit",
62
+ "nitro:codegen": "nitrogen",
63
+ "//native": "Single .so (§8): cargo-ndk builds the shim-uniffi staticlib (uniffi control plane + render C-ABI, sharing fressh-core's registry statics); the umbrella android/CMakeLists links it + the ubrn-generated C++ + the Nitro C++ into libReactNativeTerminal.so.",
64
+ "build:android": "cd rust && cargo ndk -t arm64-v8a -t x86_64 -o ../android/src/main/jniLibs build -p shim-uniffi --release",
65
+ "//build:ios": "iOS analogue of build:android (§1/§8): cargo-build shim-uniffi for device + sim, lipo the sim slices, package one shim_uniffi.xcframework the podspec links. JSI bindings (cpp/generated) come from the cross-platform ubrn:generate, so the xcframework needs no headers — just the staticlib.",
66
+ "build:ios": "cd rust && ./build-ios.sh",
67
+ "//angle:fetch": "Vendor prebuilt ANGLE (libEGL/libGLESv2, Metal) for iOS — the GLES2 driver the renderer runs over (§2/§5). Gitignored xcframeworks at the package root; run once (and after bumping the pinned tag).",
68
+ "angle:fetch": "./ios/fetch-angle.sh",
69
+ "//ubrn:generate": "Reads uniffi metadata from the HOST staticlib (.a) — the canonical ubrn library-mode input (ubrn itself uses .a for the android/ios targets). The .a name is OS-independent; the cdylib name is NOT (.so on Linux, .dylib on macOS), so hardcoding .so built fine on Linux but broke macOS. Do NOT change back to a platform-specific cdylib extension.",
70
+ "ubrn:generate": "cd rust && ubrn generate jsi bindings --library --ts-dir ../src/generated --cpp-dir ../cpp/generated target/debug/libshim_uniffi.a",
71
+ "//ubrn:build:ios": "ubrn build ios --and-generate --release",
72
+ "cargo:build": "cd rust && cargo build",
73
+ "cargo:lint:fix": "cd rust && just lint",
74
+ "cargo:lint:check": "cd rust && just lint-check",
75
+ "cargo:fmt:fix": "cd rust && just fmt",
76
+ "cargo:fmt:check": "cd rust && just fmt-check",
77
+ "cargo:test": "cd rust && just test",
78
+ "cargo:update": "cd rust && just update-deps"
79
+ },
80
+ "dependencies": {
81
+ "@ubjs/core": "0.31.0-3",
82
+ "uniffi-bindgen-react-native": "0.31.0-3"
83
+ },
84
+ "devDependencies": {
85
+ "@types/react": "catalog:",
86
+ "nitrogen": "0.35.9",
87
+ "react": "catalog:",
88
+ "react-native": "catalog:",
89
+ "react-native-nitro-modules": "0.35.9",
90
+ "typescript": "catalog:"
91
+ },
92
+ "peerDependencies": {
93
+ "react": ">=19.0.0",
94
+ "react-native": ">=0.85.0",
95
+ "react-native-nitro-modules": ">=0.35.0"
96
+ }
97
+ }
@@ -0,0 +1,12 @@
1
+ const path = require('node:path');
2
+
3
+ module.exports = {
4
+ dependency: {
5
+ platforms: {
6
+ android: { sourceDir: 'android' },
7
+ // Autolink the hand-authored podspec (no xcodeproj — the pod compiles
8
+ // our ios/** + the generated bindings and links shim_uniffi.xcframework, §8).
9
+ ios: { podspecPath: path.join(__dirname, 'ReactNativeTerminal.podspec') },
10
+ },
11
+ },
12
+ };
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>AvailableLibraries</key>
6
+ <array>
7
+ <dict>
8
+ <key>BinaryPath</key>
9
+ <string>libshim_uniffi.a</string>
10
+ <key>LibraryIdentifier</key>
11
+ <string>ios-arm64</string>
12
+ <key>LibraryPath</key>
13
+ <string>libshim_uniffi.a</string>
14
+ <key>SupportedArchitectures</key>
15
+ <array>
16
+ <string>arm64</string>
17
+ </array>
18
+ <key>SupportedPlatform</key>
19
+ <string>ios</string>
20
+ </dict>
21
+ <dict>
22
+ <key>BinaryPath</key>
23
+ <string>libshim_uniffi.a</string>
24
+ <key>LibraryIdentifier</key>
25
+ <string>ios-arm64_x86_64-simulator</string>
26
+ <key>LibraryPath</key>
27
+ <string>libshim_uniffi.a</string>
28
+ <key>SupportedArchitectures</key>
29
+ <array>
30
+ <string>arm64</string>
31
+ <string>x86_64</string>
32
+ </array>
33
+ <key>SupportedPlatform</key>
34
+ <string>ios</string>
35
+ <key>SupportedPlatformVariant</key>
36
+ <string>simulator</string>
37
+ </dict>
38
+ </array>
39
+ <key>CFBundlePackageType</key>
40
+ <string>XFWK</string>
41
+ <key>XCFrameworkFormatVersion</key>
42
+ <string>1.0</string>
43
+ </dict>
44
+ </plist>
@@ -0,0 +1,135 @@
1
+ import { type ComponentProps, type Ref, useMemo } from 'react';
2
+ import { PixelRatio } from 'react-native';
3
+ import { getHostComponent, type HybridRef } from 'react-native-nitro-modules';
4
+
5
+ import TerminalViewConfig from '../nitrogen/generated/shared/json/TerminalConfig.json';
6
+ import type { TerminalMethods, TerminalProps } from '../nitro/Terminal.nitro';
7
+
8
+ /** Ref handle for imperative methods on the native terminal view. */
9
+ export type TerminalRef = HybridRef<TerminalProps, TerminalMethods>;
10
+
11
+ /** Cursor shapes the renderer can draw (config override; see fressh-render). */
12
+ export type CursorStyle = 'block' | 'beam' | 'underline' | 'hollow';
13
+
14
+ /** Cursor blink modes (see fressh-render `CursorBlink`). `never`/`always` force
15
+ * the behaviour; `off`/`on` defer to the program's escape sequences. */
16
+ export type CursorBlink = 'never' | 'off' | 'on' | 'always';
17
+
18
+ /**
19
+ * Friendly, logical-unit terminal config (the "assemble in RN, pass it" object).
20
+ * The wrapper scales sizes by device pixel density and serializes to the native
21
+ * `configJson` string prop. All fields optional — omitted ones use renderer
22
+ * defaults. `colorScheme` names must match the Rust presets (see `ColorScheme::
23
+ * by_name`): 'default' | 'solarizedDark' | 'solarizedLight' | 'dracula' |
24
+ * 'gruvboxDark'.
25
+ */
26
+ export interface TerminalRenderConfig {
27
+ /** Font size in logical points. */
28
+ fontSize?: number;
29
+ /** Inner padding in logical points, applied to both axes. */
30
+ padding?: number;
31
+ cursorStyle?: CursorStyle;
32
+ /** Cursor blink mode (the live override). Default `off`. */
33
+ cursorBlink?: CursorBlink;
34
+ /** Blink half-period in ms. Default 750. */
35
+ blinkInterval?: number;
36
+ /** Stop blinking after this many seconds without input (cursor stays solid).
37
+ * `0` disables the timeout (blink forever). Default 5. */
38
+ blinkTimeout?: number;
39
+ colorScheme?: string;
40
+ /** Draw bold text using the bright color variants. */
41
+ boldIsBright?: boolean;
42
+ }
43
+
44
+ /** Default font size (logical points) when `config.fontSize` is unset. */
45
+ const DEFAULT_FONT_PT = 16;
46
+
47
+ /** Serialize the friendly config to the native wire format (physical px). */
48
+ function buildConfigJson(config: TerminalRenderConfig | undefined) {
49
+ const density = PixelRatio.get();
50
+ const fontPt =
51
+ config?.fontSize && config.fontSize > 0 ? config.fontSize : DEFAULT_FONT_PT;
52
+ const padPt = config?.padding ?? 0;
53
+ return JSON.stringify({
54
+ fontSizePx: fontPt * density,
55
+ paddingXPx: padPt * density,
56
+ paddingYPx: padPt * density,
57
+ cursorStyle: config?.cursorStyle ?? 'block',
58
+ cursorBlink: config?.cursorBlink ?? 'off',
59
+ blinkIntervalMs:
60
+ config?.blinkInterval && config.blinkInterval > 0
61
+ ? config.blinkInterval
62
+ : 750,
63
+ // `?? 5` (not `||`) so an explicit 0 (no timeout) is preserved.
64
+ blinkTimeoutS: config?.blinkTimeout ?? 5,
65
+ colorScheme: config?.colorScheme ?? 'default',
66
+ boldIsBright: config?.boldIsBright ?? true,
67
+ });
68
+ }
69
+
70
+ const NativeTerminal = getHostComponent<TerminalProps, TerminalMethods>(
71
+ 'Terminal',
72
+ () => TerminalViewConfig,
73
+ );
74
+
75
+ /** Props for the JS `<Terminal>` wrapper: the native component's props (incl. RN
76
+ * view props like `style`) minus the wire `configJson`, plus the friendly `config`
77
+ * object. */
78
+ export type TerminalComponentProps = Omit<
79
+ ComponentProps<typeof NativeTerminal>,
80
+ 'configJson'
81
+ > & {
82
+ config?: TerminalRenderConfig;
83
+ };
84
+
85
+ /**
86
+ * Native terminal view (Nitro HybridView). Renders the durable `Term` that
87
+ * fressh-core keeps for `shellId`. Accepts standard RN view props (e.g. `style`)
88
+ * and a `config` object that is scaled + serialized to the renderer.
89
+ */
90
+ export function Terminal({
91
+ config,
92
+ ref,
93
+ ...props
94
+ }: TerminalComponentProps & { ref?: Ref<TerminalRef> }) {
95
+ // Destructure the fields buildConfigJson reads so the memo depends on their
96
+ // values, not the (per-render) `config` object identity.
97
+ const {
98
+ fontSize,
99
+ padding,
100
+ cursorStyle,
101
+ cursorBlink,
102
+ blinkInterval,
103
+ blinkTimeout,
104
+ colorScheme,
105
+ boldIsBright,
106
+ } = config ?? {};
107
+ const configJson = useMemo(
108
+ () =>
109
+ buildConfigJson({
110
+ fontSize,
111
+ padding,
112
+ cursorStyle,
113
+ cursorBlink,
114
+ blinkInterval,
115
+ blinkTimeout,
116
+ colorScheme,
117
+ boldIsBright,
118
+ }),
119
+ [
120
+ fontSize,
121
+ padding,
122
+ cursorStyle,
123
+ cursorBlink,
124
+ blinkInterval,
125
+ blinkTimeout,
126
+ colorScheme,
127
+ boldIsBright,
128
+ ],
129
+ );
130
+ // The runtime ref is the Nitro HybridRef (TerminalRef); the host-component's
131
+ // TS ref type is looser, so cast to keep the public contract.
132
+ return (
133
+ <NativeTerminal ref={ref as never} {...props} configJson={configJson} />
134
+ );
135
+ }