Merge branch 'main' into ideation

This commit is contained in:
webdevcody
2026-01-03 23:58:56 -05:00
6 changed files with 56 additions and 31 deletions

View File

@@ -13,7 +13,8 @@ import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useAppStore, defaultBackgroundSettings } from '@/store/app-store'; import { useAppStore, defaultBackgroundSettings } from '@/store/app-store';
import { getHttpApiClient, getServerUrlSync } from '@/lib/http-api-client'; import { getHttpApiClient } from '@/lib/http-api-client';
import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
import { useBoardBackgroundSettings } from '@/hooks/use-board-background-settings'; import { useBoardBackgroundSettings } from '@/hooks/use-board-background-settings';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { import {
@@ -62,12 +63,13 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa
// Update preview image when background settings change // Update preview image when background settings change
useEffect(() => { useEffect(() => {
if (currentProject && backgroundSettings.imagePath) { if (currentProject && backgroundSettings.imagePath) {
const serverUrl = import.meta.env.VITE_SERVER_URL || getServerUrlSync();
// Add cache-busting query parameter to force browser to reload image // Add cache-busting query parameter to force browser to reload image
const cacheBuster = imageVersion ? `&v=${imageVersion}` : `&v=${Date.now()}`; const cacheBuster = imageVersion ?? Date.now().toString();
const imagePath = `${serverUrl}/api/fs/image?path=${encodeURIComponent( const imagePath = getAuthenticatedImageUrl(
backgroundSettings.imagePath backgroundSettings.imagePath,
)}&projectPath=${encodeURIComponent(currentProject.path)}${cacheBuster}`; currentProject.path,
cacheBuster
);
setPreviewImage(imagePath); setPreviewImage(imagePath);
} else { } else {
setPreviewImage(null); setPreviewImage(null);

View File

@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils';
import { ImageIcon, X, Loader2, FileText } from 'lucide-react'; import { ImageIcon, X, Loader2, FileText } from 'lucide-react';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { getServerUrlSync } from '@/lib/http-api-client'; import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
import { useAppStore, type FeatureImagePath, type FeatureTextFilePath } from '@/store/app-store'; import { useAppStore, type FeatureImagePath, type FeatureTextFilePath } from '@/store/app-store';
import { import {
sanitizeFilename, sanitizeFilename,
@@ -94,9 +94,8 @@ export function DescriptionImageDropZone({
// Construct server URL for loading saved images // Construct server URL for loading saved images
const getImageServerUrl = useCallback( const getImageServerUrl = useCallback(
(imagePath: string): string => { (imagePath: string): string => {
const serverUrl = import.meta.env.VITE_SERVER_URL || getServerUrlSync();
const projectPath = currentProject?.path || ''; const projectPath = currentProject?.path || '';
return `${serverUrl}/api/fs/image?path=${encodeURIComponent(imagePath)}&projectPath=${encodeURIComponent(projectPath)}`; return getAuthenticatedImageUrl(imagePath, projectPath);
}, },
[currentProject?.path] [currentProject?.path]
); );

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useAppStore, defaultBackgroundSettings } from '@/store/app-store'; import { useAppStore, defaultBackgroundSettings } from '@/store/app-store';
import { getServerUrlSync } from '@/lib/http-api-client'; import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
interface UseBoardBackgroundProps { interface UseBoardBackgroundProps {
currentProject: { path: string; id: string } | null; currentProject: { path: string; id: string } | null;
@@ -22,14 +22,14 @@ export function useBoardBackground({ currentProject }: UseBoardBackgroundProps)
return {}; return {};
} }
const imageUrl = getAuthenticatedImageUrl(
backgroundSettings.imagePath,
currentProject.path,
backgroundSettings.imageVersion
);
return { return {
backgroundImage: `url(${ backgroundImage: `url(${imageUrl})`,
import.meta.env.VITE_SERVER_URL || getServerUrlSync()
}/api/fs/image?path=${encodeURIComponent(
backgroundSettings.imagePath
)}&projectPath=${encodeURIComponent(currentProject.path)}${
backgroundSettings.imageVersion ? `&v=${backgroundSettings.imageVersion}` : ''
})`,
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundPosition: 'center', backgroundPosition: 'center',
backgroundRepeat: 'no-repeat', backgroundRepeat: 'no-repeat',

View File

@@ -153,3 +153,37 @@ export async function apiDeleteRaw(
): Promise<Response> { ): Promise<Response> {
return apiFetch(endpoint, 'DELETE', options); return apiFetch(endpoint, 'DELETE', options);
} }
/**
* Build an authenticated image URL for use in <img> tags or CSS background-image
* Adds authentication via query parameter since headers can't be set for image loads
*
* @param path - Image path
* @param projectPath - Project path
* @param version - Optional cache-busting version
* @returns Full URL with auth credentials
*/
export function getAuthenticatedImageUrl(
path: string,
projectPath: string,
version?: string | number
): string {
const serverUrl = getServerUrl();
const params = new URLSearchParams({
path,
projectPath,
});
if (version !== undefined) {
params.set('v', String(version));
}
// Add auth credential as query param (needed for image loads that can't set headers)
const apiKey = getApiKey();
if (apiKey) {
params.set('apiKey', apiKey);
}
// Note: Session token auth relies on cookies which are sent automatically by the browser
return `${serverUrl}/api/fs/image?${params.toString()}`;
}

View File

@@ -5,6 +5,7 @@
*/ */
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
import { Buffer } from 'buffer';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { import {
@@ -118,21 +119,10 @@ test.describe('Add Context Image', () => {
test('should import an image file to context', async ({ page }) => { test('should import an image file to context', async ({ page }) => {
await setupProjectWithFixture(page, getFixturePath()); await setupProjectWithFixture(page, getFixturePath());
await authenticateForTests(page);
await page.goto('/'); await page.goto('/');
await waitForNetworkIdle(page); await waitForNetworkIdle(page);
// Check if we're on the login screen and authenticate if needed
const loginInput = page.locator('input[type="password"][placeholder*="API key"]');
const isLoginScreen = await loginInput.isVisible({ timeout: 2000 }).catch(() => false);
if (isLoginScreen) {
const apiKey = process.env.AUTOMAKER_API_KEY || 'test-api-key-for-e2e-tests';
await loginInput.fill(apiKey);
await page.locator('button:has-text("Login")').click();
await page.waitForURL('**/', { timeout: 5000 });
await waitForNetworkIdle(page);
}
await navigateToContext(page); await navigateToContext(page);
// Wait for the file input to be attached to the DOM before setting files // Wait for the file input to be attached to the DOM before setting files

View File

@@ -12,7 +12,7 @@ const lockfilePath = join(process.cwd(), 'package-lock.json');
try { try {
const content = readFileSync(lockfilePath, 'utf8'); const content = readFileSync(lockfilePath, 'utf8');
// Check for git+ssh:// URLs // Check for git+ssh:// URLs
if (content.includes('git+ssh://')) { if (content.includes('git+ssh://')) {
console.error('Error: package-lock.json contains git+ssh:// URLs.'); console.error('Error: package-lock.json contains git+ssh:// URLs.');
@@ -20,7 +20,7 @@ try {
console.error('Or run: npm run fix:lockfile'); console.error('Or run: npm run fix:lockfile');
process.exit(1); process.exit(1);
} }
console.log('✓ No git+ssh:// URLs found in package-lock.json'); console.log('✓ No git+ssh:// URLs found in package-lock.json');
process.exit(0); process.exit(0);
} catch (error) { } catch (error) {