Skip to content

feat: add Miller columns JSON explorer and professional playback cont…#44

Merged
tejpratap46 merged 1 commit into
mainfrom
feat/update-web-sdui
May 12, 2026
Merged

feat: add Miller columns JSON explorer and professional playback cont…#44
tejpratap46 merged 1 commit into
mainfrom
feat/update-web-sdui

Conversation

@tejpratap46

@tejpratap46 tejpratap46 commented May 12, 2026

Copy link
Copy Markdown
Owner

…rols

  • Introduced JsonFinder, a Miller columns-style explorer for navigating, editing, and deleting JSON data with breadcrumb support.
  • Integrated JsonFinder into the main application, replacing the basic textarea editor for managing SDUI configurations.
  • Implemented bulk editing functionality in JsonFinder allowing path-based field updates via formulas (e.g., +10, *0.5).
  • Redesigned the preview player with professional controls, including SVG-based play/pause/step buttons and a custom-styled seek bar.
  • Added media detection to the JSON explorer to provide inline previews for image, video, and audio URLs.
  • Enhanced keyboard navigation within the explorer, supporting arrow keys for navigation and "Enter" or "E" for editing.
  • Updated global styles for range inputs and introduced reusable player button components.

Summary by CodeRabbit

  • New Features

    • Introduced interactive JSON editor with column-based nested object and array explorer
    • Professional playback controls: seek bar, step-back/forward buttons, and play/pause toggle
    • Media preview support in editor for images, videos, audio, and external links
    • JSON file export capability
    • Keyboard navigation for editor controls
    • Displays FPS and computed duration metadata
  • Style

    • Enhanced player controls styling with improved range input and button appearance

Review Change Stack

…rols

* Introduced `JsonFinder`, a Miller columns-style explorer for navigating, editing, and deleting JSON data with breadcrumb support.
* Integrated `JsonFinder` into the main application, replacing the basic textarea editor for managing SDUI configurations.
* Implemented bulk editing functionality in `JsonFinder` allowing path-based field updates via formulas (e.g., `+10`, `*0.5`).
* Redesigned the preview player with professional controls, including SVG-based play/pause/step buttons and a custom-styled seek bar.
* Added media detection to the JSON explorer to provide inline previews for image, video, and audio URLs.
* Enhanced keyboard navigation within the explorer, supporting arrow keys for navigation and "Enter" or "E" for editing.
* Updated global styles for range inputs and introduced reusable player button components.
@tejpratap46 tejpratap46 merged commit b5575a7 into main May 12, 2026
4 of 7 checks passed
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2a1d425-035a-46ec-b3fa-80a2802ab079

📥 Commits

Reviewing files that changed from the base of the PR and between 3cb1124 and c7224df.

📒 Files selected for processing (3)
  • web-sdui/src/App.tsx
  • web-sdui/src/components/JsonFinder.tsx
  • web-sdui/src/index.css

📝 Walkthrough

Walkthrough

This PR introduces a new JsonFinder component for interactive JSON exploration and integrates it into the App, replacing a textarea-based JSON editor. The playback controls are reworked with seek slider, step buttons, and play/pause toggle. All supporting styles for range inputs and player buttons are added.

Changes

JSON Editor Integration & Playback UI

