chore: test deploy snapshot

This commit is contained in:
2026-05-28 15:47:24 +09:00
parent bb275c0534
commit b1bec9cb6f
7 changed files with 253 additions and 60 deletions

View File

@@ -14,7 +14,7 @@ test('extractChatMessageParts normalizes absolute legacy dot-codex prompt previe
assert.ok(prompt); assert.ok(prompt);
assert.equal( assert.equal(
prompt.options[0]?.preview?.url, prompt.options[0]?.preview?.url,
'/api/chat/resources/chat-room/resource/source/chat-room-reference.md', '/api/chat/resources/.codex_chat/chat-room/resource/source/chat-room-reference.md',
); );
}); });
@@ -32,7 +32,7 @@ test('parseChatMessageParts normalizes absolute legacy link card urls to api cha
{ {
type: 'link_card', type: 'link_card',
title: 'legacy resource', title: 'legacy resource',
url: '/api/chat/resources/chat-room/resource/uploads/spec.png', url: '/api/chat/resources/.codex_chat/chat-room/resource/uploads/spec.png',
actionLabel: '열기', actionLabel: '열기',
}, },
]); ]);

View File

@@ -90,6 +90,20 @@ const CHAT_PUBLIC_DOT_CODEX_MARKER = '/public/.codex_chat/';
const RESOURCE_MANAGER_PREVIEW_MARKER = '/api/resource-manager/preview/'; const RESOURCE_MANAGER_PREVIEW_MARKER = '/api/resource-manager/preview/';
const RESOURCE_MANAGER_ROOT_MARKER = 'resource/'; const RESOURCE_MANAGER_ROOT_MARKER = 'resource/';
function buildCanonicalChatApiResourcePath(relativePath: string) {
const normalizedRelativePath = normalizeText(relativePath).replace(/^\/+/, '');
if (!normalizedRelativePath) {
return '';
}
if (normalizedRelativePath.startsWith('.codex_chat/')) {
return `${CHAT_API_RESOURCE_MARKER}${normalizedRelativePath}`;
}
return `${CHAT_API_RESOURCE_MARKER}.codex_chat/${normalizedRelativePath}`;
}
function normalizeText(value: unknown) { function normalizeText(value: unknown) {
return String(value ?? '').trim(); return String(value ?? '').trim();
} }
@@ -239,11 +253,11 @@ function normalizeUrl(value: string) {
} }
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) { if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`; return buildCanonicalChatApiResourcePath(pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length));
} }
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) { if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`; return buildCanonicalChatApiResourcePath(pathname.slice(CHAT_DOT_CODEX_MARKER.length));
} }
} catch { } catch {
// Fall through to handle relative and embedded resource paths below. // Fall through to handle relative and embedded resource paths below.
@@ -259,18 +273,18 @@ function normalizeUrl(value: string) {
const apiPath = normalized.slice(apiMarkerIndex); const apiPath = normalized.slice(apiMarkerIndex);
const dotCodexIndex = apiPath.indexOf(CHAT_DOT_CODEX_MARKER); const dotCodexIndex = apiPath.indexOf(CHAT_DOT_CODEX_MARKER);
return dotCodexIndex >= 0 return dotCodexIndex >= 0
? `${CHAT_API_RESOURCE_MARKER}${apiPath.slice(dotCodexIndex + 1)}` ? buildCanonicalChatApiResourcePath(apiPath.slice(dotCodexIndex + 1))
: apiPath; : apiPath;
} }
const publicDotCodexIndex = normalized.lastIndexOf(CHAT_PUBLIC_DOT_CODEX_MARKER); const publicDotCodexIndex = normalized.lastIndexOf(CHAT_PUBLIC_DOT_CODEX_MARKER);
if (publicDotCodexIndex >= 0) { if (publicDotCodexIndex >= 0) {
return `${CHAT_API_RESOURCE_MARKER}${normalized.slice(publicDotCodexIndex + 8)}`; return buildCanonicalChatApiResourcePath(normalized.slice(publicDotCodexIndex + 8));
} }
const dotCodexIndex = normalized.lastIndexOf(CHAT_DOT_CODEX_MARKER); const dotCodexIndex = normalized.lastIndexOf(CHAT_DOT_CODEX_MARKER);
if (dotCodexIndex >= 0) { if (dotCodexIndex >= 0) {
return `${CHAT_API_RESOURCE_MARKER}${normalized.slice(dotCodexIndex + 1)}`; return buildCanonicalChatApiResourcePath(normalized.slice(dotCodexIndex + 1));
} }
if (normalized === 'resource' || normalized === '/resource' || normalized.startsWith('resource/') || normalized.includes('/resource/')) { if (normalized === 'resource' || normalized === '/resource' || normalized.startsWith('resource/') || normalized.includes('/resource/')) {

View File

@@ -6,6 +6,20 @@ const CHAT_PUBLIC_DOT_CODEX_MARKER = '/public/.codex_chat/';
const RESOURCE_MANAGER_PREVIEW_MARKER = '/api/resource-manager/preview/'; const RESOURCE_MANAGER_PREVIEW_MARKER = '/api/resource-manager/preview/';
const RESOURCE_MANAGER_ROOT_MARKER = 'resource/'; const RESOURCE_MANAGER_ROOT_MARKER = 'resource/';
function buildCanonicalChatApiResourcePath(relativePath: string) {
const normalizedRelativePath = String(relativePath ?? '').trim().replace(/^\/+/, '');
if (!normalizedRelativePath) {
return '';
}
if (normalizedRelativePath.startsWith('.codex_chat/')) {
return `${CHAT_API_RESOURCE_MARKER}${normalizedRelativePath}`;
}
return `${CHAT_API_RESOURCE_MARKER}.codex_chat/${normalizedRelativePath}`;
}
function normalizeUrlFragmentValue(value: string) { function normalizeUrlFragmentValue(value: string) {
const normalized = String(value ?? '').trim().replace(/^#+/, ''); const normalized = String(value ?? '').trim().replace(/^#+/, '');
@@ -154,20 +168,20 @@ function extractEmbeddedResourcePath(value: string) {
const apiPath = normalized.slice(apiMarkerIndex); const apiPath = normalized.slice(apiMarkerIndex);
const dotCodexIndex = apiPath.indexOf(CHAT_DOT_CODEX_MARKER); const dotCodexIndex = apiPath.indexOf(CHAT_DOT_CODEX_MARKER);
return dotCodexIndex >= 0 return dotCodexIndex >= 0
? `${CHAT_API_RESOURCE_MARKER}${apiPath.slice(dotCodexIndex + 1)}` ? buildCanonicalChatApiResourcePath(apiPath.slice(dotCodexIndex + 1))
: apiPath; : apiPath;
} }
const publicDotCodexIndex = normalized.lastIndexOf(CHAT_PUBLIC_DOT_CODEX_MARKER); const publicDotCodexIndex = normalized.lastIndexOf(CHAT_PUBLIC_DOT_CODEX_MARKER);
if (publicDotCodexIndex >= 0) { if (publicDotCodexIndex >= 0) {
return `${CHAT_API_RESOURCE_MARKER}${normalized.slice(publicDotCodexIndex + 8)}`; return buildCanonicalChatApiResourcePath(normalized.slice(publicDotCodexIndex + 8));
} }
const dotCodexIndex = normalized.lastIndexOf(CHAT_DOT_CODEX_MARKER); const dotCodexIndex = normalized.lastIndexOf(CHAT_DOT_CODEX_MARKER);
if (dotCodexIndex >= 0) { if (dotCodexIndex >= 0) {
return `${CHAT_API_RESOURCE_MARKER}${normalized.slice(dotCodexIndex + 1)}`; return buildCanonicalChatApiResourcePath(normalized.slice(dotCodexIndex + 1));
} }
if (normalized === 'resource' || normalized === '/resource' || normalized.startsWith('resource/') || normalized.includes('/resource/')) { if (normalized === 'resource' || normalized === '/resource' || normalized.startsWith('resource/') || normalized.includes('/resource/')) {
@@ -197,11 +211,11 @@ function extractKnownPreviewPath(value: string) {
} }
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) { if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`; return buildCanonicalChatApiResourcePath(pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length));
} }
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) { if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`; return buildCanonicalChatApiResourcePath(pathname.slice(CHAT_DOT_CODEX_MARKER.length));
} }
return normalized; return normalized;

View File

@@ -19,6 +19,20 @@ const CHAT_DOT_CODEX_MARKER = '/.codex_chat/';
const CHAT_PUBLIC_DOT_CODEX_MARKER = '/public/.codex_chat/'; const CHAT_PUBLIC_DOT_CODEX_MARKER = '/public/.codex_chat/';
const RESOURCE_MANAGER_PREVIEW_MARKER = '/api/resource-manager/preview/'; const RESOURCE_MANAGER_PREVIEW_MARKER = '/api/resource-manager/preview/';
const RESOURCE_MANAGER_ROOT_MARKER = 'resource/'; const RESOURCE_MANAGER_ROOT_MARKER = 'resource/';
function buildCanonicalChatApiResourcePath(relativePath: string) {
const normalizedRelativePath = normalizeText(relativePath).replace(/^\/+/, '');
if (!normalizedRelativePath) {
return '';
}
if (normalizedRelativePath.startsWith('.codex_chat/')) {
return `${CHAT_API_RESOURCE_MARKER}${normalizedRelativePath}`;
}
return `${CHAT_API_RESOURCE_MARKER}.codex_chat/${normalizedRelativePath}`;
}
type PromptPart = Extract<ChatMessagePart, { type: 'prompt' }>; type PromptPart = Extract<ChatMessagePart, { type: 'prompt' }>;
type PromptOption = PromptPart['options'][number]; type PromptOption = PromptPart['options'][number];
type PromptPreview = NonNullable<PromptOption['preview']>; type PromptPreview = NonNullable<PromptOption['preview']>;
@@ -174,11 +188,11 @@ function extractKnownPreviewPath(value: string) {
} }
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) { if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`; return buildCanonicalChatApiResourcePath(pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length));
} }
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) { if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`; return buildCanonicalChatApiResourcePath(pathname.slice(CHAT_DOT_CODEX_MARKER.length));
} }
return normalized; return normalized;
@@ -210,18 +224,18 @@ function normalizeUrl(value: string) {
const apiPath = normalized.slice(apiMarkerIndex); const apiPath = normalized.slice(apiMarkerIndex);
const dotCodexIndex = apiPath.indexOf(CHAT_DOT_CODEX_MARKER); const dotCodexIndex = apiPath.indexOf(CHAT_DOT_CODEX_MARKER);
return dotCodexIndex >= 0 return dotCodexIndex >= 0
? `${CHAT_API_RESOURCE_MARKER}${apiPath.slice(dotCodexIndex + 1)}` ? buildCanonicalChatApiResourcePath(apiPath.slice(dotCodexIndex + 1))
: apiPath; : apiPath;
} }
const publicDotCodexIndex = normalized.lastIndexOf(CHAT_PUBLIC_DOT_CODEX_MARKER); const publicDotCodexIndex = normalized.lastIndexOf(CHAT_PUBLIC_DOT_CODEX_MARKER);
if (publicDotCodexIndex >= 0) { if (publicDotCodexIndex >= 0) {
return `${CHAT_API_RESOURCE_MARKER}${normalized.slice(publicDotCodexIndex + 8)}`; return buildCanonicalChatApiResourcePath(normalized.slice(publicDotCodexIndex + 8));
} }
const dotCodexIndex = normalized.lastIndexOf(CHAT_DOT_CODEX_MARKER); const dotCodexIndex = normalized.lastIndexOf(CHAT_DOT_CODEX_MARKER);
if (dotCodexIndex >= 0) { if (dotCodexIndex >= 0) {
return `${CHAT_API_RESOURCE_MARKER}${normalized.slice(dotCodexIndex + 1)}`; return buildCanonicalChatApiResourcePath(normalized.slice(dotCodexIndex + 1));
} }
if (normalized === 'resource' || normalized === '/resource' || normalized.startsWith('resource/') || normalized.includes('/resource/')) { if (normalized === 'resource' || normalized === '/resource' || normalized.startsWith('resource/') || normalized.includes('/resource/')) {

View File

@@ -1658,6 +1658,7 @@
border-radius: 999px; border-radius: 999px;
color: rgba(30, 41, 59, 0.92); color: rgba(30, 41, 59, 0.92);
background: rgba(226, 232, 240, 0.72); background: rgba(226, 232, 240, 0.72);
flex: 0 0 auto;
} }
.chat-share-page__process-inspector-sections { .chat-share-page__process-inspector-sections {
@@ -1673,8 +1674,9 @@
.chat-share-page__process-inspector-section { .chat-share-page__process-inspector-section {
display: grid; display: grid;
gap: 6px; gap: 8px;
min-height: 0; min-height: 0;
align-content: start;
} }
.chat-share-page__process-inspector-section--checklist, .chat-share-page__process-inspector-section--checklist,
@@ -1695,6 +1697,14 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.chat-share-page__process-inspector-section-head-actions {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
min-width: 0;
}
.chat-share-page__process-inspector-checklist, .chat-share-page__process-inspector-checklist,
.chat-share-page__process-inspector-narratives, .chat-share-page__process-inspector-narratives,
.chat-share-page__process-inspector-log { .chat-share-page__process-inspector-log {
@@ -1797,6 +1807,7 @@
@media (max-width: 960px) { @media (max-width: 960px) {
.chat-share-page__process-inspector { .chat-share-page__process-inspector {
width: min(100vw - 16px, 720px); width: min(100vw - 16px, 720px);
max-height: min(100dvh - 16px, 760px);
} }
.chat-share-page__process-inspector-sections { .chat-share-page__process-inspector-sections {
@@ -1811,6 +1822,88 @@
} }
} }
@media (max-width: 640px) {
.chat-share-page__process-inspector {
width: calc(100vw - 12px);
max-height: calc(100dvh - 12px);
border-radius: 18px;
}
.chat-share-page__process-inspector-drag {
align-items: flex-start;
padding: 12px;
}
.chat-share-page__process-inspector-drag-copy {
width: 100%;
}
.chat-share-page__process-inspector-window-actions {
flex: 0 0 auto;
}
.chat-share-page__process-inspector-summary {
gap: 10px;
padding: 12px;
}
.chat-share-page__process-inspector-sections {
gap: 14px;
padding: 12px;
}
.chat-share-page__process-inspector-summary-head,
.chat-share-page__process-inspector-section-head {
align-items: flex-start;
}
.chat-share-page__process-inspector-section-head {
gap: 10px;
}
.chat-share-page__process-inspector-section-head .ant-typography {
flex: 1 1 auto;
min-width: 0;
}
.chat-share-page__process-inspector-section-head-actions {
align-self: stretch;
}
.chat-share-page__process-inspector-table-row,
.chat-share-page__process-inspector-narrative {
grid-template-columns: minmax(0, 1fr);
gap: 4px;
padding: 10px;
}
.chat-share-page__process-inspector-check-item {
grid-template-columns: minmax(0, 1fr);
gap: 6px;
align-items: start;
padding: 10px;
}
.chat-share-page__process-inspector-check-item .ant-tag,
.chat-share-page__process-inspector-summary-toggle.ant-btn {
justify-self: flex-start;
}
.chat-share-page__process-inspector-summary-toggle.ant-btn {
margin-top: 2px;
}
.chat-share-page__process-inspector-log {
max-height: min(38dvh, 320px);
}
.chat-share-page__process-inspector-log-line {
grid-template-columns: 28px minmax(0, 1fr);
gap: 8px;
padding: 9px 10px;
}
}
.chat-share-page__process-inspector-minimized { .chat-share-page__process-inspector-minimized {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;

View File

@@ -186,7 +186,7 @@ type ShareNotificationClientStatus = {
tone: ShareNotificationStatusTone; tone: ShareNotificationStatusTone;
}; };
type ShareProcessInspectorMode = 'default' | 'fullscreen' | 'minimized'; type ShareProcessInspectorMode = 'default' | 'fullscreen' | 'minimized';
type ShareProcessInspectorExpandedSection = 'summary' | 'narratives' | null; type ShareProcessInspectorExpandedSection = 'summary' | 'checklist' | 'narratives' | 'log' | null;
type ShareProcessChecklistStep = { type ShareProcessChecklistStep = {
key: string; key: string;
label: string; label: string;
@@ -5565,7 +5565,7 @@ export function ChatSharePage() {
setActiveProcessInspectorRequestId(requestId); setActiveProcessInspectorRequestId(requestId);
setProcessInspectorMode('default'); setProcessInspectorMode('default');
setProcessInspectorExpandedSection(null); setProcessInspectorExpandedSection('checklist');
}, []); }, []);
const closeProcessInspector = useCallback(() => { const closeProcessInspector = useCallback(() => {
@@ -5577,9 +5577,16 @@ export function ChatSharePage() {
setProcessInspectorExpandedSection((current) => (current === 'summary' ? null : 'summary')); setProcessInspectorExpandedSection((current) => (current === 'summary' ? null : 'summary'));
}, []); }, []);
const handleToggleProcessInspectorChecklist = useCallback(() => {
setProcessInspectorExpandedSection((current) => (current === 'checklist' ? null : 'checklist'));
}, []);
const handleToggleProcessInspectorNarratives = useCallback(() => { const handleToggleProcessInspectorNarratives = useCallback(() => {
setProcessInspectorExpandedSection((current) => (current === 'narratives' ? null : 'narratives')); setProcessInspectorExpandedSection((current) => (current === 'narratives' ? null : 'narratives'));
}, []); }, []);
const handleToggleProcessInspectorLog = useCallback(() => {
setProcessInspectorExpandedSection((current) => (current === 'log' ? null : 'log'));
}, []);
const handleProgramMinimizedPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => { const handleProgramMinimizedPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
if (event.pointerType === 'mouse' && event.button !== 0) { if (event.pointerType === 'mouse' && event.button !== 0) {
@@ -7506,7 +7513,9 @@ export function ChatSharePage() {
[activeProcessInspectorRequestId, requestById], [activeProcessInspectorRequestId, requestById],
); );
const isProcessInspectorSummaryCollapsed = processInspectorExpandedSection !== 'summary'; const isProcessInspectorSummaryCollapsed = processInspectorExpandedSection !== 'summary';
const isProcessInspectorChecklistCollapsed = processInspectorExpandedSection !== 'checklist';
const isProcessInspectorNarrativesCollapsed = processInspectorExpandedSection !== 'narratives'; const isProcessInspectorNarrativesCollapsed = processInspectorExpandedSection !== 'narratives';
const isProcessInspectorLogCollapsed = processInspectorExpandedSection !== 'log';
const activeProcessInspectorPayload = useMemo(() => { const activeProcessInspectorPayload = useMemo(() => {
if (!activeProcessInspectorRequest) { if (!activeProcessInspectorRequest) {
return null; return null;
@@ -7720,7 +7729,17 @@ export function ChatSharePage() {
<section className="chat-share-page__process-inspector-section chat-share-page__process-inspector-section--checklist"> <section className="chat-share-page__process-inspector-section chat-share-page__process-inspector-section--checklist">
<div className="chat-share-page__process-inspector-section-head"> <div className="chat-share-page__process-inspector-section-head">
<Text strong> </Text> <Text strong> </Text>
<Button
type="text"
size="small"
className="chat-share-page__process-inspector-summary-toggle"
icon={isProcessInspectorChecklistCollapsed ? <DownOutlined /> : <UpOutlined />}
onClick={handleToggleProcessInspectorChecklist}
>
{isProcessInspectorChecklistCollapsed ? '보기' : '접기'}
</Button>
</div> </div>
{isProcessInspectorChecklistCollapsed ? null : (
<div className="chat-share-page__process-inspector-checklist" role="table" aria-label="계획 체크리스트"> <div className="chat-share-page__process-inspector-checklist" role="table" aria-label="계획 체크리스트">
{activeProcessInspectorPayload.checklist.map((step) => ( {activeProcessInspectorPayload.checklist.map((step) => (
<div key={step.key} className="chat-share-page__process-inspector-check-item" role="row"> <div key={step.key} className="chat-share-page__process-inspector-check-item" role="row">
@@ -7740,6 +7759,7 @@ export function ChatSharePage() {
</div> </div>
))} ))}
</div> </div>
)}
</section> </section>
<section className="chat-share-page__process-inspector-section chat-share-page__process-inspector-section--narratives"> <section className="chat-share-page__process-inspector-section chat-share-page__process-inspector-section--narratives">
<div className="chat-share-page__process-inspector-section-head"> <div className="chat-share-page__process-inspector-section-head">
@@ -7768,8 +7788,20 @@ export function ChatSharePage() {
<section className="chat-share-page__process-inspector-section chat-share-page__process-inspector-section--log"> <section className="chat-share-page__process-inspector-section chat-share-page__process-inspector-section--log">
<div className="chat-share-page__process-inspector-section-head"> <div className="chat-share-page__process-inspector-section-head">
<Text strong> </Text> <Text strong> </Text>
<div className="chat-share-page__process-inspector-section-head-actions">
<Text type="secondary">{activeProcessInspectorPayload.activityLines.length}</Text> <Text type="secondary">{activeProcessInspectorPayload.activityLines.length}</Text>
<Button
type="text"
size="small"
className="chat-share-page__process-inspector-summary-toggle"
icon={isProcessInspectorLogCollapsed ? <DownOutlined /> : <UpOutlined />}
onClick={handleToggleProcessInspectorLog}
>
{isProcessInspectorLogCollapsed ? '보기' : '접기'}
</Button>
</div> </div>
</div>
{isProcessInspectorLogCollapsed ? null : (
<div className="chat-share-page__process-inspector-log" role="table" aria-label="활동 로그"> <div className="chat-share-page__process-inspector-log" role="table" aria-label="활동 로그">
{activeProcessInspectorPayload.activityLines.length > 0 ? ( {activeProcessInspectorPayload.activityLines.length > 0 ? (
activeProcessInspectorPayload.activityLines.map((line, index) => ( activeProcessInspectorPayload.activityLines.map((line, index) => (
@@ -7782,6 +7814,7 @@ export function ChatSharePage() {
<Text type="secondary"> .</Text> <Text type="secondary"> .</Text>
)} )}
</div> </div>
)}
</section> </section>
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@ import {
SyncOutlined, SyncOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Alert, Button, Empty, Space, Tag, Typography, message } from 'antd'; import { Alert, Button, Empty, Space, Tag, Typography, message } from 'antd';
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { useEffect, useEffectEvent, useMemo, useRef, useState, type ReactNode } from 'react';
import { useTokenAccess } from '../../app/main/tokenAccess'; import { useTokenAccess } from '../../app/main/tokenAccess';
import { DataStatePanel } from '../../components/dataStatePanel'; import { DataStatePanel } from '../../components/dataStatePanel';
import { copyText } from '../../app/main/mainChatPanel'; import { copyText } from '../../app/main/mainChatPanel';
@@ -802,6 +802,13 @@ export function ServerCommandPage({ sharedAccess = null }: ServerCommandPageProp
} }
}; };
const refreshServerCommandState = useEffectEvent((options?: { silent?: boolean }) => Promise.all([
loadItems(options),
loadReservation({ silent: true }),
loadWorkServerDeployment({ silent: true }),
loadTestDeployment({ silent: true }),
]));
useEffect(() => { useEffect(() => {
if (!hasAccess && !isSharedManageMode) { if (!hasAccess && !isSharedManageMode) {
setItems([]); setItems([]);
@@ -813,7 +820,7 @@ export function ServerCommandPage({ sharedAccess = null }: ServerCommandPageProp
return; return;
} }
void Promise.all([loadItems(), loadReservation({ silent: true }), loadWorkServerDeployment({ silent: true }), loadTestDeployment({ silent: true })]); void refreshServerCommandState();
}, [allowedKeysKey, hasAccess, isSharedManageMode, sharedAccess?.shareToken]); }, [allowedKeysKey, hasAccess, isSharedManageMode, sharedAccess?.shareToken]);
useEffect(() => { useEffect(() => {
@@ -834,12 +841,7 @@ export function ServerCommandPage({ sharedAccess = null }: ServerCommandPageProp
const refresh = async () => { const refresh = async () => {
try { try {
await Promise.all([ await refreshServerCommandState({ silent: true });
loadItems({ silent: true }),
loadReservation({ silent: true }),
loadWorkServerDeployment({ silent: true }),
loadTestDeployment({ silent: true }),
]);
} catch { } catch {
if (!cancelled) { if (!cancelled) {
// ignore polling errors and keep the latest visible state // ignore polling errors and keep the latest visible state
@@ -858,6 +860,28 @@ export function ServerCommandPage({ sharedAccess = null }: ServerCommandPageProp
}; };
}, [hasAccess, isSharedManageMode, runningActionKey, sharedAccess?.shareToken, testDeployment?.status, workServerDeployment?.status]); }, [hasAccess, isSharedManageMode, runningActionKey, sharedAccess?.shareToken, testDeployment?.status, workServerDeployment?.status]);
useEffect(() => {
if (!hasAccess && !isSharedManageMode) {
return undefined;
}
const handleWindowAttention = () => {
if (document.visibilityState !== 'visible') {
return;
}
void refreshServerCommandState({ silent: true });
};
window.addEventListener('focus', handleWindowAttention);
document.addEventListener('visibilitychange', handleWindowAttention);
return () => {
window.removeEventListener('focus', handleWindowAttention);
document.removeEventListener('visibilitychange', handleWindowAttention);
};
}, [hasAccess, isSharedManageMode, refreshServerCommandState]);
useEffect(() => { useEffect(() => {
if (!workServerDeployment || workServerDeployment.status === 'idle' || workServerDeployment.status === 'running') { if (!workServerDeployment || workServerDeployment.status === 'idle' || workServerDeployment.status === 'running') {
return; return;
@@ -874,6 +898,7 @@ export function ServerCommandPage({ sharedAccess = null }: ServerCommandPageProp
} }
if (workServerDeployment.status === 'completed') { if (workServerDeployment.status === 'completed') {
void refreshServerCommandState({ silent: true });
void messageApi.success('WORK 서버 무중단 배포가 완료되었습니다.'); void messageApi.success('WORK 서버 무중단 배포가 완료되었습니다.');
return; return;
} }