Skip to content
Merged
8 changes: 5 additions & 3 deletions app/api/detect-and-install-packages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ export async function POST(request: NextRequest) {
// Remove duplicates
const uniquePackages = [...new Set(packageNames)];

console.log('[detect-and-install-packages] Packages to install:', uniquePackages);

if (uniquePackages.length === 0) {
// Add known Stellar SDK packages to ensure they are installed in sandbox environments
const stellarPackages = ['@stellar/stellar-sdk', '@stellar/freighter-api'];
const finalPackageList = [...new Set([...uniquePackages, ...stellarPackages])];
console.log('[detect-and-install-packages] Final packages to install (including Stellar SDKs):', finalPackageList);
if (finalPackageList.length === 0) {
return NextResponse.json({
success: true,
packagesInstalled: [],
Expand Down
22 changes: 17 additions & 5 deletions app/api/run-command/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';

// Simple command parser that respects double quotes
function parseCommand(command: string): string[] {
const regex = /"([^"]*)"|'([^']*)'|\S+/g;
const args: string[] = [];
let match;
while ((match = regex.exec(command)) !== null) {
args.push(match[1] ?? match[2] ?? match[0]);
}
return args;
}



// Get active sandbox from global state (in production, use a proper state management solution)
declare global {
var activeSandbox: any;
Expand All @@ -25,11 +38,10 @@ export async function POST(request: NextRequest) {

console.log(`[run-command] Executing: ${command}`);

// Parse command and arguments
const commandParts = command.trim().split(/\s+/);
const cmd = commandParts[0];
const args = commandParts.slice(1);

// Parse command and arguments using robust shell parsing
const parts = parseCommand(command);
const cmd = parts[0];
const args = parts.slice(1);
// Execute command using Vercel Sandbox
const result = await global.activeSandbox.runCommand({
cmd,
Expand Down
5 changes: 4 additions & 1 deletion components/shared/notifications/slack-notification.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import Image from "next/image";
import { useEffect, useState } from "react";

export default function SlackNotification({
Expand Down Expand Up @@ -32,10 +33,12 @@ export default function SlackNotification({
>
<div className="flex flex-row items-center max-w-xs cursor-pointer">
<div className="flex min-w-16 mr-2 mt-1">
<img
<Image
className="w-16 h-16"
src="/images/slack_logo_icon.png"
alt="Slack Logo"
width={64}
height={64}
/>
</div>
<div className="flex flex-col">
Expand Down
50 changes: 31 additions & 19 deletions components/shared/pixi/utils.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,57 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck -- TODO: fix this
// PixiJS utility helpers with full TypeScript type safety

import { Application, Assets, Sprite, Texture } from "pixi.js";
import { Application, Assets, Sprite, Texture, Container } from "pixi.js";

export const isDestroyed = (app: Application) => {
if (!app.ticker || !app.renderer || !app.stage || !app.renderer.gl)
return true;
/**
* Checks if the Pixi Application's rendering context has been lost.
*/
export const isDestroyed = (app: Application): boolean => {
if (!app.ticker || !app.renderer || !app.stage) return true;

return app.renderer.gl.isContextLost();
// Determine if renderer is a WebGLRenderer with a gl context
const maybeGL = app.renderer as unknown as { gl?: { isContextLost?: () => boolean } };
if (maybeGL.gl && typeof maybeGL.gl.isContextLost === "function") {
return maybeGL.gl.isContextLost();
}

// WebGPURenderer or other renderers – assume not destroyed
return false;
};

export const generateTexture = (app: Application, graphic: any) => {
/**
* Generates a texture from a Container. Returns a fallback white texture if the app is destroyed.
*/
export const generateTexture = (app: Application, graphic: Container): Texture => {
const renderer = app.renderer;

if (!isDestroyed(app)) {
return renderer.generateTexture(graphic);
}

return Texture.WHITE;
};

export const degreesToRadians = (degrees: number) => {
/** Converts degrees to radians. */
export const degreesToRadians = (degrees: number): number => {
return degrees * (Math.PI / 180);
};

export const imageToSprite = async (app: Application, path: string) => {
/** Loads an image path as a Sprite, using cached texture if available. */
export const imageToSprite = async (app: Application, path: string): Promise<Sprite> => {
let texture;

if (Assets.cache.has(path)) {
texture = Assets.cache.get(path);
} else {
texture = await Assets.load(path);
}

const sprite = Sprite.from(texture);

return sprite;
};

/** Creates a render loop that caps the update rate to the given FPS. */
export const createRenderWithFPS = (app: Application, fps: number) => {
let lastUpdateTime = 0;

return () => {
const currentTime = performance.now();
const timeSinceLastUpdate = currentTime - lastUpdateTime;

if (timeSinceLastUpdate >= 1000 / fps) {
app.ticker.update();
app.render();
Expand All @@ -53,8 +60,13 @@ export const createRenderWithFPS = (app: Application, fps: number) => {
};
};

export const waitUntilPixiIsReady = (app: Application) => {
/** Returns a promise that resolves when the Pixi canvas dispatches "pixi-initialized". */
export const waitUntilPixiIsReady = (app: Application): Promise<void> => {
return new Promise((resolve) => {
app.canvas.addEventListener("pixi-initialized", resolve);
const canvas = app.canvas as unknown as EventTarget;
canvas.addEventListener("pixi-initialized", () => resolve());
});
};



8 changes: 4 additions & 4 deletions components/shared/preview/json-error-highlighter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cn } from "@/utils/cn";
import React, { useRef, useState, useEffect } from "react";
import React, { useRef, useState, useEffect, useCallback } from "react";

export function JsonErrorHighlighter({
value,
Expand Down Expand Up @@ -31,7 +31,7 @@ export function JsonErrorHighlighter({
const errorLineIdx = (error?.line ?? 1) - 1;

// Calculate visible lines on scroll or resize
const recalcVisibleLines = () => {
const recalcVisibleLines = useCallback(() => {
const textarea = textareaRef.current;
if (!textarea) return;
const lineHeight = parseFloat(getComputedStyle(textarea).lineHeight) || 24;
Expand All @@ -49,15 +49,15 @@ export function JsonErrorHighlighter({
lineHeight,
clientHeight,
});
};
}, [lines]);

useEffect(() => {
recalcVisibleLines();
// Sync overlay height with textarea
const handleResize = () => recalcVisibleLines();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [value]);
}, [recalcVisibleLines]);

// Attach scroll handler
const handleScroll = () => {
Expand Down
1 change: 0 additions & 1 deletion lib/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { MongoClient, Db } from 'mongodb'
// Index creation now runs only once per application lifecycle

declare global {
// eslint-disable-next-line no-var
var _mongoClient: MongoClient | undefined
var _mongoIndexesCreated: boolean | undefined
}
Expand Down
12 changes: 9 additions & 3 deletions lib/morph-fast-apply.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
// Using direct fetch to Morph's OpenAI-compatible API to avoid SDK type issues
// Simple shell argument quoting helper
function shellQuote(args: string[]) {
// Use JSON.stringify to safely quote each arg
return args.map(a => JSON.stringify(a)).join(' ');
}


export interface MorphEditBlock {
targetFile: string;
Expand Down Expand Up @@ -90,14 +96,14 @@ async function readFileFromSandbox(sandbox: any, normalizedPath: string, fullPat
// Try provider runCommand (preferred for provider pattern)
if (typeof sandbox?.runCommand === 'function') {
try {
const res = await sandbox.runCommand(`cat ${normalizedPath}`);
const res = await sandbox.runCommand(shellQuote(['cat', normalizedPath]));
if (res && typeof res.stdout === 'string') {
return res.stdout as string;
}
} catch {}
// fallback to absolute path
try {
const resAbs = await sandbox.runCommand(`cat ${fullPath}`);
const resAbs = await sandbox.runCommand(shellQuote(['cat', fullPath]));
if (resAbs && typeof resAbs.stdout === 'string') {
return resAbs.stdout as string;
}
Expand All @@ -106,7 +112,7 @@ async function readFileFromSandbox(sandbox: any, normalizedPath: string, fullPat

// Try shell cat via commands.run
if (sandbox?.commands?.run) {
const result = await sandbox.commands.run(`cat ${fullPath}`, { cwd: '/home/user/app', timeout: 30 });
const result = await sandbox.commands.run(shellQuote(['cat', fullPath]), { cwd: '/home/user/app', timeout: 30 });
if (result?.exitCode === 0 && typeof result?.stdout === 'string') {
return result.stdout as string;
}
Expand Down
Loading
Loading