Layer / File(s) Summary
JsonFinder types, styles, and helper components
web-sdui/src/components/JsonFinder.tsx
Introduces JsonPrimitive, JsonValue, and JsonType types, defines comprehensive embedded CSS for layout and components, and implements TypeBadge, UrlBadge, ChevronIcon, and InlineEditor subcomponents for type display and value editing.
JsonFinder UI panels
web-sdui/src/components/JsonFinder.tsx
ColumnPanel renders selectable rows with keyboard focus, MediaPreview detects and displays inline previews for images/video/audio/links, PreviewEditor provides type-specific editing, and PreviewPanel displays leaf metadata, value display/edit, copy-to-clipboard, and deletion controls.
JsonFinder state management and JSON navigation
web-sdui/src/components/JsonFinder.tsx
Main JsonFinder component manages state (JSON text, parse error, parsed root, columns, selection), loads/parses from localStorage with fallback to initialJson, implements core navigation (refreshFromRoot rebuilds columns), and provides path-building and persistence utilities.
JsonFinder bulk edit and keyboard navigation
web-sdui/src/components/JsonFinder.tsx
Adds bulk edit to apply arithmetic/static updates across arrays at dot-paths, implements global keyboard navigation (arrow/Enter/Right for column movement and expand, 'E' for leaf edit, Delete/Backspace for deletion), and tracks breadcrumb state.
JsonFinder render tree and UI layout
web-sdui/src/components/JsonFinder.tsx
Wires complete render tree: root with injected CSS, toolbar with breadcrumb and action buttons, optional JSON input textarea, column explorer layout, conditional preview panel for selected leaves, and formula bar for bulk edits.
App state setup and JsonFinder integration
web-sdui/src/App.tsx
Expands defaultSDUI constant, changes state to store sdui directly, recalculates maxFrames from views on change, adds handleJsonUpdate callback, implements skipFrames helper, and integrates JsonFinder component with initialJson and onUpdate wiring.
App playback controls and metadata display
web-sdui/src/App.tsx
Reworks preview and playback UI: adds seek slider (range input), step-back/step-forward buttons, and SVG play/pause toggle, displays meta info showing FPS and computed duration.
Range input and player button styling
web-sdui/src/index.css
Styles HTML range input with accent color, track dimensions, and WebKit thumb appearance (white circle with shadow), and defines .player-btn base styles (circular, transitions, hover/active) and .player-btn.primary variant (blue background).

Sequence Diagram

sequenceDiagram
  participant User
  participant App
  participant JsonFinder
  participant PreviewPanel
  User->>JsonFinder: load or edit JSON
  JsonFinder->>JsonFinder: parse and load from localStorage
  User->>JsonFinder: select leaf in column view
  JsonFinder->>PreviewPanel: display metadata & value
  User->>PreviewPanel: double-click to edit
  PreviewPanel->>PreviewPanel: enter edit mode
  User->>PreviewPanel: save changes
  PreviewPanel->>JsonFinder: persist to nested object
  JsonFinder->>JsonFinder: update localStorage
  JsonFinder->>App: onUpdate callback with new sdui
  App->>App: update sdui state & recalc maxFrames
  User->>App: seek or play/pause playback
  App->>App: update currentFrame via skipFrames
  App->>App: render preview at currentFrame
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 A JSON explorer hops through nested trees,
Column by column, with keyboard-dance ease,
Leaves can be edited, bulk-formatted fast,
And the playback controls at last,
With sliders and buttons—a professional blast!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/update-web-sdui

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR introduces a sophisticated Miller columns JSON explorer with bulk editing capabilities and professional playback controls. The implementation demonstrates strong UI/UX design and comprehensive functionality.

Critical Issues Found (4)

I've identified 4 critical defects that must be fixed before merge:

  1. Division by zero vulnerability in bulk formula processing - will crash with /0 formula
  2. Array bounds validation missing in setValueAtPath - causes silent failures with invalid indices
  3. Array bounds validation missing in deleteAtPath - causes incorrect behavior with out-of-bounds indices
  4. Unhandled clipboard API rejection - causes console errors in restricted contexts

All issues have specific code suggestions that can be applied directly from the GitHub UI.

Files Changed

  • web-sdui/src/App.tsx - Integrated JsonFinder component with improved player controls
  • web-sdui/src/components/JsonFinder.tsx - New Miller columns explorer (1790 lines)
  • web-sdui/src/index.css - Enhanced styling for player controls

Please address the critical issues before merging.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment on lines +1056 to +1060
navigator.clipboard.writeText(text).then(() => {
setCopied(key);
setTimeout(() => setCopied(null), 1400);
});
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Crash Risk: Missing error handling for clipboard write operation. When clipboard API fails (e.g., in non-HTTPS contexts or when permissions are denied), unhandled promise rejection will cause console errors and potentially break the UI.

