diff --git a/.changelog/next/added-issue-4137.md b/.changelog/next/added-issue-4137.md new file mode 100644 index 0000000000..011c4d9842 --- /dev/null +++ b/.changelog/next/added-issue-4137.md @@ -0,0 +1 @@ +- App detection now classifies Python, Go, Docker, and static repos instead of labelling them `unknown`, and PM2 standardization (which writes a Node ecosystem config) is no longer offered for them diff --git a/client/src/components/apps/constants.js b/client/src/components/apps/constants.js index 343173db37..38ac3aa3b4 100644 --- a/client/src/components/apps/constants.js +++ b/client/src/components/apps/constants.js @@ -44,10 +44,42 @@ export function resolveLaunchPanelProcess(app, result) { return result?.results?.[processName]?.success === false ? null : processName; } -export const getAppTypeLabel = (type) => - type === 'ios-native' ? '📱 iOS' : - type === 'macos-native' ? '🖥️ macOS' : - type === 'swift' ? '🐦 Swift' : '🔨 Xcode'; +// Mirrors NON_NODE_TYPES / NON_STANDARDIZABLE_TYPES in +// server/services/streamingDetect.js (parity tests assert the Sets match). The +// PM2 standardizer writes a NODE ecosystem config from a prompt that opens "You +// are analyzing a Node.js application", so a Python/Go/Docker/static repo must +// not be offered the flow. It's a DENY list, not an allowlist of Node types: +// `type` is a free-form string persisted on the app record, so records in the +// wild carry legacy values this file has never heard of, and an allowlist would +// silently withdraw the button from all of them. See the server's rationale. +export const NON_NODE_TYPES = new Set(['python', 'go', 'docker', 'static']); + +export const NON_STANDARDIZABLE_TYPES = new Set([...NON_PM2_TYPES, ...NON_NODE_TYPES]); + +/** Whether the PM2 standardizer (which writes a NODE ecosystem config) applies. */ +export const isStandardizable = (type) => !NON_STANDARDIZABLE_TYPES.has(type); + +// Every app type that can reach a UI label. Kept TOTAL (with a raw-type +// fallback) rather than a ternary chain ending in '🔨 Xcode' — that default +// meant any type not explicitly listed rendered as an Xcode project. +const APP_TYPE_LABELS = { + 'ios-native': '📱 iOS', + 'macos-native': '🖥️ macOS', + swift: '🐦 Swift', + xcode: '🔨 Xcode', + python: '🐍 Python', + go: '🐹 Go', + docker: '🐳 Docker', + static: '📄 Static', + desktop: '🎮 Desktop', + 'vite+express': '⚡ Vite + Express', + vite: '⚡ Vite', + 'single-node-server': '🟢 Node', + express: '🟢 Express', + nextjs: '▲ Next.js' +}; + +export const getAppTypeLabel = (type) => APP_TYPE_LABELS[type] || type || 'Unknown'; // Where an app's autonomous work items live. Mirrors WORK_TRACKERS + // TRACKER_LABELS in server/lib/workTracker.js — shared by the Edit App picker diff --git a/client/src/components/apps/tabs/OverviewTab.jsx b/client/src/components/apps/tabs/OverviewTab.jsx index 54475f5b7f..0d89e88430 100644 --- a/client/src/components/apps/tabs/OverviewTab.jsx +++ b/client/src/components/apps/tabs/OverviewTab.jsx @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo } from 'react'; import { Link } from 'react-router'; import { FolderOpen, Gamepad2, Terminal, Code, RefreshCw, Wrench, Archive, ArchiveRestore, Download, Tag, AlertTriangle, Rocket, Camera, Image, Sparkles } from 'lucide-react'; import toast from '../../ui/Toast'; -import { NON_PM2_TYPES } from '../constants'; +import { isStandardizable } from '../constants'; import ActivityLog from '../ActivityLog'; import SlashDoPanel from '../SlashDoPanel'; import Banner from '../../ui/Banner'; @@ -313,8 +313,10 @@ export default function OverviewTab({ app, onRefresh }) { {detectingIcon ? 'Scanning...' : 'Detect Icon'} {/* PortOS's own ecosystem.config.cjs is the canonical PORTS source — - it is never regenerated from an LLM analysis (the server refuses too). */} - {!NON_PM2_TYPES.has(app.type) && app.id !== api.PORTOS_APP_ID && ( + it is never regenerated from an LLM analysis (the server refuses too). + `isStandardizable` also keeps the button off non-Node repos, whose + ecosystem config the Node-shaped prompt has no business writing. */} + {isStandardizable(app.type) && app.id !== api.PORTOS_APP_ID && ( - {(!app.processes?.length || app.processes.some(p => !p.ports || Object.keys(p.ports).length === 0)) && ( + {/* The standardizer writes a NODE ecosystem config — + never offer it for a Python/Go/Docker/static repo + (the server refuses too). */} + {isStandardizable(app.type) && (!app.processes?.length || app.processes.some(p => !p.ports || Object.keys(p.ports).length === 0)) && (