Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ RUN chmod +x docker-init.sh 2>/dev/null || true
WORKDIR /usr/src/app/frontend
RUN bun run build

# Set the environment as production
ENV NODE_ENV=production
Comment on lines 32 to -35
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The ENV NODE_ENV=production is set after the frontend build (RUN bun run build). The frontend build process relies on NODE_ENV=production to create an optimized production build. Without it, the resulting frontend assets will be larger and less performant. This should be set before the build command is run.


# Install required tools, canvas dependencies, and vespa CLI
USER root
Expand All @@ -50,13 +48,28 @@ RUN apt-get update && apt-get install -y \
libpixman-1-dev \
libfontconfig1-dev \
libfreetype6-dev \
python3 python3-pip nodejs npm r-base-core r-base-dev \
build-essential libcurl4-openssl-dev libssl-dev libxml2-dev \
jq bubblewrap \
Comment on lines +51 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Setuid bwrap + script execution + privileged compose is a dangerous combination.

This line:

RUN chmod u+s $(which bwrap)

turns bwrap into a setuid‑root helper. On its own, that’s expected for unprivileged bubblewrap sandboxing. However, in this PR it’s combined with:

  • A workflow feature that runs user‑authored Python/JS/R scripts, and
  • A docker-compose config that sets privileged: true on the app service.

That means a bug in your script‑executor wrapper (e.g., insufficient validation of paths/flags passed to bwrap) could give an attacker a path from “run arbitrary script code” → “root inside container” → “root with near‑host capabilities”.

Recommendations:

  • If at all possible, drop privileged: true in docker-compose.app-build.yml and rely on unprivileged bubblewrap + namespaces only.
  • Ensure the server‑side script executor strictly whitelists what can be mounted/exposed into the sandbox (no arbitrary --bind / or host paths).
  • Consider separating a stricter “production” Dockerfile/compose profile where the threat model explicitly assumes untrusted scripts.

Also applies to: 94-94

🤖 Prompt for AI Agents
In Dockerfile around lines 51-53 (and also line 94), the build currently
installs bubblewrap and sets it setuid-root via "chmod u+s $(which bwrap)",
which combined with the repo's privileged docker-compose and server-side script
execution creates an escalation risk; remove the setuid change and avoid setting
privileged: true in docker-compose.app-build.yml, restrict the script executor
to a strict whitelist of allowed mounts/flags and disallow binding arbitrary
host paths, and add a separate production Dockerfile/compose profile (or build
arg) that enforces unprivileged sandboxing and stricter runtime constraints for
running untrusted scripts.

⚠️ Potential issue | 🟠 Major

Language runtime setup works, but npm global install is non‑deterministic.

The overall pattern (install Python/Node/R + requirements, create script_executor_utils, chown to bun) fits the script‑executor goal. One issue is reproducibility of the JS runtime:

RUN npm install -g $(cat /usr/src/app/js-requirements.json | jq -r '.dependencies | keys[]')

This installs only package names (no versions), so each build may pick different axios (and any future packages) depending on the current npm registry state, even though js-requirements.json declares versions.

To make builds deterministic and align with js-requirements.json, consider:

-RUN npm install -g $(cat /usr/src/app/js-requirements.json | jq -r '.dependencies | keys[]')
+RUN npm install -g $(jq -r '.dependencies | to_entries[] | "\(.key)@\(.value)"' /usr/src/app/js-requirements.json)

You may also want to revisit whether you need global installs at all versus a dedicated tools directory on PATH, but that’s optional.

Also applies to: 63-71, 88-89

🤖 Prompt for AI Agents
In Dockerfile around lines 51-53 (also applies to 63-71 and 88-89), the RUN that
does npm install -g uses only package names, which makes builds
non-deterministic; change the install step to install packages with explicit
versions derived from js-requirements.json (e.g. use jq to emit "name@version"
for each dependency and pass that list to npm install -g), or alternatively
create a package.json/package-lock.json in a temp directory and run npm ci (or
install into a dedicated tools dir on PATH instead of global) so the exact
versions from js-requirements.json are installed deterministically.