Suggested change
navigator.clipboard.writeText(text).then(() => {
setCopied(key);
setTimeout(() => setCopied(null), 1400);
});
};
const copy = (text: string, key: string) => {
navigator.clipboard.writeText(text).then(() => {
setCopied(key);
setTimeout(() => setCopied(null), 1400);
}).catch(() => {
// Silently fail if clipboard access is denied
});

Comment on lines +99 to +102
const idx = typeof head === "number" ? head : parseInt(head, 10);
const copy = [...root];
copy[idx] = setValueAtPath(copy[idx], tail, newValue);
return copy;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Missing boundary check causes array index out of bounds. When an array item at idx doesn't exist in the nested path, accessing copy[idx] will be undefined, causing setValueAtPath to fail silently or crash when trying to traverse deeper. Add validation before recursive call.

Suggested change
const idx = typeof head === "number" ? head : parseInt(head, 10);
const copy = [...root];
copy[idx] = setValueAtPath(copy[idx], tail, newValue);
return copy;
const idx = typeof head === "number" ? head : parseInt(head, 10);
if (idx < 0 || idx >= root.length) return root;
const copy = [...root];
copy[idx] = setValueAtPath(copy[idx], tail, newValue);

if (op === '+') newVal = currentVal + valNum;
else if (op === '-') newVal = currentVal - valNum;
else if (op === '*') newVal = currentVal * valNum;
else if (op === '/') newVal = currentVal / valNum;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Division by Zero Crash Risk: Missing validation before division operation. When formula is /0, this will cause division by zero, resulting in Infinity which could break JSON serialization and cause application crashes.

Suggested change
else if (op === '/') newVal = currentVal / valNum;
else if (op === '/') newVal = valNum !== 0 ? currentVal / valNum : currentVal;

Comment on lines +116 to +124
const idx = typeof head === "number" ? head : parseInt(head, 10);
if (tail.length === 0) {
const copy = [...root];
copy.splice(idx, 1);
return copy;
}
const copy = [...root];
copy[idx] = deleteAtPath(copy[idx], tail);
return copy;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Missing boundary check causes array index out of bounds. When idx is beyond array bounds, copy[idx] will be undefined and calling deleteAtPath on undefined will cause incorrect behavior or crashes.

Suggested change
const idx = typeof head === "number" ? head : parseInt(head, 10);
if (tail.length === 0) {
const copy = [...root];
copy.splice(idx, 1);
return copy;
}
const copy = [...root];
copy[idx] = deleteAtPath(copy[idx], tail);
return copy;
const idx = typeof head === "number" ? head : parseInt(head, 10);
if (idx < 0 || idx >= root.length) return root;
if (tail.length === 0) {
const copy = [...root];
copy.splice(idx, 1);
return copy;
}
const copy = [...root];
copy[idx] = deleteAtPath(copy[idx], tail);

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly upgrades the user interface by replacing the basic JSON text editor with a column-based explorer component, JsonFinder, and enhancing the video player with professional controls and frame-skipping functionality. The code review identifies several areas for improvement, including optimizing performance by moving inlined styles and stabilizing global event listeners, enhancing type safety by removing explicit any annotations, and fixing a potential bug in the bulk editing logic where division by zero could lead to invalid JSON values.

Comment thread web-sdui/src/App.tsx
if (sdui && sdui.views) {
let max = 0;
parsed.views.forEach((v: any) => {
sdui.views.forEach((v: any) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of any here bypasses TypeScript's type checking. Since sdui.views is an array of MotionViewProps, the type can be inferred automatically by removing the explicit any annotation.

Suggested change
sdui.views.forEach((v: any) => {
sdui.views.forEach((v) => {


if (!Array.isArray(target)) return;

const newRoot = JSON.parse(JSON.stringify(root));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using JSON.parse(JSON.stringify(root)) for deep cloning is inefficient for large JSON objects and can be slow. For a JSON explorer that might handle significant datasets, consider using the native structuredClone() method which is more performant and handles more data types.

Suggested change
const newRoot = JSON.parse(JSON.stringify(root));
const newRoot = structuredClone(root);

if (op === '+') newVal = currentVal + valNum;
else if (op === '-') newVal = currentVal - valNum;
else if (op === '*') newVal = currentVal * valNum;
else if (op === '/') newVal = currentVal / valNum;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Potential division by zero. If valNum is 0, newVal will become Infinity. Since Infinity is not a valid JSON value, JSON.stringify will convert it to null during serialization, which may lead to unintended data loss in the configuration.

Suggested change
else if (op === '/') newVal = currentVal / valNum;
else if (op === '/') newVal = valNum !== 0 ? currentVal / valNum : currentVal;

Comment on lines +1464 to +1583
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
// Ignore if typing in an input/textarea
const target = e.target as HTMLElement;
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT") {
if (e.key === "Escape") {
(target as HTMLElement).blur();
setKeyboardMode(true);
}
return;
}

if (!root) return;

const maxCol = columns.length - 1;
const currentCol = columns[focusedCol];
if (!currentCol) return;

const maxRow = currentCol.items.length - 1;

switch (e.key) {
case "ArrowDown": {
e.preventDefault();
setKeyboardMode(true);
setFocusedRow((prev) => {
const next = prev < 0 ? 0 : Math.min(prev + 1, maxRow);
return next;
});
break;
}
case "ArrowUp": {
e.preventDefault();
setKeyboardMode(true);
setFocusedRow((prev) => {
const next = prev < 0 ? maxRow : Math.max(prev - 1, 0);
return next;
});
break;
}
case "ArrowRight": {
e.preventDefault();
setKeyboardMode(true);
const item = currentCol.items[focusedRow >= 0 ? focusedRow : 0];
if (item && isExpandable(item.value)) {
handleSelect(focusedCol, item);
setFocusedCol((prev) => Math.min(prev + 1, maxCol + 1));
setFocusedRow(0);
} else if (focusedCol < maxCol) {
setFocusedCol((prev) => prev + 1);
setFocusedRow(0);
}
break;
}
case "ArrowLeft": {
e.preventDefault();
setKeyboardMode(true);
if (focusedCol > 0) {
setFocusedCol((prev) => prev - 1);
setFocusedRow(-1);
}
break;
}
case "Enter": {
e.preventDefault();
setKeyboardMode(true);
const item = currentCol.items[focusedRow >= 0 ? focusedRow : 0];
if (item) {
handleSelect(focusedCol, item);
if (isExpandable(item.value)) {
setFocusedCol((prev) => Math.min(prev + 1, maxCol + 1));
setFocusedRow(0);
}
}
break;
}
case "Escape": {
setEditingKey(null);
if (selectedItem) {
setSelectedItem(null);
}
break;
}
case "Delete":
case "Backspace": {
if (selectedItem && focusedRow >= 0) {
e.preventDefault();
handleDelete();
setFocusedRow((prev) => Math.max(prev - 1, 0));
}
break;
}
case "e":
case "E": {
if (!e.ctrlKey && !e.metaKey && !e.altKey) {
const item = currentCol.items[focusedRow >= 0 ? focusedRow : 0];
if (item && !isExpandable(item.value)) {
e.preventDefault();
handleDoubleClick(item);
}
}
break;
}
case "Home": {
e.preventDefault();
setKeyboardMode(true);
setFocusedRow(0);
break;
}
case "End": {
e.preventDefault();
setKeyboardMode(true);
setFocusedRow(maxRow);
break;
}
}
};

window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [root, columns, focusedCol, focusedRow, selectedItem, handleSelect, handleDelete, handleDoubleClick]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The global keydown event listener is re-registered on every state change (e.g., every time the user moves the selection with arrow keys) due to the extensive dependency array. This is inefficient and can cause performance degradation as the JSON complexity increases. Consider using a useRef to store the current state or a useReducer to keep the event handler stable. Additionally, attaching the listener to window is less idiomatic for a component; consider using a local onKeyDown on a focusable container element.


return (
<div className="jf-root" onClick={() => setKeyboardMode(false)}>
<style>{css}</style>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Inlining a <style> tag containing an @import inside the render function is a performance bottleneck. The browser may re-parse the CSS and potentially re-trigger the font download on every re-render (which happens on every keyboard navigation step). Move the CSS to a global stylesheet or use useMemo to prevent unnecessary re-processing of the style block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant