diff --git a/package.json b/package.json
index 57d642f..daa3e71 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lms",
- "version": "1.2.6",
+ "version": "1.2.7",
"description": "Autonomy Studio LMS Electron app",
"main": "./out/main/index.js",
"author": {
diff --git a/src/main/bridgeArtifacts.ts b/src/main/bridgeArtifacts.ts
index 69bd417..9b34139 100644
--- a/src/main/bridgeArtifacts.ts
+++ b/src/main/bridgeArtifacts.ts
@@ -15,9 +15,9 @@ const artifacts = [
{
image: jetsonBridgeImage,
architecture: 'arm64',
- url: `${root}/arm64/20260819-0659d4cf5578/slam-ros-bridge-jetson-orin.tar.zst`,
- fileName: 'slam-ros-bridge-jetson-orin-0659d4cf5578.tar.zst',
- checksumSha256: '0659d4cf5578c43bc9c25e4a4fc5d226ccd5b7bec43becfdff83b5390717114e',
+ url: `${root}/arm64/20261013-21acdf0cbdee/slam-ros-bridge-jetson-orin.tar.zst`,
+ fileName: 'slam-ros-bridge-jetson-orin-21acdf0cbdee.tar.zst',
+ checksumSha256: '21acdf0cbdee45d8b7b9dba78c3d321d67d8d7a2467511c4af0826e36ea0c74f',
required: true,
},
]
diff --git a/src/main/index.ts b/src/main/index.ts
index 22c0086..e7536cd 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -2462,9 +2462,12 @@ async function readRemoteVisualizationBridgeStatus(target: RemoteWorkspaceTarget
'docker ps --format "{{.Names}}\\t{{.Status}}" 2>/dev/null || true',
10_000,
)
- const nav2 = output.split('\n').find((line) => line.startsWith('nav2-ros-bridge\t'))
- const autoware = output.split('\n').find((line) => line.startsWith('autoware-ros-bridge\t'))
+ const lines = output.split('\n')
+ const nav2 = lines.find((line) => line.startsWith('nav2-ros-bridge\t'))
+ const slam = lines.find((line) => line.startsWith('slam-ros-bridge\t'))
+ const autoware = lines.find((line) => line.startsWith('autoware-ros-bridge\t'))
if (nav2) return { ok: true, running: true, stack: 'nav2' }
+ if (slam) return { ok: true, running: true, stack: 'slam' }
if (autoware) return { ok: true, running: true, stack: 'autoware' }
return { ok: true, running: false, stack: null }
} catch (error) {
@@ -2472,6 +2475,18 @@ async function readRemoteVisualizationBridgeStatus(target: RemoteWorkspaceTarget
}
}
+const managedVisualizationBridgeEnvKeys = new Set([
+ 'ROS_DOMAIN_ID',
+ 'ROS_LOCALHOST_ONLY',
+ 'FASTDDS_DEFAULT_PROFILES_FILE',
+ 'FASTRTPS_DEFAULT_PROFILES_FILE',
+ 'AUTONOMY_STUDIO_ROS_NETWORK',
+])
+
+function isManagedVisualizationBridgeEnvKey(key: string): boolean {
+ return managedVisualizationBridgeEnvKeys.has(key)
+}
+
async function startRemoteVisualizationBridge(
target: RemoteWorkspaceTarget,
stackInput?: string | null,
@@ -2486,7 +2501,7 @@ async function startRemoteVisualizationBridge(
const fastDdsConfigPath = await prepareRemoteRosNetworkConfig(normalized, '~/.autonomy-studio/runtime/nav2', rosNetwork, rosDomainId)
const containerName = visualizationBridgeContainerName(stack)
const bridgeEnvArgs = Object.entries(environmentProfile?.bridge?.env ?? {})
- .filter(([key]) => /^[A-Z_][A-Z0-9_]*$/.test(key))
+ .filter(([key]) => /^[A-Z_][A-Z0-9_]*$/.test(key) && !isManagedVisualizationBridgeEnvKey(key))
.map(([key, value]) => `-e ${quoteShellSingle(`${key}=${value}`)}`)
.join(' ')
let image = resolveVisualizationBridgeImage(stack, environmentProfile?.bridge, environmentProfile?.runtimeImages)
@@ -4022,7 +4037,9 @@ async function startVisualizationBridge(
CMD_VEL_TOPIC: '/cmd_vel',
NAVIGATE_TO_POSE_ACTION: '/navigate_to_pose'
} : {})
- Object.entries(bridgeEnv).forEach(([key, value]) => args.push('-e', `${key}=${value}`))
+ Object.entries(bridgeEnv).forEach(([key, value]) => {
+ if (!isManagedVisualizationBridgeEnvKey(key)) args.push('-e', `${key}=${value}`)
+ })
if (usesSlamBridge) {
args.push('-e', `PORT=${bridgePort}`)
}
diff --git a/src/renderer/public/slam-viewer/index.html b/src/renderer/public/slam-viewer/index.html
index b0c9d81..f434c5c 100644
--- a/src/renderer/public/slam-viewer/index.html
+++ b/src/renderer/public/slam-viewer/index.html
@@ -3135,6 +3135,9 @@ renderer.domElement.addEventListener('click', async (event) => {
});
function applySlamState(state) {
+ // Any valid state payload confirms that the bridge is responding. Missing
+ // ROS data is reported separately by the topic status indicators.
+ markVisualizationResponseReceived();
latestSlamState = state;
latestSlamStateReceivedAt = Date.now();
resetNavigationPoseForNewSession(state);
@@ -3150,7 +3153,6 @@ function applySlamState(state) {
clearNavigationGoalMarker();
navigationGoalId = null;
}
- if (state?.pose || state?.pose_3d || state?.scan || state?.map || state?.point_cloud || state?.sensor_point_cloud) markVisualizationResponseReceived();
if (NAV2_VIEW_MODE === 'navigation' && state?.tf?.map_to_odom) {
navigationMapToOdom = state.tf.map_to_odom;
updateNavigationMapTransform();
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index ad4b68d..d0ec402 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -990,7 +990,7 @@ function LocalPcWorkspaceMockup({
const surface = dark ? 'bg-[#202020] text-[#e6e6e6]' : 'bg-[#eef1f6] text-[#151827]'
const topbar = dark
? 'border-white/10 bg-[#303030]'
- : 'border-[#cbbdde] bg-[linear-gradient(90deg,#e6ddf1_0%,#e5def1_52%,#dce9f5_100%)]'
+ : 'border-[#cbbdde] bg-[linear-gradient(90deg,#f8f6fb_0%,#f5f2f9_52%,#dce9f5_100%)]'
const panel = dark ? 'border-white/10 bg-[#252525]' : 'border-[#cfd6e4] bg-[#e9edf4]'
const textStrong = dark ? 'text-white' : 'text-[#111520]'
const textMid = dark ? 'text-white/70' : 'text-[#4f596b]'
@@ -1663,6 +1663,7 @@ function LocalPcWorkspaceMockup({
performDeleteTreeTarget,
requestDeleteTreeTarget,
pasteTreeClipboard,
+ moveTreeTarget,
refreshModuleFiles
} = useWorkspaceFileActions({
selectedModule,
@@ -3681,7 +3682,7 @@ function LocalPcWorkspaceMockup({
{workspaceActive && (
void moveTreeTarget(sourcePath, sourceKind, targetFolder)}
onCopyTreePath={() => void copyTreePathToClipboard()}
onRenameTreeTarget={requestRenameTreeTarget}
onDeleteTreeTarget={requestDeleteTreeTarget}
@@ -4112,6 +4114,7 @@ function LocalPcWorkspaceMockup({
)}
{worldEditorOpen && (
{
setWorldLayoutVersion((version) => version + 1)
@@ -5826,12 +5829,12 @@ export default function App(): React.JSX.Element {
const controlStatus = platform.control?.status
const copy = controlStatus === 'QUEUED'
? {
- title: `${platform.title} 대기 예약 취소 중`,
- description: '대기열에서 요청을 제거하고 플랫폼 상태를 다시 확인하고 있습니다.'
+ title: t('{{platform}} 대기 예약 취소 중', { platform: platform.title }),
+ description: t('대기열에서 요청을 제거하고 플랫폼 상태를 다시 확인하고 있습니다.')
}
: {
- title: `${platform.title} 제어권 요청 중`,
- description: '플랫폼 사용 가능 여부와 대기열 상태를 확인하고 있습니다.'
+ title: t('{{platform}} 제어권 요청 중', { platform: platform.title }),
+ description: t('플랫폼 사용 가능 여부와 대기열 상태를 확인하고 있습니다.')
}
const loadingStartedAt = Date.now()
@@ -5846,12 +5849,14 @@ export default function App(): React.JSX.Element {
shouldWaitForControlPaint = true
}
if (result.method === 'DELETE') {
- showTitlebarNotice(`${platform.title} 대기 예약이 취소되었습니다`, 'info', true)
+ showTitlebarNotice(t('{{platform}} 대기 예약이 취소되었습니다', { platform: platform.title }), 'info', true)
} else if (payload?.result === 'GRANTED' || payload?.result === 'ALREADY_GRANTED') {
- showTitlebarNotice(`${platform.title} 제어권이 부여되었습니다`, 'info', true)
+ showTitlebarNotice(t('{{platform}} 제어권이 부여되었습니다', { platform: platform.title }), 'info', true)
} else if (payload?.result === 'QUEUED') {
const position = payload.queue?.position
- showTitlebarNotice(`${platform.title} 대기 예약되었습니다${position ? ` (${position}번째)` : ''}`, 'info', true)
+ showTitlebarNotice(position
+ ? t('{{platform}} 대기 예약되었습니다 ({{position}}번째)', { platform: platform.title, position })
+ : t('{{platform}} 대기 예약되었습니다', { platform: platform.title }), 'info', true)
}
} catch {
// Keep the existing curriculum visible when a control request fails.
diff --git a/src/renderer/src/components/AppStatusModals.tsx b/src/renderer/src/components/AppStatusModals.tsx
index 370a514..a19d1f4 100644
--- a/src/renderer/src/components/AppStatusModals.tsx
+++ b/src/renderer/src/components/AppStatusModals.tsx
@@ -166,7 +166,7 @@ export function RunningPlatformCloseModal({
title={t("개발 스튜디오 종료")}
message={t("{{v0}} 개발 스튜디오를 종료할까요?", { v0: platformTitle === '로컬 PC' ? t('로컬 PC') : platformTitle })}
detail={t("현재 열린 개발 스튜디오 세션만 종료합니다. 플랫폼 제어권은 반납되지 않습니다.")}
- confirmLabel={t("종료")}
+ confirmLabel={t("확인")}
onCancel={onCancel}
onConfirm={onConfirm}
/>
diff --git a/src/renderer/src/components/TitleBar.tsx b/src/renderer/src/components/TitleBar.tsx
index 31a5bdd..3e1f03e 100644
--- a/src/renderer/src/components/TitleBar.tsx
+++ b/src/renderer/src/components/TitleBar.tsx
@@ -1046,7 +1046,7 @@ export default function TitleBar({ user, accessToken = '', onHelpClick, onOpenCo
type="button"
onClick={handleConfirmClose}
className="cursor-pointer rounded-md border border-red-400/35 bg-red-500/15 px-5 py-2 text-[12px] font-bold text-red-100 transition-colors hover:border-red-400/50 hover:bg-red-500/24"
- >{t("종료")}
+ >{t("앱 종료")}
diff --git a/src/renderer/src/features/curriculum/CurriculumHomeScreen.tsx b/src/renderer/src/features/curriculum/CurriculumHomeScreen.tsx
index f69b401..b749862 100644
--- a/src/renderer/src/features/curriculum/CurriculumHomeScreen.tsx
+++ b/src/renderer/src/features/curriculum/CurriculumHomeScreen.tsx
@@ -70,7 +70,7 @@ export default function CurriculumHomeScreen({
-
+
{t("주차별 커리큘럼")}
{curriculumSummary}
diff --git a/src/renderer/src/features/curriculum/LearningInfoCard.tsx b/src/renderer/src/features/curriculum/LearningInfoCard.tsx
index c6b7b20..77360db 100644
--- a/src/renderer/src/features/curriculum/LearningInfoCard.tsx
+++ b/src/renderer/src/features/curriculum/LearningInfoCard.tsx
@@ -84,7 +84,7 @@ export default function LearningInfoCard({
disabled={studioDisabled}
onClick={onStartStudio}
className={[
- 'flex h-[39px] w-full items-center justify-center gap-2 rounded-md border-0 text-[14px] font-bold transition-all duration-150 ease-out',
+ 'flex h-[39px] w-full items-center justify-center gap-2 rounded-md border-0 text-[14px] font-bold leading-none transition-all duration-150 ease-out',
studioDisabled
? 'cursor-not-allowed bg-[#d8dce5] text-[#8a91a0]'
: 'cursor-pointer bg-[#714ef6] text-white shadow-[0_8px_18px_rgba(113,78,246,0.20)] hover:-translate-y-px hover:bg-[#6040df] hover:shadow-[0_10px_20px_rgba(113,78,246,0.26)] active:translate-y-0 active:shadow-[0_6px_14px_rgba(113,78,246,0.18)]'
@@ -97,7 +97,7 @@ export default function LearningInfoCard({
disabled={!hasMaterials}
onClick={onOpenMaterials}
className={[
- 'mt-2.5 flex h-[39px] w-full items-center justify-center gap-2 rounded-md border text-[14px] font-bold transition-all duration-150 ease-out',
+ 'mt-2.5 flex h-[39px] w-full items-center justify-center gap-2 rounded-md border text-[14px] font-bold leading-none transition-all duration-150 ease-out',
hasMaterials
? 'cursor-pointer border-[#714ef6] bg-white text-[#714ef6] hover:-translate-y-px hover:bg-[#f5f2ff] hover:shadow-[0_8px_16px_rgba(113,78,246,0.13)] active:translate-y-0 active:shadow-none'
: 'cursor-not-allowed border-[#d8dce5] bg-[#f3f4f7] text-[#9aa1af]'
diff --git a/src/renderer/src/features/platforms/PlatformCards.tsx b/src/renderer/src/features/platforms/PlatformCards.tsx
index c0459f3..d913921 100644
--- a/src/renderer/src/features/platforms/PlatformCards.tsx
+++ b/src/renderer/src/features/platforms/PlatformCards.tsx
@@ -31,7 +31,7 @@ export function RunningPlatformCard({
-
{platform.platformKey === 'futuredrive-local-pc' ? t('로컬 PC') : platform.title}
+
{platform.platformKey === 'futuredrive-local-pc' ? t('시뮬레이션') : platform.title}
{weekTitle}
@@ -103,7 +103,7 @@ export function PlatformOptionCard({
-
{platform.platformKey === 'futuredrive-local-pc' ? t('로컬 PC') : platform.title}
+ {platform.platformKey === 'futuredrive-local-pc' ? t('시뮬레이션') : platform.title}
{platform.description}
@@ -142,9 +142,9 @@ export function PlatformOptionCard({
+ {t("설정")}
)
diff --git a/src/renderer/src/features/workspace/ModuleListHeader.tsx b/src/renderer/src/features/workspace/ModuleListHeader.tsx
index 067077f..8bb25ba 100644
--- a/src/renderer/src/features/workspace/ModuleListHeader.tsx
+++ b/src/renderer/src/features/workspace/ModuleListHeader.tsx
@@ -8,7 +8,7 @@ type ModuleListHeaderProps = {
export default function ModuleListHeader({ dark }: ModuleListHeaderProps): React.JSX.Element {
useLocaleRender()
return (
-
+
{t("상태")}
{t("분류")}
{t("모듈명")}
diff --git a/src/renderer/src/features/workspace/ModuleListRow.tsx b/src/renderer/src/features/workspace/ModuleListRow.tsx
index 4e105ae..12581aa 100644
--- a/src/renderer/src/features/workspace/ModuleListRow.tsx
+++ b/src/renderer/src/features/workspace/ModuleListRow.tsx
@@ -192,7 +192,7 @@ export default function ModuleListRow
({
useEffect(() => { if (!busy) setProgress('') }, [busy])
const stateStyle: Record = {
running: dark ? 'bg-[#12372d] text-[#72e2b5]' : 'bg-[#dff8ed] text-[#177553]',
- stopped: dark ? 'bg-white/8 text-white/45' : 'bg-[#dce2eb] text-[#3e4c62]',
+ stopped: dark ? 'bg-white/8 text-white/45' : 'bg-[#edf0f4] text-[#3e4c62]',
uninstalled: dark ? 'bg-[#2d2d2d] text-white/45' : 'bg-[#e7deef] text-[#60486f]'
}
const controlActions: Array<{
@@ -300,7 +300,7 @@ export default function ModuleListRow({
{module.cpu.toFixed(module.cpu >= 10 ? 0 : 1)}%
-
+
>
@@ -314,7 +314,7 @@ export default function ModuleListRow({
{module.memory.toFixed(module.memory >= 10 ? 0 : 1)}%
-
+
>
@@ -337,7 +337,7 @@ export default function ModuleListRow({
'group/action relative grid h-7 w-7 cursor-pointer place-items-center rounded-md border transition',
dark
? 'border-[#2f8dff]/35 bg-[#2f8dff]/12 text-[#9cc8ff] hover:bg-[#2f8dff]/22'
- : 'border-[#2f8dff]/28 bg-[#edf6ff] text-[#1d5fae] hover:bg-[#dfefff]'
+ : 'border-[#a78bfa] bg-[#faf7ff] text-[#7c3aed] hover:border-[#7c3aed] hover:bg-[#f3efff]'
].join(' ')}
>
diff --git a/src/renderer/src/features/workspace/ModuleListToolbar.tsx b/src/renderer/src/features/workspace/ModuleListToolbar.tsx
index e004275..685282f 100644
--- a/src/renderer/src/features/workspace/ModuleListToolbar.tsx
+++ b/src/renderer/src/features/workspace/ModuleListToolbar.tsx
@@ -75,7 +75,7 @@ export default function ModuleListToolbar({
]
return (
-
+
{FILTER_TABS.map(({ id, label }) => {
const active = filter === id
@@ -90,10 +90,10 @@ export default function ModuleListToolbar({
active
? dark
? 'border-white/10 bg-[#2b2b2b] text-white'
- : 'border-[#c4b5df] bg-[#e2e8f0] text-[#38205b]'
+ : 'border-[#cfc2e1] bg-[#f8f6fb] text-[#38205b]'
: dark
? 'border-white/8 bg-[#202020] text-white/45 hover:text-white/70'
- : 'border-[#d4c9e4] bg-[#eee8f6] text-[#475569] hover:bg-[#e8def3] hover:text-[#38205b]'
+ : 'border-[#d8cee6] bg-white text-[#475569] hover:bg-[#f7f4fa] hover:text-[#38205b]'
].join(' ')}
>
{t(label)}
diff --git a/src/renderer/src/features/workspace/ModuleListView.tsx b/src/renderer/src/features/workspace/ModuleListView.tsx
index a8d8d65..3662acf 100644
--- a/src/renderer/src/features/workspace/ModuleListView.tsx
+++ b/src/renderer/src/features/workspace/ModuleListView.tsx
@@ -259,7 +259,7 @@ export default function ModuleListView({
return (
-
+
{t("모듈 리스트")}
{t("자율주행 알고리즘 모듈 관리")}
diff --git a/src/renderer/src/features/workspace/WorkspaceEditorPane.tsx b/src/renderer/src/features/workspace/WorkspaceEditorPane.tsx
index 28445ce..c89b427 100644
--- a/src/renderer/src/features/workspace/WorkspaceEditorPane.tsx
+++ b/src/renderer/src/features/workspace/WorkspaceEditorPane.tsx
@@ -52,7 +52,7 @@ function WorkspaceEditorPane({
- {t("파일을 선택하세요")}
+ {t("파일을 선택하세요")}
) : activeFileIsImage ? (
diff --git a/src/renderer/src/features/workspace/WorkspaceEditorTabs.tsx b/src/renderer/src/features/workspace/WorkspaceEditorTabs.tsx
index f26b6b5..31296c0 100644
--- a/src/renderer/src/features/workspace/WorkspaceEditorTabs.tsx
+++ b/src/renderer/src/features/workspace/WorkspaceEditorTabs.tsx
@@ -38,7 +38,7 @@ function WorkspaceEditorTabs({
if (files.length === 0) return null
return (
-
+
{files.map((file, index) => (