&& wget https://github.com/vespa-engine/vespa/releases/download/v8.453.24/vespa-cli_8.453.24_linux_amd64.tar.gz \
&& tar -xzf vespa-cli_8.453.24_linux_amd64.tar.gz \
&& mv vespa-cli_8.453.24_linux_amd64/bin/vespa /usr/local/bin/ \
&& rm -rf vespa-cli_8.453.24_linux_amd64 vespa-cli_8.453.24_linux_amd64.tar.gz \
&& apt-get clean && rm -rf /var/lib/apt/lists/*



# Copy and install language requirements
COPY requirements.txt /usr/src/app/
COPY js-requirements.json /usr/src/app/
COPY requirements.R /usr/src/app/

# Install language packages
RUN python3 -m pip install --break-system-packages -r /usr/src/app/requirements.txt
RUN npm install -g $(cat /usr/src/app/js-requirements.json | jq -r '.dependencies | keys[]')
RUN Rscript /usr/src/app/requirements.R
Comment on lines +68 to +70
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Each RUN command in a Dockerfile creates a new layer in the image. To optimize the image size and build time, it's a best practice to chain related commands into a single RUN instruction. These three package installation commands can be combined.

RUN python3 -m pip install --break-system-packages -r /usr/src/app/requirements.txt && \
    npm install -g $(cat /usr/src/app/js-requirements.json | jq -r '.dependencies | keys[]') && \
    Rscript /usr/src/app/requirements.R



# Copy data restoration script and make it executable
#COPY deployment/restore-data.sh /usr/src/app/deployment/restore-data.sh
#RUN chmod +x /usr/src/app/deployment/restore-data.sh
Expand All @@ -66,15 +79,19 @@ RUN apt-get update && apt-get install -y \

# Note: Application ports are exposed below

# Set the environment as production
ENV NODE_ENV=production

WORKDIR /usr/src/app/server

# Create runtime directories and set ownership for bun user
RUN mkdir -p downloads vespa-data vespa-logs uploads migrations && \
chown bun:bun downloads vespa-data vespa-logs uploads migrations
RUN mkdir -p downloads vespa-data vespa-logs uploads migrations script_executor_utils && \
chown bun:bun downloads vespa-data vespa-logs uploads migrations script_executor_utils

# Copy and setup startup script
COPY --chown=bun:bun start.sh /usr/src/app/start.sh
RUN chmod +x /usr/src/app/start.sh
RUN chmod u+s $(which bwrap)
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

Setting the setuid bit on bwrap is a significant security decision. When combined with the privileged: true container setting, this could create potential privilege escalation vectors if any vulnerabilities exist in bwrap. This approach should be re-evaluated. Consider using more granular capabilities with cap_add in your Docker Compose file instead of running a privileged container, which may mitigate the need for this or at least reduce the associated risk.


USER bun

Expand Down
2 changes: 1 addition & 1 deletion deployment/portable/docker-compose.app-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ services:
dockerfile: Dockerfile
container_name: xyne-app
user: "${DOCKER_UID}:${DOCKER_GID}"
# Ports removed - NGINX handles external access
privileged: true
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

Running a container with privileged: true disables all security mechanisms and gives it full access to the host system, which is a critical security risk. This is likely to support bubblewrap sandboxing. To follow the principle of least privilege, you should grant only the necessary capabilities instead. For bubblewrap, this is typically SYS_ADMIN.

    cap_add:
      - SYS_ADMIN

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

privileged: true seriously weakens isolation, especially with script execution and setuid bwrap.

Granting full privileged: true to the app container effectively disables most Docker isolation. Combined with:

  • Untrusted user‑supplied Python/JS/R scripts, and
  • A setuid‑root bwrap binary inside the image,

a compromise of the script executor or an argument‑validation bug in the bubblewrap wrapper can turn into near‑host‑level access.

Unless you have a very specific, documented requirement, it would be much safer to:

  • Remove privileged: true, and
  • If needed, add only narrowly scoped cap_add capabilities (or devices) required by bubblewrap, instead of the full privileged set.

If this is only for a local “power user” build flow, please clearly mark it as such and keep your production/staging compose files non‑privileged.

# ports:
# - "${PORT:-3000}:${PORT:-3000}"
# - "3001:3001"
Expand Down
2 changes: 1 addition & 1 deletion deployment/portable/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ http {
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https: wss: ws:; frame-ancestors 'none';" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https: wss: ws:; frame-ancestors 'none';" always;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Adding third-party CDN to CSP increases supply chain attack risk.

Allowing https://cdn.jsdelivr.net in script-src introduces a third-party dependency that could serve malicious code if compromised. jsdelivr is a public CDN hosting user-uploaded packages, creating a broader attack surface. While jsdelivr is reputable and likely required for Monaco Editor, consider these mitigations:

  1. Use Subresource Integrity (SRI) hashes when loading resources from jsdelivr to ensure they haven't been tampered with:

    <script src="https://cdn.jsdelivr.net/..." integrity="sha384-..." crossorigin="anonymous"></script>
  2. Consider hosting Monaco Editor locally to eliminate third-party dependency entirely and reduce attack surface.

  3. Document the security trade-off and establish a plan to periodically review the necessity of this CDN dependency.

Note: The CSP already includes 'unsafe-inline' and 'unsafe-eval', which significantly weaken protection against XSS. Consider replacing these with nonces or hashes in a future iteration.

Would you like me to generate a script to verify which Monaco Editor resources are being loaded from jsdelivr, or help create a local hosting alternative?


I'll verify the review comment by examining the actual code changes and context. Let me start with shell scripts to inspect the nginx.conf file and related Monaco Editor integration.


🏁 Script executed:

#!/bin/bash
# Examine the nginx.conf file around line 54
echo "=== nginx.conf content around line 54 ==="
sed -n '50,60p' deployment/portable/nginx.conf

echo -e "\n=== Full CSP header (if spanning multiple lines) ==="
sed -n '54p' deployment/portable/nginx.conf | head -c 500

echo -e "\n\n=== Search for Monaco Editor references ==="
rg -i "monaco" --type-list | head -5
rg -i "monaco" -n -C 3 2>/dev/null || echo "No Monaco references found"

echo -e "\n=== Search for jsdelivr references in codebase ==="
rg "jsdelivr" -n -C 2 2>/dev/null || echo "No jsdelivr references found"

echo -e "\n=== Check for web worker configuration ==="
rg "worker-src" -n -C 2 deployment/portable/nginx.conf

Length of output: 11425


🏁 Script executed:

#!/bin/bash
# Check HTML/template files for Monaco Editor loading
echo "=== Search for script tags loading Monaco or CDN resources ==="
find . -type f \( -name "*.html" -o -name "*.htm" -o -name "*.hjs" -o -name "*.ejs" \) -exec grep -l "monaco\|cdn.jsdelivr" {} \; 2>/dev/null | head -10

echo -e "\n=== Check HTML files for resource loading patterns ==="
rg "<script.*src" -n --type=html 2>/dev/null | grep -E "(monaco|jsdelivr|cdn)" | head -20

Length of output: 118


🌐 Web query:

jsdelivr CDN supply chain security risks vulnerabilities 2024 2025

💡 Result:

Summary — jsDelivr CDN supply‑chain risks observed 2024–2025

Key incidents and findings

  • Attackers have hosted trojanized JavaScript on jsDelivr that exfiltrated form data and harvested credentials; researchers found malicious scripts served directly from the official jsDelivr URLs. [1]
  • Multiple 2025 npm supply‑chain compromises (phishing of maintainers) produced malicious packages whose browser‑side code was also mirrored to CDNs (including jsDelivr), enabling live exploitation in web pages using those CDN URLs. Those compromises affected high‑download packages and were live for short windows, magnifying risk to sites that hot‑link remote assets. [2][3]
  • Earlier CDN supply‑chain compromises (e.g., polyfill.js) show attackers can replace or poison commonly included shared resources; CVE advisories and vendor notes documented real-world impact and remediation steps. [4]

Primary attack vectors and vulnerabilities

  • Trust in remote, mutable CDN paths: pulling scripts by URL (no integrity checks) lets any updated/tainted file execute in site context. [1][2]
  • Compromise of upstream package maintainers or repositories can propagate malicious code into CDN‑served assets. [2]
  • Dynamic/obfuscated payloads served from CDNs evade casual review and can target specific victims or environments (conditional logic). [1][3]
  • Lack of subresource integrity (SRI), no strict Content Security Policy (CSP), and wide reuse of popular URLs increase blast radius. [1][4]

Practical mitigations (concise)

  • Serve third‑party libraries from pinned, internal copies (bundle in builds) instead of hot‑linking CDN URLs. [1][4]
  • Use Subresource Integrity (SRI) for public CDN assets plus strict CSP to prevent unexpected script execution. [1]
  • Monitor dependencies and CDN URLs (SCA, SRI mismatch alerts, file‑hash monitoring) and deploy rapid rollback/incident playbooks. [2][3]
  • Enforce strong maintainer hygiene: 2FA, org‑level protections, phishing resistance training, and least‑privilege tokens for package publishing. [2]
  • Prefer signed, immutable artifact delivery (artifact registries with supply‑chain provenance/SBOMs) and automate dependency scanning in CI. [3][4]

If you want, I can:

  • list the exact compromised package names/IOCs from the 2025 incidents,
  • produce SRI/CSP examples and a short remediation checklist for a web app.

Sources
[1] Cside blog: “Web supply chain attack through trojanized jQuery on npm, GitHub and CDNs” (jsDelivr evidence). (cside.com)
[2] Multiple reports on the Sept 2025 npm supply‑chain compromises (coverage & advisories). (sisainfosec.com)
[3] Technical writeups summarizing the 2025 npm campaign and package list. (foresiet.com)
[4] Posit / Sansec / CVE writeups on CDN polyfill compromise and CVE‑2024‑38526. (support.posit.co)

Citations:


I need to verify how Monaco Editor loads its assets to determine if the SRI recommendations are feasible.


🏁 Script executed:

#!/bin/bash
# Check if Monaco Editor loads from CDN or is bundled
echo "=== Check package.json Monaco configuration ==="
jq '.dependencies | keys[] | select(. | contains("monaco"))' frontend/package.json

echo -e "\n=== Check CodeEditor.tsx for loader configuration or CDN references ==="
grep -n -i "cdn\|jsdelivr\|loader\|src=" frontend/src/components/CodeEditor.tsx | head -20

echo -e "\n=== Check public directory for Monaco files ==="
find . -name "*monaco*" -type f 2>/dev/null | grep -v node_modules | head -20

echo -e "\n=== Search for webpack/build config that might bundle Monaco ==="
find . -name "webpack.config.*" -o -name "vite.config.*" -o -name "tsconfig.json" 2>/dev/null | head -5
rg "monaco" webpack.config.* vite.config.* 2>/dev/null || echo "No Monaco refs in build config"

echo -e "\n=== Check if @monaco-editor/react uses default loader or custom CDN ==="
grep -r "monacoEditorModule\|getEditorModule\|loader" frontend/src/components/CodeEditor.tsx 2>/dev/null | head -10

Length of output: 581


🌐 Web query:

@monaco-editor/react CDN loading subresource integrity SRI

💡 Result:

Short answer: you can use SRI when loading @monaco-editor/react from a CDN, but you must use fixed, single-file (versioned) URLs and provide matching integrity hashes for every cross‑origin resource; because Monaco also loads multiple worker and asset files at runtime, in practice it's usually easier to self‑host the Monaco assets (or pin & SRI every file) and add crossorigin="anonymous". [1][2][3]

What to do (concise steps)

  • Use a versioned, single-file CDN URL (no aliases or dynamic/minified-on-demand URLs). Example provider: jsDelivr supports npm package URLs. [2][1]
  • Compute the SRI hash for the exact file you will load (sha384 or sha512 recommended). You can generate this during your build (tools: sri-toolbox / node-sri or your build pipeline). [5][6]
  • Add both integrity and crossorigin attributes to the <script> or tag. Example: <script src="https://cdn.jsdelivr.net/npm/@monaco-editor/react@4.7.0/dist/index.umd.js" integrity="sha384-BASE64_HASH_HERE" crossorigin="anonymous"></script>
  • If Monaco will fetch additional runtime files (workers, language files, etc.), those must either be:
    • Hosted locally (so you can control and SRI them), or
    • Also referenced by exact versioned URLs with integrity hashes — otherwise SRI will break when those files are fetched/changed. jsDelivr explicitly warns not to use SRI with dynamically generated or version‑aliased files. [1]

Useful references

  • jsDelivr: Using SRI with dynamic files (how/when to use SRI and warnings about dynamic URLs). [1]
  • @monaco-editor/react docs / npm / GitHub: notes about CDN usage and loader/config (Monaco loads assets and can be reconfigured to load from local paths). [2][3]
  • Tools to generate SRI hashes: sri-toolbox, node-sri (for CI/build). [5][6]

If you want, tell me:

  • which CDN (jsDelivr / unpkg / unpkg-style URL) and exact package version you plan to use, and whether you can self‑host Monaco worker files — I’ll generate the exact script/link tags and show how to compute the integrity hashes for those files.

Sources:
[1] jsDelivr — Using SRI with dynamic files.
[2] @monaco-editor/react — npm / docs on CDN usage.
[3] monaco-react — GitHub README (loader.config details).
[5] sri-toolbox (generate SRI in build).
[6] node-sri (generate SRI).


CDN supply chain risk in CSP requires alternative mitigation approach.

Adding https://cdn.jsdelivr.net to script-src introduces legitimate supply chain risk. Recent 2024–2025 CDN attacks show malicious scripts served from jsDelivr URLs that exfiltrated credentials, and compromised npm packages were mirrored to CDNs enabling live exploitation. However, the recommended approach needs adjustment:

SRI hashes are not practical here. @monaco-editor/react uses a default dynamic loader that fetches multiple runtime assets (workers, language files) at startup. jsDelivr warns against using SRI with dynamically generated or version-aliased files, making SRI ineffective for this configuration.

Instead, consider:

  1. Bundle Monaco locally — Package @monaco-editor/react and monaco-editor as part of your build rather than loading from CDN. This eliminates the third-party dependency entirely and is the most robust mitigation.

  2. If CDN is necessary, accept the trade-off and document it as a calculated risk, noting the need for periodic supply chain reviews and a rollback plan in case of compromise.

  3. Plan CSP improvements — The existing 'unsafe-inline' and 'unsafe-eval' in script-src already weaken protection significantly. Removing these should be prioritized once Monaco (or alternatives) can run with stricter CSP directives.

🤖 Prompt for AI Agents
In deployment/portable/nginx.conf around line 54, the CSP currently allows
scripts from https://cdn.jsdelivr.net which creates a supply-chain risk; fix by
removing the CDN from script-src and instead bundle
@monaco-editor/react/monaco-editor into the app build so runtime assets are
served locally (update build config to include Monaco assets and serve them from
your origin), or if you must keep the CDN, document it as an accepted risk with
a plan for periodic supply-chain reviews and rollback procedures; additionally,
plan and track a follow-up to remove 'unsafe-inline' and 'unsafe-eval' from
script-src once Monaco is running from local bundles and tighten
worker-src/connect-src as appropriate.


# Rate Limiting Zones
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
Expand Down
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@livekit/components-core": "^0.12.9",
"@livekit/components-react": "^2.9.14",
"@livekit/components-styles": "^1.1.6",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.4",
Expand Down Expand Up @@ -57,6 +58,7 @@
"livekit-server-sdk": "^2.13.3",
"lucide-react": "^0.475.0",
"mermaid": "^11.6.0",
"monaco-editor": "^0.52.2",
"notion-md-crawler": "^1.0.0",
"papaparse": "^5.5.3",
"react": "^19.1.0",
Expand Down
195 changes: 195 additions & 0 deletions frontend/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import React, { useRef, useEffect } from "react"
import { Editor } from "@monaco-editor/react"
import { X, Save } from "lucide-react"
import { Button } from "@/components/ui/button"
import { editor } from "monaco-editor"

interface CodeEditorProps {
isOpen: boolean
onClose: () => void
language: string
initialValue: string
onChange: (value: string) => void
onSave: (value: string) => void
theme?: 'light' | 'dark' // Add optional theme prop
}

export const CodeEditor: React.FC<CodeEditorProps> = ({
isOpen,
onClose,
language,
initialValue,
onChange,
onSave,
theme,
}) => {
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null)
const [currentValue, setCurrentValue] = React.useState(initialValue)
const [isMonacoLoading, setIsMonacoLoading] = React.useState(true)
const [monacoError, setMonacoError] = React.useState<string | null>(null)

// Simple initialization without custom loader configuration
useEffect(() => {
if (isOpen) {
// Set loading to false immediately - let Monaco handle its own loading
const timer = setTimeout(() => {
setIsMonacoLoading(false)
}, 1000)

return () => clearTimeout(timer)
}
}, [isOpen])
Comment on lines +32 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This useEffect hook uses a setTimeout to manage the loading state of the Monaco editor. This is a fragile approach, as the editor's actual load time can vary. The component already uses the onMount prop, which is the correct and more robust way to handle this. This effect is redundant and could lead to UI glitches if the editor loads faster or slower than the 1-second timeout.


// Use parent theme if provided, otherwise fall back to auto-detection
const monacoTheme = theme === 'dark' ? 'vs-dark' : theme === 'light' ? 'vs' : 'vs'

// Update current value when initialValue changes
useEffect(() => {
setCurrentValue(initialValue)
}, [initialValue])

// Handle editor mount
const handleEditorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: typeof import('monaco-editor')) => {
editorRef.current = editor
setIsMonacoLoading(false) // Editor successfully mounted
setMonacoError(null)

// Fix for Chrome single space issue
editor.addCommand(monaco.KeyCode.Space, () => {
const position = editor.getPosition()
if (position) {
editor.trigger('keyboard', 'type', { text: ' ' })
}
return true // Prevent default space handling
})

// Focus the editor after a brief delay to ensure proper initialization
setTimeout(() => {
editor.focus()
// Set cursor to end of content if there's initial content
if (initialValue) {
const model = editor.getModel()
if (model) {
const lineCount = model.getLineCount()
const lastLineLength = model.getLineLength(lineCount)
editor.setPosition({ lineNumber: lineCount, column: lastLineLength + 1 })
}
}
}, 100)
}

// Handle value change
const handleEditorChange = (value: string | undefined) => {
const newValue = value || ""
setCurrentValue(newValue)
onChange(newValue)
}

// Handle save
const handleSave = () => {
onSave(currentValue)
onClose()
}


if (!isOpen) return null

return (
<div className="fixed inset-0 bg-black/50 z-[100] flex items-center justify-center">
<div
className="bg-white dark:bg-gray-900 rounded-lg shadow-xl w-[90vw] h-[80vh] flex flex-col overflow-hidden"
// onKeyDown={handleKeyDown}
// tabIndex={-1}
>
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
Code Editor
</h2>
<span className="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded capitalize">
{language}
</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={onClose}
className="flex items-center justify-center w-8 h-8 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
>
<X className="w-5 h-5 text-gray-600 dark:text-gray-400" />
</button>
</div>
</div>

{/* Editor */}
<div className="flex-1 overflow-hidden">
{isMonacoLoading ? (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="animate-spin w-8 h-8 border-2 border-gray-300 border-t-blue-600 rounded-full mx-auto mb-4"></div>
<p className="text-gray-600 dark:text-gray-400">Loading Monaco Editor...</p>
</div>
</div>
) : monacoError ? (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<p className="text-red-500 mb-4">{monacoError}</p>
<textarea
className="w-full h-64 p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-200 font-mono text-sm resize-none"
value={currentValue}
onChange={(e) => handleEditorChange(e.target.value)}
placeholder={`Enter your ${language} code here...`}
/>
</div>
</div>
) : (
<Editor
height="100%"
language={language}
value={currentValue}
onChange={handleEditorChange}
onMount={handleEditorDidMount}
loading={
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="animate-spin w-8 h-8 border-2 border-gray-300 border-t-blue-600 rounded-full mx-auto mb-4"></div>
<p className="text-gray-600 dark:text-gray-400">Loading Monaco Editor...</p>
</div>
</div>
}
theme={monacoTheme}
options={{
fontSize: 14,
minimap: { enabled: true },
scrollBeyondLastLine: false,
automaticLayout: true,
lineNumbers: "on",
folding: true,
cursorSmoothCaretAnimation: "off",
selectOnLineNumbers: true,
insertSpaces: true,
wordWrap: "on",
contextmenu: true,
mouseWheelZoom: true,
smoothScrolling: true,
cursorBlinking: "blink"
}}
/>
)}
</div>

{/* Footer */}
<div className="px-6 py-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex items-center justify-end">
<Button
onClick={handleSave}
className="bg-blue-600 hover:bg-blue-700 text-white text-sm"
size="sm"
>
<Save className="w-4 h-4 mr-2" />
Save
</Button>
</div>
</div>
</div>
)
}
Loading