From 80112c19ac46bf51fbd09f886a9d27152deac98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=86=A0=E8=BE=B0?= Date: Wed, 20 May 2026 18:07:57 +0800 Subject: [PATCH 1/2] fix(selfhost): derive local port URLs from env --- .env.example | 19 ++++- .github/workflows/ci.yml | 3 + Makefile | 2 +- apps/web/config/runtime-urls.test.ts | 82 +++++++++++++++++++ apps/web/config/runtime-urls.ts | 18 ++++ apps/web/next.config.ts | 3 +- docker-compose.selfhost.yml | 12 +-- .../views/issues/components/issue-detail.tsx | 2 +- scripts/check.sh | 4 +- scripts/dev.sh | 3 + scripts/install.ps1 | 49 ++++++++++- scripts/install.sh | 51 +++++++++++- scripts/selfhost-config.test.sh | 36 ++++++++ 13 files changed, 264 insertions(+), 20 deletions(-) create mode 100644 apps/web/config/runtime-urls.test.ts create mode 100644 apps/web/config/runtime-urls.ts create mode 100755 scripts/selfhost-config.test.sh diff --git a/.env.example b/.env.example index 1c09af4454..94ba04bb49 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,11 @@ APP_ENV= # 888888 and keep APP_ENV non-production. This is ignored when APP_ENV=production. MULTICA_DEV_VERIFICATION_CODE= PORT=8080 +# Optional aliases for the local/self-host backend port. If one is set, it +# takes precedence over PORT in compose, Makefile, and installer helpers. +# BACKEND_PORT=8080 +# API_PORT=8080 +# SERVER_PORT=8080 # Prometheus metrics are disabled by default. When enabled, bind to loopback # unless you protect the listener with private networking, allowlists, or # proxy auth. Do not expose this endpoint through the public app/API ingress. @@ -28,7 +33,9 @@ PORT=8080 # METRICS_ADDR=127.0.0.1:9090 JWT_SECRET=change-me-in-production MULTICA_SERVER_URL=ws://localhost:8080/ws -MULTICA_APP_URL=http://localhost:3000 +# Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT. +# Set explicitly only when the app's public URL differs from local frontend. +# MULTICA_APP_URL=http://localhost:3000 # Public URL the API is reachable at from the open internet (no trailing # slash). Used to mint absolute webhook URLs for autopilot webhook # triggers. Leave unset behind a same-origin reverse proxy or for plain @@ -91,7 +98,9 @@ SMTP_TLS_INSECURE=false # rebuild is needed. GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= -GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback +# Derived by docker-compose.selfhost.yml from FRONTEND_PORT. +# Set explicitly only when your OAuth callback URL differs from local frontend. +# GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback # S3 / CloudFront # S3_BUCKET — bucket NAME only (e.g. "my-bucket"). Do NOT include the @@ -170,9 +179,11 @@ GITHUB_WEBHOOK_SECRET= # Frontend FRONTEND_PORT=3000 -FRONTEND_ORIGIN=http://localhost:3000 +# Derived by docker-compose.selfhost.yml from FRONTEND_PORT. +# Set explicitly only when serving frontend on a different origin/domain. +# FRONTEND_ORIGIN=http://localhost:3000 # Leave empty — auto-derived from page origin in browser, set by Makefile for local dev. -# Only set explicitly if frontend and backend are on different domains. +# NEXT_PUBLIC_API_URL also feeds the Next.js SSR proxy when explicitly set. NEXT_PUBLIC_API_URL= NEXT_PUBLIC_WS_URL= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a56929fe69..3753706724 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,9 @@ jobs: - name: Install dependencies run: pnpm install + - name: Test self-host env derivation + run: bash scripts/selfhost-config.test.sh + - name: Verify reserved-slugs.ts is up to date # Re-runs the generator and fails on any drift from the # checked-in TypeScript output. The Go side embeds the JSON diff --git a/Makefile b/Makefile index ea2c58f038..f31251de7e 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ POSTGRES_DB ?= multica POSTGRES_USER ?= multica POSTGRES_PASSWORD ?= multica POSTGRES_PORT ?= 5432 -PORT ?= 8080 +PORT := $(or $(BACKEND_PORT),$(API_PORT),$(SERVER_PORT),$(PORT),8080) FRONTEND_PORT ?= 3000 FRONTEND_ORIGIN ?= http://localhost:$(FRONTEND_PORT) MULTICA_APP_URL ?= $(FRONTEND_ORIGIN) diff --git a/apps/web/config/runtime-urls.test.ts b/apps/web/config/runtime-urls.test.ts new file mode 100644 index 0000000000..dae7927869 --- /dev/null +++ b/apps/web/config/runtime-urls.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from "vitest"; + +import { resolveRemoteApiUrl } from "./runtime-urls"; + +describe("resolveRemoteApiUrl", () => { + it("prefers REMOTE_API_URL when explicitly configured", () => { + expect( + resolveRemoteApiUrl({ + REMOTE_API_URL: "http://backend:8080", + NEXT_PUBLIC_API_URL: "http://localhost:19000", + PORT: "18080", + }), + ).toBe("http://backend:8080"); + }); + + it("uses NEXT_PUBLIC_API_URL when REMOTE_API_URL is unset", () => { + expect( + resolveRemoteApiUrl({ + NEXT_PUBLIC_API_URL: "http://localhost:19000", + PORT: "18080", + }), + ).toBe("http://localhost:19000"); + }); + + it("derives localhost backend URL from PORT when no API URL is set", () => { + expect(resolveRemoteApiUrl({ PORT: "19080" })).toBe("http://localhost:19080"); + }); + + it("supports explicit backend port aliases before PORT", () => { + expect(resolveRemoteApiUrl({ BACKEND_PORT: "28080", PORT: "19080" })).toBe( + "http://localhost:28080", + ); + expect(resolveRemoteApiUrl({ API_PORT: "38080", PORT: "19080" })).toBe( + "http://localhost:38080", + ); + expect(resolveRemoteApiUrl({ SERVER_PORT: "48080", PORT: "19080" })).toBe( + "http://localhost:48080", + ); + }); + + it("prefers backend port aliases by documented precedence", () => { + expect( + resolveRemoteApiUrl({ + BACKEND_PORT: "28080", + API_PORT: "38080", + SERVER_PORT: "48080", + PORT: "19080", + }), + ).toBe("http://localhost:28080"); + + expect( + resolveRemoteApiUrl({ + API_PORT: "38080", + SERVER_PORT: "48080", + PORT: "19080", + }), + ).toBe("http://localhost:38080"); + + expect(resolveRemoteApiUrl({ SERVER_PORT: "48080", PORT: "19080" })).toBe( + "http://localhost:48080", + ); + }); + + it("ignores whitespace-only backend URL values", () => { + expect( + resolveRemoteApiUrl({ + REMOTE_API_URL: " ", + NEXT_PUBLIC_API_URL: " ", + BACKEND_PORT: " ", + API_PORT: " ", + SERVER_PORT: " ", + PORT: "19080", + }), + ).toBe("http://localhost:19080"); + + expect(resolveRemoteApiUrl({ PORT: " " })).toBe("http://localhost:8080"); + }); + + it("falls back to the historical backend port when no env is configured", () => { + expect(resolveRemoteApiUrl({})).toBe("http://localhost:8080"); + }); +}); diff --git a/apps/web/config/runtime-urls.ts b/apps/web/config/runtime-urls.ts new file mode 100644 index 0000000000..419243e4b3 --- /dev/null +++ b/apps/web/config/runtime-urls.ts @@ -0,0 +1,18 @@ +type RuntimeEnv = Record; + +export function resolveRemoteApiUrl(env: RuntimeEnv): string { + const explicitRemote = env.REMOTE_API_URL?.trim(); + if (explicitRemote) return explicitRemote; + + const publicApi = env.NEXT_PUBLIC_API_URL?.trim(); + if (publicApi) return publicApi; + + const port = + env.BACKEND_PORT?.trim() || + env.API_PORT?.trim() || + env.SERVER_PORT?.trim() || + env.PORT?.trim(); + if (port) return `http://localhost:${port}`; + + return "http://localhost:8080"; +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 2b72f46efd..63689fb933 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,11 +1,12 @@ import type { NextConfig } from "next"; import { config } from "dotenv"; import { resolve } from "path"; +import { resolveRemoteApiUrl } from "./config/runtime-urls"; // Load root .env so REMOTE_API_URL is available to next.config.ts config({ path: resolve(__dirname, "../../.env") }); -const remoteApiUrl = process.env.REMOTE_API_URL || "http://localhost:8080"; +const remoteApiUrl = resolveRemoteApiUrl(process.env); const docsUrl = process.env.DOCS_URL || "http://localhost:4000"; // Parse hostnames from CORS_ALLOWED_ORIGINS so that Next.js dev server diff --git a/docker-compose.selfhost.yml b/docker-compose.selfhost.yml index 0ef30d0034..5fc6db2d81 100644 --- a/docker-compose.selfhost.yml +++ b/docker-compose.selfhost.yml @@ -13,8 +13,8 @@ # # Edit .env — change JWT_SECRET at minimum # docker compose -f docker-compose.selfhost.yml up -d # -# Frontend: http://localhost:3000 -# Backend: http://localhost:8080 (also used by CLI/daemon) +# Frontend: http://localhost:${FRONTEND_PORT:-3000} +# Backend: http://localhost:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}} name: multica @@ -42,7 +42,7 @@ services: postgres: condition: service_healthy ports: - - "127.0.0.1:${PORT:-8080}:8080" + - "127.0.0.1:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}:8080" volumes: - backend_uploads:/app/data/uploads environment: @@ -50,7 +50,7 @@ services: PORT: "8080" METRICS_ADDR: ${METRICS_ADDR:-} JWT_SECRET: ${JWT_SECRET:-change-me-in-production} - FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:3000} + FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:${FRONTEND_PORT:-3000}} CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-} RESEND_API_KEY: ${RESEND_API_KEY:-} RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-noreply@multica.ai} @@ -61,7 +61,7 @@ services: SMTP_TLS_INSECURE: ${SMTP_TLS_INSECURE:-false} GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} - GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:3000/auth/callback} + GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:${FRONTEND_PORT:-3000}/auth/callback} S3_BUCKET: ${S3_BUCKET:-} S3_REGION: ${S3_REGION:-us-west-2} CLOUDFRONT_DOMAIN: ${CLOUDFRONT_DOMAIN:-} @@ -70,7 +70,7 @@ services: COOKIE_DOMAIN: ${COOKIE_DOMAIN:-} APP_ENV: ${APP_ENV:-production} MULTICA_DEV_VERIFICATION_CODE: ${MULTICA_DEV_VERIFICATION_CODE:-} - MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:3000} + MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:${FRONTEND_PORT:-3000}} ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true} ALLOWED_EMAILS: ${ALLOWED_EMAILS:-} ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-} diff --git a/packages/views/issues/components/issue-detail.tsx b/packages/views/issues/components/issue-detail.tsx index 22488df43a..51170303f7 100644 --- a/packages/views/issues/components/issue-detail.tsx +++ b/packages/views/issues/components/issue-detail.tsx @@ -1463,7 +1463,7 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr onClick={() => setMetadataOpen(true)} > {t(($) => $.detail.section_metadata)} - + diff --git a/scripts/check.sh b/scripts/check.sh index 2a024c06e7..80e94e000c 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -21,9 +21,11 @@ set +a POSTGRES_DB="${POSTGRES_DB:-multica}" POSTGRES_USER="${POSTGRES_USER:-multica}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" -PORT="${PORT:-8080}" +PORT="${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}" FRONTEND_PORT="${FRONTEND_PORT:-3000}" PLAYWRIGHT_BASE_URL="${PLAYWRIGHT_BASE_URL:-http://localhost:${FRONTEND_PORT}}" +export PORT +export FRONTEND_PORT export PLAYWRIGHT_BASE_URL BACKEND_PID="" diff --git a/scripts/dev.sh b/scripts/dev.sh index e83ffe6725..1f24d16184 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -40,6 +40,9 @@ set -a . "$ENV_FILE" set +a +PORT="${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}" +export PORT + # ---------- Install dependencies ---------- if [ ! -d node_modules ]; then echo "==> Installing dependencies..." diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 5eaa8acd3e..f2aad96059 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -30,6 +30,46 @@ function Test-CommandExists { $null -ne (Get-Command $Name -ErrorAction SilentlyContinue) } +function Get-EnvFileValue { + param( + [string]$Path, + [string]$Name, + [string]$Default + ) + + if (-not (Test-Path $Path)) { + return $Default + } + + $prefix = "$Name=" + $line = Get-Content $Path | + Where-Object { $_.StartsWith($prefix) } | + Select-Object -Last 1 + if (-not $line) { + return $Default + } + + $value = $line.Substring($prefix.Length).Trim().Trim('"').Trim("'") + if ([string]::IsNullOrWhiteSpace($value)) { + return $Default + } + return $value +} + +function Get-SelfHostBackendPort { + foreach ($name in @("BACKEND_PORT", "API_PORT", "SERVER_PORT", "PORT")) { + $value = Get-EnvFileValue -Path (Join-Path $InstallDir ".env") -Name $name -Default "" + if (-not [string]::IsNullOrWhiteSpace($value)) { + return $value + } + } + return "8080" +} + +function Get-SelfHostFrontendPort { + return Get-EnvFileValue -Path (Join-Path $InstallDir ".env") -Name "FRONTEND_PORT" -Default "3000" +} + function Get-LatestVersion { try { $release = Invoke-RestMethod -Uri "https://api.github.com/repos/multica-ai/multica/releases/latest" -ErrorAction Stop @@ -386,10 +426,11 @@ function Install-Server { docker compose -f docker-compose.selfhost.yml up -d Write-Info "Waiting for backend to be ready..." + $backendPort = Get-SelfHostBackendPort $ready = $false for ($i = 1; $i -le 45; $i++) { try { - $null = Invoke-WebRequest -Uri "http://localhost:8080/health" -UseBasicParsing -TimeoutSec 2 + $null = Invoke-WebRequest -Uri "http://localhost:$backendPort/health" -UseBasicParsing -TimeoutSec 2 $ready = $true break } catch { @@ -451,8 +492,10 @@ function Start-LocalInstall { Write-Host " [OK] Multica server is running and CLI is ready!" -ForegroundColor Green Write-Host " ============================================" -ForegroundColor Green Write-Host "" - Write-Host " Frontend: http://localhost:3000" - Write-Host " Backend: http://localhost:8080" + $frontendPort = Get-SelfHostFrontendPort + $backendPort = Get-SelfHostBackendPort + Write-Host " Frontend: http://localhost:$frontendPort" + Write-Host " Backend: http://localhost:$backendPort" Write-Host " Server at: $InstallDir" Write-Host "" Write-Host " Next: configure your CLI to connect" diff --git a/scripts/install.sh b/scripts/install.sh index 4a82ad541e..6a6ad2dc0e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -41,6 +41,46 @@ fail() { printf "${BOLD}${RED}✗ %s${RESET}\n" "$*" >&2; exit 1; } command_exists() { command -v "$1" >/dev/null 2>&1; } +env_file_value() { + local file="$1" + local key="$2" + local default="$3" + local line value + line="$(grep -E "^${key}=" "$file" 2>/dev/null | tail -n 1 || true)" + if [ -z "$line" ]; then + printf "%s" "$default" + return + fi + value="${line#*=}" + value="${value%$'\r'}" + value="${value%\"}" + value="${value#\"}" + value="${value%\'}" + value="${value#\'}" + if [ -z "$value" ]; then + printf "%s" "$default" + else + printf "%s" "$value" + fi +} + +selfhost_backend_port() { + local file="${1:-.env}" + local value + for key in BACKEND_PORT API_PORT SERVER_PORT PORT; do + value="$(env_file_value "$file" "$key" "")" + if [ -n "$value" ]; then + printf "%s" "$value" + return + fi + done + printf "8080" +} + +selfhost_frontend_port() { + env_file_value "${1:-.env}" "FRONTEND_PORT" "3000" +} + detect_os() { case "$(uname -s)" in Darwin) OS="darwin" ;; @@ -339,9 +379,11 @@ setup_server() { # Wait for health check info "Waiting for backend to be ready..." + local backend_port + backend_port="$(selfhost_backend_port .env)" local ready=false for i in $(seq 1 45); do - if curl -sf http://localhost:8080/health >/dev/null 2>&1; then + if curl -sf "http://localhost:${backend_port}/health" >/dev/null 2>&1; then ready=true break fi @@ -403,8 +445,11 @@ run_with_server() { printf "${BOLD}${GREEN} ✓ Multica server is running and CLI is ready!${RESET}\n" printf "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n" printf "\n" - printf " ${BOLD}Frontend:${RESET} http://localhost:3000\n" - printf " ${BOLD}Backend:${RESET} http://localhost:8080\n" + local frontend_port backend_port + frontend_port="$(selfhost_frontend_port "$INSTALL_DIR/.env")" + backend_port="$(selfhost_backend_port "$INSTALL_DIR/.env")" + printf " ${BOLD}Frontend:${RESET} http://localhost:%s\n" "$frontend_port" + printf " ${BOLD}Backend:${RESET} http://localhost:%s\n" "$backend_port" printf " ${BOLD}Server at:${RESET} %s\n" "$INSTALL_DIR" printf "\n" printf " ${BOLD}Next: configure your CLI to connect${RESET}\n" diff --git a/scripts/selfhost-config.test.sh b/scripts/selfhost-config.test.sh new file mode 100755 index 0000000000..6c094c9f1f --- /dev/null +++ b/scripts/selfhost-config.test.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +require_config() { + local config=$1 + local expected=$2 + + if ! grep -Fq "$expected" <<<"$config"; then + echo "Missing expected docker compose config value:" + echo " $expected" + exit 1 + fi +} + +config="$( + FRONTEND_PORT=3100 BACKEND_PORT=9100 docker compose \ + --env-file .env.example \ + -f docker-compose.selfhost.yml \ + config +)" + +require_config "$config" 'published: "3100"' +require_config "$config" 'published: "9100"' +require_config "$config" 'FRONTEND_ORIGIN: http://localhost:3100' +require_config "$config" 'GOOGLE_REDIRECT_URI: http://localhost:3100/auth/callback' +require_config "$config" 'MULTICA_APP_URL: http://localhost:3100' + +if ! grep -Eq '^export PORT($|[[:space:]])' scripts/check.sh; then + echo "scripts/check.sh must export the derived PORT for its backend subprocess." + exit 1 +fi + +echo "self-host compose env derivation ok" From 0f651a1f379f4cae9bc20939efa47b85dd6ab792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=86=A0=E8=BE=B0?= Date: Thu, 21 May 2026 22:45:50 +0800 Subject: [PATCH 2/2] fix(selfhost): derive local script URLs --- .env.example | 12 ++-- Makefile | 1 + .../views/issues/components/issue-detail.tsx | 2 +- scripts/check.sh | 11 +--- scripts/dev.sh | 4 +- scripts/local-env.sh | 20 ++++++ scripts/selfhost-config.test.sh | 61 +++++++++++++++++-- 7 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 scripts/local-env.sh diff --git a/.env.example b/.env.example index 94ba04bb49..97c132f5ab 100644 --- a/.env.example +++ b/.env.example @@ -32,7 +32,9 @@ PORT=8080 # HTTP request metrics start accumulating only when this listener is enabled. # METRICS_ADDR=127.0.0.1:9090 JWT_SECRET=change-me-in-production -MULTICA_SERVER_URL=ws://localhost:8080/ws +# Derived by Makefile / local scripts from the backend port. +# Set explicitly only when the daemon reaches the API through a different URL. +# MULTICA_SERVER_URL=ws://localhost:8080/ws # Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT. # Set explicitly only when the app's public URL differs from local frontend. # MULTICA_APP_URL=http://localhost:3000 @@ -98,7 +100,7 @@ SMTP_TLS_INSECURE=false # rebuild is needed. GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= -# Derived by docker-compose.selfhost.yml from FRONTEND_PORT. +# Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT. # Set explicitly only when your OAuth callback URL differs from local frontend. # GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback @@ -130,7 +132,9 @@ COOKIE_DOMAIN= # Local file storage (fallback when S3_BUCKET is not set) LOCAL_UPLOAD_DIR=./data/uploads -LOCAL_UPLOAD_BASE_URL=http://localhost:8080 +# Derived by Makefile / local scripts from the backend port. +# Set explicitly only when uploads are served through a different public URL. +# LOCAL_UPLOAD_BASE_URL=http://localhost:8080 # Security # Comma-separated list of allowed origins for CORS and WebSocket connections. @@ -179,7 +183,7 @@ GITHUB_WEBHOOK_SECRET= # Frontend FRONTEND_PORT=3000 -# Derived by docker-compose.selfhost.yml from FRONTEND_PORT. +# Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT. # Set explicitly only when serving frontend on a different origin/domain. # FRONTEND_ORIGIN=http://localhost:3000 # Leave empty — auto-derived from page origin in browser, set by Makefile for local dev. diff --git a/Makefile b/Makefile index f31251de7e..3186295dd1 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ NEXT_PUBLIC_API_URL ?= http://localhost:$(PORT) NEXT_PUBLIC_WS_URL ?= ws://localhost:$(PORT)/ws GOOGLE_REDIRECT_URI ?= $(FRONTEND_ORIGIN)/auth/callback MULTICA_SERVER_URL ?= ws://localhost:$(PORT)/ws +LOCAL_UPLOAD_BASE_URL ?= http://localhost:$(PORT) export diff --git a/packages/views/issues/components/issue-detail.tsx b/packages/views/issues/components/issue-detail.tsx index 51170303f7..22488df43a 100644 --- a/packages/views/issues/components/issue-detail.tsx +++ b/packages/views/issues/components/issue-detail.tsx @@ -1463,7 +1463,7 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr onClick={() => setMetadataOpen(true)} > {t(($) => $.detail.section_metadata)} -