feat: Add interactive browser-based playground and component gallery#1646
feat: Add interactive browser-based playground and component gallery#1646Subhash21022 wants to merge 2 commits into
Conversation
|
Hi @Subhash21022 👋 ⭐ Star this repo before your PR merges. Why? GSSoC 2026 contributors who star get priority review and points credit. After you star, push any commit (or re-run this check). The Thanks for your contribution to TermUI. |
There was a problem hiding this comment.
🎉 Thanks for your first PR to TermUI, @Subhash21022.
Before your PR merges:
- ⭐ Star the repo. Required. The
star-checkjob blocks your merge otherwise. - ✅ All checks green:
build,test,typecheck. - 🏷 PR title follows
type: short description. Example:fix: handle empty list. - 🔗 Link your closing issue in the description.
GSSoC 2026 points come from labels after merge:
gssoc:approved. +50 base points.level:beginner/intermediate/advanced/critical. +20 / +35 / +55 / +80.quality:clean/exceptional. x 1.2 / x 1.5.type:*. Stackable bonus.
Your reviewer responds within 48 hours. Ping @Karanjot786 on Discord for urgent help.
🚀 Welcome to the cohort.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
💤 Files with no reviewable changes (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe PR replaces the static TermUI website with a React-based interactive playground. It adds a ChangesInteractive TermUI Playground
Sequence Diagram(s)sequenceDiagram
participant User
participant Playground as Playground Component
participant Babel as `@babel/standalone`
participant Eval as Sandboxed eval()
participant TermUI as TermUI App/Box
participant Xterm as xterm.js Terminal
User->>Playground: selects example or edits code
Playground->>Babel: transpile TypeScript+JSX source
Babel-->>Playground: transpiled JS string
Playground->>Eval: execute with custom require() scope
Eval-->>Playground: default export (component function)
Playground->>TermUI: mount(component, Box) with mock stdout/stdin
TermUI->>Xterm: write ANSI sequences via mock stdout
User->>Xterm: key input
Xterm->>TermUI: feed to EventEmitter stdin
TermUI->>TermUI: setRequestRender triggers re-reconcile
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
3 similar comments
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@website/src/components/Playground.tsx`:
- Around line 186-208: The Playground component sets global JSX runtime state
with termuijsJsx.setCurrentApp(app) and termuijsJsx.setRequestRender(...) but
does not clean them up during unmount or on error paths, which can leave stale
callbacks pointing to dead app instances. Add a cleanup function to the
useEffect hook that resets both termuijsJsx.setCurrentApp and
termuijsJsx.setRequestRender to null or their initial empty states, ensuring
this cleanup is called when the effect unmounts or when the app state changes.
- Line 21: Change the Playground component in
website/src/components/Playground.tsx from a default export to a named export by
replacing export default function Playground() with export function
Playground(). Then update the corresponding import statement in
website/src/main.tsx line 3 to use a named import syntax instead of a default
import, importing Playground as a named export rather than as the default
export.
- Around line 169-175: The onData subscription in the Playground component is
not being cleaned up when the effect reruns, causing multiple handlers to
accumulate and keystrokes to be emitted repeatedly. Store the IDisposable object
returned by xtermRef.current.onData() in a variable, and then return a cleanup
function from the useEffect that calls .dispose() on this subscription. This
ensures each handler is properly cleaned up before a new one is registered when
the code dependency changes.
- Around line 127-128: The code execution in the Function constructor at lines
where `new Function` is instantiated and `fn(...Object.values(scope))` is called
executes editor input directly in the main page context, exposing DOM, storage,
and network access to the docs origin. Refactor this by creating an isolated
sandbox boundary using a sandboxed iframe or Web Worker, then establish a
message bridge to communicate between the main Playground component and the
sandbox. Move the Function creation and execution logic to run inside the
sandbox instead of the main context, passing the termuiTranspiled code and scope
data through the message bridge, and receive execution results back through the
same bridge to maintain the component's functionality while preventing access to
browser APIs and sensitive resources.
- Line 29: Replace the `useRef<any>(null)` type parameter in the termuiAppRef
declaration with the proper type `useRef<termuijsCore.App | null>` to provide
explicit typing for the ref instead of bypassing type safety with the any type.
This ensures the ref is properly typed to hold either a termuijsCore.App
instance or null value.
In `@website/src/empty.ts`:
- Line 1: Remove the default export statement from the empty.ts file. The line
containing `export default {};` violates the TypeScript guideline that requires
using named exports only and never using default exports. Delete this entire
line from the file, as the default export is not being used anywhere in the
codebase and conflicts with the established coding standards.
In `@website/src/main.tsx`:
- Around line 6-7: The code at line 6 uses a non-null assertion on
document.getElementById('app') without an explicit guard clause, which violates
strict TypeScript guidelines and creates a potential runtime crash path. Remove
the non-null assertion operator (!) and replace it with an explicit guard clause
that checks if the app element exists. If the element is not found, throw an
error or handle the missing element appropriately before passing it to the
createRoot function.
In `@website/src/style.css`:
- Line 4: The font-family property in the style.css file has unnecessary quotes
around the Inter font name, which violates the Stylelint font-family-name-quotes
rule. Remove the single quotes surrounding Inter in the font-family declaration
so it reads Inter instead of 'Inter'. Since the font name contains no
whitespace, digits, or special characters, the quotes are not required per the
always-where-recommended Stylelint rule.
In `@website/vite.config.ts`:
- Around line 25-26: Remove both the 'path' and 'node:path' alias entries from
the alias configuration in vite.config.ts. These aliases redirect to an empty
stub file which contradicts the nodePolyfills plugin configuration that is
explicitly enabled to provide proper path module support. Removing these aliases
will allow nodePolyfills to provide a working implementation of the path module
and prevent potential bugs if future code attempts to use path module APIs like
join, resolve, dirname, or basename.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d982729f-8afd-4910-a918-2e95a4b2bf98
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.lockpackages/core/src/terminal/__snapshots__/snapshot.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (13)
.gitignorepackage.jsonpackages/jsx/src/index.tsscripts/install-theme.jswebsite/index.htmlwebsite/package.jsonwebsite/src/components/Playground.tsxwebsite/src/empty.tswebsite/src/examples.tswebsite/src/main.tswebsite/src/main.tsxwebsite/src/style.csswebsite/vite.config.ts
💤 Files with no reviewable changes (2)
- website/src/main.ts
- scripts/install-theme.js
| const terminalContainerRef = useRef<HTMLDivElement>(null); | ||
| const xtermRef = useRef<XTerm | null>(null); | ||
| const fitAddonRef = useRef<FitAddon | null>(null); | ||
| const termuiAppRef = useRef<any>(null); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists and get its size
wc -l website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 104
🏁 Script executed:
# View the specific lines mentioned in the review (29, 131, 179-180, 194, 212)
sed -n '25,35p; 127,135p; 175,185p; 190,200p; 208,216p' website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 1602
🏁 Script executed:
# Check the imports at the top of the file to understand available types
head -30 website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 1265
🏁 Script executed:
# Get full context around each flagged line with line numbers
sed -n '125,135p' website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 411
🏁 Script executed:
# Check lines around 179-180
sed -n '175,185p' website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 301
🏁 Script executed:
# Check lines around 194 and 212
sed -n '190,215p' website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 854
🏁 Script executed:
# Search for all occurrences of 'any' in the file to see if review caught them all
rg '\bany\b' website/src/components/Playground.tsx -nRepository: Karanjot786/TermUI
Length of output: 348
🏁 Script executed:
# Check the beginning of the function to see module/exports setup
sed -n '65,145p' website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 2510
🏁 Script executed:
# Find where mockStdout and mockStdin are defined
rg 'mockStdout|mockStdin' website/src/components/Playground.tsx -B2 -A2Repository: Karanjot786/TermUI
Length of output: 797
🏁 Script executed:
# Check the entire relevant section to see variable declarations
sed -n '100,200p' website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 3207
Remove untyped any and type assertions in strict TS paths.
useRef<any>, as any casts, and catch (err: any) bypass type safety and violate strict typing rules. Each occurrence lacks an inline comment explaining why:
- Line 29:
useRef<any>should beuseRef<termuijsCore.App | null> - Line 131:
module.exports as any— first typeexportsandmoduleproperly instead - Lines 179–180:
mockStdout as anyandmockStdin as anyneed proper type annotations - Line 194:
globalThis as anyshould use a specific type assertion - Line 212:
catch (err: any)should becatch (err: unknown)with type narrowing
Suggested fixes
- const termuiAppRef = useRef<any>(null);
+ const termuiAppRef = useRef<termuijsCore.App | null>(null);
- const exports = {};
- const module = { exports };
+ const exports: Record<string, unknown> = {};
+ const module: { exports: Record<string, unknown> } = { exports };
- const AppComponent = (module.exports as any).default;
+ const AppComponent = module.exports.default;
- stdout: mockStdout as any,
- stdin: mockStdin as any,
+ stdout: mockStdout,
+ stdin: mockStdin,
- const instances = (globalThis as any).__termuijs_instances;
+ const instances = (globalThis as { __termuijs_instances?: Map<unknown, unknown> }).__termuijs_instances;
- } catch (err: any) {
- console.error(err);
- setError(err.message || String(err));
+ } catch (err: unknown) {
+ console.error(err);
+ setError(err instanceof Error ? err.message : String(err));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@website/src/components/Playground.tsx` at line 29, Replace the
`useRef<any>(null)` type parameter in the termuiAppRef declaration with the
proper type `useRef<termuijsCore.App | null>` to provide explicit typing for the
ref instead of bypassing type safety with the any type. This ensures the ref is
properly typed to hold either a termuijsCore.App instance or null value.
Source: Coding guidelines
| const fn = new Function(...Object.keys(scope), termuiTranspiled); | ||
| fn(...Object.values(scope)); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n website/src/components/Playground.tsx | head -150 | tail -50Repository: Karanjot786/TermUI
Length of output: 2028
🏁 Script executed:
wc -l website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 104
🏁 Script executed:
cat -n website/src/components/Playground.tsx | head -130 | tail -40Repository: Karanjot786/TermUI
Length of output: 1590
🏁 Script executed:
rg -n "customRequire" website/src/components/Playground.tsx -B 5 -A 2Repository: Karanjot786/TermUI
Length of output: 521
🏁 Script executed:
rg -n "code" website/src/components/Playground.tsx | head -20Repository: Karanjot786/TermUI
Length of output: 496
🏁 Script executed:
cat -n website/src/components/Playground.tsx | head -90Repository: Karanjot786/TermUI
Length of output: 3486
🏁 Script executed:
rg -n "interface\|type.*Props\|export.*Playground" website/src/components/Playground.tsxRepository: Karanjot786/TermUI
Length of output: 44
🏁 Script executed:
cat -n website/src/examples.ts 2>/dev/null || cat -n website/src/examples.tsx 2>/dev/null || find website/src -name "*example*" -type fRepository: Karanjot786/TermUI
Length of output: 7562
🏁 Script executed:
cat -n website/src/components/Playground.tsx | sed -n '240,260p'Repository: Karanjot786/TermUI
Length of output: 1331
Isolate evaluated code from the website origin.
new Function executes editor input in the main page context, giving evaluated snippets direct access to DOM, storage, and network on the docs origin. While the whitelisted customRequire limits module imports to TermUI libraries and React, it does not prevent access to browser APIs. Use an isolated sandbox boundary (e.g., sandboxed iframe/worker + message bridge) to properly contain executed code.
🧰 Tools
🪛 OpenGrep (1.22.0)
[ERROR] 127-127: new Function() with dynamic input can execute arbitrary code. Avoid dynamic code evaluation entirely, or use a safe alternative.
(coderabbit.code-injection.new-function-js)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@website/src/components/Playground.tsx` around lines 127 - 128, The code
execution in the Function constructor at lines where `new Function` is
instantiated and `fn(...Object.values(scope))` is called executes editor input
directly in the main page context, exposing DOM, storage, and network access to
the docs origin. Refactor this by creating an isolated sandbox boundary using a
sandboxed iframe or Web Worker, then establish a message bridge to communicate
between the main Playground component and the sandbox. Move the Function
creation and execution logic to run inside the sandbox instead of the main
context, passing the termuiTranspiled code and scope data through the message
bridge, and receive execution results back through the same bridge to maintain
the component's functionality while preventing access to browser APIs and
sensitive resources.
Source: Linters/SAST tools
Description
This PR introduces an interactive, browser-based playground and component gallery for the TermUI website. It utilizes
@babel/standaloneto parse TypeScript/JSX in the browser on the fly, and seamlessly integratesxterm.jsto render the TermUI components exactly as they would appear in a real terminal (complete with absolute positioning, flexbox layouts, and true colors). Users can select from several preset examples (e.g., System Dashboard, Command Palette, Form) and edit the code in real-time to immediately see the capabilities of the library live.Related Issue
Closes #1637
Which package(s)?
website,@termuijs/jsxType of Change
type:bug)type:feature)type:docs)type:testing)type:refactor)type:design)type:accessibility)type:performance)type:devops)type:security)Checklist
needs-starcheck blocks your merge otherwise.bun vitest runbun run buildbun run typecheckCONTRIBUTING.md.type: short description. (Suggested title:feat: add interactive playground and component gallery to website)markDirty()(if your change affects rendering).anytypes without an inline comment explaining why.GSSoC 2026 Participation
https://gssoc.girlscript.org/profile/____Screenshots / Recordings (UI changes)
Notes for the Reviewer
setCurrentAppexport was added to@termuijs/jsx/src/index.tsto support providing the app context to hooks during live editing.useEffectcarefully to ensure thattermuijsJsx.unmountAll()is called and that we don't accidentally getxterm.jsstuck in the alternate screen buffer when the code hot-reloads on every keystroke.Summary by CodeRabbit
New Features
Chores