feat: separate codex live and automation context
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
- 문서 정리나 Codex 작업 시 Git 원격 동기화, 브랜치 운영, 자동 merge 흐름은 기본 전제로 사용하지 않습니다.
|
- 문서 정리나 Codex 작업 시 Git 원격 동기화, 브랜치 운영, 자동 merge 흐름은 기본 전제로 사용하지 않습니다.
|
||||||
- `Codex Live`, 일반 채팅, 일반 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 `main` 작업본을 바로 수정**하는 방식으로 처리합니다.
|
- `Codex Live`, 일반 채팅, 일반 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 `main` 작업본을 바로 수정**하는 방식으로 처리합니다.
|
||||||
- 자동화 작업메모도 별도 브랜치 흐름을 이 문서에 고정하지 않고, 실제 운영 설정과 요청 문맥을 기준으로 처리합니다.
|
- 자동화 작업메모도 별도 브랜치 흐름을 이 문서에 고정하지 않고, 실제 운영 설정과 요청 문맥을 기준으로 처리합니다.
|
||||||
|
- 자동화와 `Codex Live`는 별개로 취급하며, 자동화는 선택된 자동화 유형 context만 우선 참조합니다.
|
||||||
- Git 관련 작업은 사용자가 명시적으로 요청할 때만 수행합니다.
|
- Git 관련 작업은 사용자가 명시적으로 요청할 때만 수행합니다.
|
||||||
|
|
||||||
## 1. 작업일지
|
## 1. 작업일지
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Plan 자동화 기능의 데이터 구조, API 연동 방식, release 검수 연계 방식을 정리합니다.
|
Plan 자동화 기능의 데이터 구조, API 연동 방식, release 검수 연계 방식을 정리합니다.
|
||||||
|
|
||||||
현재 문서는 자동화 브랜치 전략 자체를 고정 규칙으로 설명하지 않습니다. 자동화 처리 방식은 실제 서버 설정, 요청 문맥, 현재 운영 규칙을 함께 확인해 판단합니다. 반면 `Codex Live`나 일반 수동 요청은 로컬 작업본 기준으로 처리합니다.
|
현재 문서는 자동화 브랜치 전략 자체를 고정 규칙으로 설명하지 않습니다. 자동화 처리 방식은 실제 서버 설정, 요청 문맥, 현재 운영 규칙을 함께 확인해 판단합니다. 자동화와 `Codex Live`는 별개이며, 자동화 실행기는 선택된 자동화 유형의 context만 우선 참조합니다. 반면 `Codex Live`나 일반 수동 요청은 로컬 작업본 기준으로 처리합니다.
|
||||||
|
|
||||||
## 구현 위치
|
## 구현 위치
|
||||||
|
|
||||||
@@ -38,6 +38,12 @@ Plan 자동화 기능의 데이터 구조, API 연동 방식, release 검수 연
|
|||||||
|
|
||||||
기존 저장값인 `plan_registration`, `general_development`는 서버에서 각각 `plan`, `auto_worker`로 정규화합니다.
|
기존 저장값인 `plan_registration`, `general_development`는 서버에서 각각 `plan`, `auto_worker`로 정규화합니다.
|
||||||
|
|
||||||
|
자동화 context 해석 규칙:
|
||||||
|
|
||||||
|
- 자동화 실행 시 기본 문맥은 선택된 자동화 유형 description/context만 사용
|
||||||
|
- `Codex Live` 채팅 문맥, 일반 채팅 문맥은 자동화 기본 context로 섞지 않음
|
||||||
|
- 추가 지시가 필요하면 요청 본문에 명시적으로 적어 전달
|
||||||
|
|
||||||
## API 연동 방식
|
## API 연동 방식
|
||||||
|
|
||||||
기본 API 베이스 URL 규칙:
|
기본 API 베이스 URL 규칙:
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ npm run server-command:runner
|
|||||||
|
|
||||||
현재 운영 기준에서는 `Codex Live`, 일반 채팅, 일반 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 작업본을 직접 기준으로 처리**합니다.
|
현재 운영 기준에서는 `Codex Live`, 일반 채팅, 일반 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 작업본을 직접 기준으로 처리**합니다.
|
||||||
|
|
||||||
|
`Codex Live`와 `Plan` 자동화는 별개입니다. `Codex Live`는 채팅 유형과 현재 화면 문맥을 기준으로 동작하고, 자동화 유형 context를 기본 문맥으로 섞지 않습니다.
|
||||||
|
|
||||||
브라우저 기준 접속 확인, 화면 검증, 외부 도메인 테스트는 **`https://test.sm-home.cloud/`를 기본 작업 도메인으로 사용**합니다. 별도 요청이 없는 한 `sm-home.cloud`나 `rel.sm-home.cloud`는 기본 검증 대상으로 삼지 않습니다.
|
브라우저 기준 접속 확인, 화면 검증, 외부 도메인 테스트는 **`https://test.sm-home.cloud/`를 기본 작업 도메인으로 사용**합니다. 별도 요청이 없는 한 `sm-home.cloud`나 `rel.sm-home.cloud`는 기본 검증 대상으로 삼지 않습니다.
|
||||||
|
|
||||||
채팅에서 파일, 문서, 이미지, 코드 같은 리소스를 제공할 때의 기본 공개 경로는 `public/.codex_chat/<chat-session-id>/resource/...`입니다. Codex가 원본 파일 경로만 답해도 서버가 이 위치로 세션 전용 사본을 만들고, 채팅에는 공개 URL을 다시 적어 줍니다.
|
채팅에서 파일, 문서, 이미지, 코드 같은 리소스를 제공할 때의 기본 공개 경로는 `public/.codex_chat/<chat-session-id>/resource/...`입니다. Codex가 원본 파일 경로만 답해도 서버가 이 위치로 세션 전용 사본을 만들고, 채팅에는 공개 URL을 다시 적어 줍니다.
|
||||||
@@ -71,6 +73,8 @@ npm run server-command:runner
|
|||||||
|
|
||||||
현재 운영 기준에서 자동화 작업메모의 세부 Git 절차는 이 문서에 고정하지 않습니다. 상태 전이와 실제 처리 흐름은 worker 구현, 환경 변수, 현재 운영 정책을 함께 확인해야 합니다.
|
현재 운영 기준에서 자동화 작업메모의 세부 Git 절차는 이 문서에 고정하지 않습니다. 상태 전이와 실제 처리 흐름은 worker 구현, 환경 변수, 현재 운영 정책을 함께 확인해야 합니다.
|
||||||
|
|
||||||
|
자동화 실행기는 선택된 자동화 유형의 description/context만 우선 참조합니다. `Codex Live`나 일반 채팅 문맥은 자동화 기본 context로 사용하지 않습니다.
|
||||||
|
|
||||||
안전 조건:
|
안전 조건:
|
||||||
|
|
||||||
- Git worktree가 깨끗해야 동작
|
- Git worktree가 깨끗해야 동작
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ export const DEFAULT_AUTOMATION_TYPES: AutomationTypeRecord[] = [
|
|||||||
{
|
{
|
||||||
id: 'auto_worker',
|
id: 'auto_worker',
|
||||||
name: 'autoWorker',
|
name: 'autoWorker',
|
||||||
description: '자동화 작업메모로 처리하며, 세부 절차는 현재 운영 설정을 따릅니다.',
|
description:
|
||||||
|
'자동화 작업메모로 처리합니다.\n\n## context 사용 규칙\n- 자동화 실행기는 선택된 자동화 유형의 context만 우선 참조합니다.\n- Codex Live 문맥이나 일반 채팅 문맥은 자동화 기본 context로 섞지 않습니다.\n\n## 처리 원칙\n- 세부 절차는 현재 운영 설정을 따릅니다.',
|
||||||
behaviorType: 'auto_worker',
|
behaviorType: 'auto_worker',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
updatedAt: '2026-04-23T00:00:00.000Z',
|
updatedAt: '2026-04-23T00:00:00.000Z',
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ test('buildBoardPostPlanNote formats automation work memo with clear sections',
|
|||||||
'- 게시판 제목: 알림 개선',
|
'- 게시판 제목: 알림 개선',
|
||||||
'- 메모 출처: board_posts 자동화 접수',
|
'- 메모 출처: board_posts 자동화 접수',
|
||||||
'- 선택 자동화 유형: 자동화 메모',
|
'- 선택 자동화 유형: 자동화 메모',
|
||||||
|
'- 자동화 처리 원칙: 아래 자동화 유형 context만 우선 참조하고, Codex Live 문맥은 섞지 않습니다.',
|
||||||
'',
|
'',
|
||||||
'## 선택된 유형 context',
|
'## 자동화 유형 context',
|
||||||
'## 처리 기준\n- 선택된 유형의 context를 먼저 읽습니다.',
|
'## 처리 기준\n- 선택된 유형의 context를 먼저 읽습니다.',
|
||||||
'',
|
'',
|
||||||
'## 요청 본문',
|
'## 요청 본문',
|
||||||
@@ -36,8 +37,9 @@ test('buildBoardPostPlanNote keeps context section even when automation type des
|
|||||||
'- 게시판 제목: 작업',
|
'- 게시판 제목: 작업',
|
||||||
'- 메모 출처: board_posts 자동화 접수',
|
'- 메모 출처: board_posts 자동화 접수',
|
||||||
'- 선택 자동화 유형: 빈 context 유형',
|
'- 선택 자동화 유형: 빈 context 유형',
|
||||||
|
'- 자동화 처리 원칙: 아래 자동화 유형 context만 우선 참조하고, Codex Live 문맥은 섞지 않습니다.',
|
||||||
'',
|
'',
|
||||||
'## 선택된 유형 context',
|
'## 자동화 유형 context',
|
||||||
'선택된 자동화 유형 context 없음',
|
'선택된 자동화 유형 context 없음',
|
||||||
'',
|
'',
|
||||||
'## 요청 본문',
|
'## 요청 본문',
|
||||||
|
|||||||
@@ -91,8 +91,9 @@ export function buildBoardPostPlanNote(title: string, content: string, automatio
|
|||||||
`- 게시판 제목: ${normalizedTitle}`,
|
`- 게시판 제목: ${normalizedTitle}`,
|
||||||
'- 메모 출처: board_posts 자동화 접수',
|
'- 메모 출처: board_posts 자동화 접수',
|
||||||
normalizedAutomationTypeName ? `- 선택 자동화 유형: ${normalizedAutomationTypeName}` : null,
|
normalizedAutomationTypeName ? `- 선택 자동화 유형: ${normalizedAutomationTypeName}` : null,
|
||||||
|
'- 자동화 처리 원칙: 아래 자동화 유형 context만 우선 참조하고, Codex Live 문맥은 섞지 않습니다.',
|
||||||
'',
|
'',
|
||||||
'## 선택된 유형 context',
|
'## 자동화 유형 context',
|
||||||
normalizedAutomationContext || '선택된 자동화 유형 context 없음',
|
normalizedAutomationContext || '선택된 자동화 유형 context 없음',
|
||||||
'',
|
'',
|
||||||
'## 요청 본문',
|
'## 요청 본문',
|
||||||
|
|||||||
@@ -1432,6 +1432,7 @@ function buildAgenticCodexPrompt(
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'당신은 이 저장소에서 Codex Live 요청을 처리하는 실제 Codex 실행기입니다.',
|
'당신은 이 저장소에서 Codex Live 요청을 처리하는 실제 Codex 실행기입니다.',
|
||||||
|
'Codex Live는 Plan 자동화와 별개입니다. 자동화 유형 context를 Codex Live 기본 문맥으로 섞지 마세요.',
|
||||||
`저장소 루트(main_project): ${repoPath}`,
|
`저장소 루트(main_project): ${repoPath}`,
|
||||||
'반드시 저장소 루트의 AGENTS.md를 먼저 읽고 그 규칙을 따르세요.',
|
'반드시 저장소 루트의 AGENTS.md를 먼저 읽고 그 규칙을 따르세요.',
|
||||||
'가능한 작업 범위:',
|
'가능한 작업 범위:',
|
||||||
@@ -1457,6 +1458,7 @@ function buildAgenticCodexPrompt(
|
|||||||
`- chatTypeLabel: ${context?.chatTypeLabel ?? '없음'}`,
|
`- chatTypeLabel: ${context?.chatTypeLabel ?? '없음'}`,
|
||||||
`- chatTypeDescription: ${context?.chatTypeDescription ?? '없음'}`,
|
`- chatTypeDescription: ${context?.chatTypeDescription ?? '없음'}`,
|
||||||
'- 답변 스타일과 기본 문맥은 반드시 채팅 유형 정보만 기준으로 적용하세요.',
|
'- 답변 스타일과 기본 문맥은 반드시 채팅 유형 정보만 기준으로 적용하세요.',
|
||||||
|
'- Plan 자동화용 자동화 유형 context가 있더라도 Codex Live에서는 별도 요청이 없는 한 참조하지 마세요.',
|
||||||
'',
|
'',
|
||||||
'참고 화면 정보:',
|
'참고 화면 정보:',
|
||||||
`- pageTitle: ${context?.pageTitle ?? '없음'}`,
|
`- pageTitle: ${context?.pageTitle ?? '없음'}`,
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import {
|
|||||||
BellOutlined,
|
BellOutlined,
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
CopyOutlined,
|
CopyOutlined,
|
||||||
|
DownOutlined,
|
||||||
FileMarkdownOutlined,
|
FileMarkdownOutlined,
|
||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
MenuUnfoldOutlined,
|
MenuUnfoldOutlined,
|
||||||
ProfileOutlined,
|
ProfileOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
|
RightOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
@@ -883,6 +885,7 @@ export function MainHeader({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
const [automationGroupExpanded, setAutomationGroupExpanded] = useState(false);
|
||||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
const [activeSettingsModal, setActiveSettingsModal] = useState<SettingsModalKey>('appSettings');
|
const [activeSettingsModal, setActiveSettingsModal] = useState<SettingsModalKey>('appSettings');
|
||||||
const [activeAppSettingsCategory, setActiveAppSettingsCategory] = useState<AppSettingsCategoryKey>('automation');
|
const [activeAppSettingsCategory, setActiveAppSettingsCategory] = useState<AppSettingsCategoryKey>('automation');
|
||||||
@@ -958,6 +961,8 @@ export function MainHeader({
|
|||||||
const workServerPendingUpdateCount =
|
const workServerPendingUpdateCount =
|
||||||
workServerStatus && (workServerStatus.updateAvailable || workServerStatus.buildRequired) ? 1 : 0;
|
workServerStatus && (workServerStatus.updateAvailable || workServerStatus.buildRequired) ? 1 : 0;
|
||||||
const totalPendingUpdateCount = testServerPendingUpdateCount + prodServerPendingUpdateCount + workServerPendingUpdateCount;
|
const totalPendingUpdateCount = testServerPendingUpdateCount + prodServerPendingUpdateCount + workServerPendingUpdateCount;
|
||||||
|
const totalAutomationShortcutCount =
|
||||||
|
planShortcutCounts.working + planShortcutCounts.releasePendingMain + planShortcutCounts.automationFailed;
|
||||||
const settingsStatusClassName =
|
const settingsStatusClassName =
|
||||||
totalPendingUpdateCount >= 2
|
totalPendingUpdateCount >= 2
|
||||||
? 'app-header__status-dot--inactive'
|
? 'app-header__status-dot--inactive'
|
||||||
@@ -2621,68 +2626,20 @@ export function MainHeader({
|
|||||||
const settingsMenu = (
|
const settingsMenu = (
|
||||||
<div className="app-header__settings-menu">
|
<div className="app-header__settings-menu">
|
||||||
{hasAccess ? (
|
{hasAccess ? (
|
||||||
<>
|
<div className="app-header__settings-group">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="app-header__settings-item"
|
className="app-header__settings-item"
|
||||||
|
aria-expanded={automationGroupExpanded}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSettingsOpen(false);
|
setAutomationGroupExpanded((current) => !current);
|
||||||
onOpenPlanQuickFilter('working');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="app-header__settings-icon">
|
|
||||||
<ProfileOutlined />
|
|
||||||
<span
|
|
||||||
className={`app-header__status-dot ${
|
|
||||||
planShortcutCounts.working > 0
|
|
||||||
? 'app-header__status-dot--active'
|
|
||||||
: 'app-header__status-dot--inactive'
|
|
||||||
}`}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="app-header__settings-label">
|
|
||||||
작업중 항목
|
|
||||||
<Text type="secondary"> {planShortcutCounts.working}건</Text>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="app-header__settings-item"
|
|
||||||
onClick={() => {
|
|
||||||
setSettingsOpen(false);
|
|
||||||
onOpenPlanQuickFilter('release-pending-main');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="app-header__settings-icon">
|
|
||||||
<ProfileOutlined />
|
|
||||||
<span
|
|
||||||
className={`app-header__status-dot ${
|
|
||||||
planShortcutCounts.releasePendingMain > 0
|
|
||||||
? 'app-header__status-dot--warning'
|
|
||||||
: 'app-header__status-dot--inactive'
|
|
||||||
}`}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="app-header__settings-label">
|
|
||||||
release 상태 작업
|
|
||||||
<Text type="secondary"> {planShortcutCounts.releasePendingMain}건</Text>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="app-header__settings-item"
|
|
||||||
onClick={() => {
|
|
||||||
setSettingsOpen(false);
|
|
||||||
onOpenPlanQuickFilter('automation-failed');
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="app-header__settings-icon">
|
<span className="app-header__settings-icon">
|
||||||
<ReloadOutlined />
|
<ReloadOutlined />
|
||||||
<span
|
<span
|
||||||
className={`app-header__status-dot ${
|
className={`app-header__status-dot ${
|
||||||
planShortcutCounts.automationFailed > 0
|
totalAutomationShortcutCount > 0
|
||||||
? 'app-header__status-dot--warning'
|
? 'app-header__status-dot--warning'
|
||||||
: 'app-header__status-dot--inactive'
|
: 'app-header__status-dot--inactive'
|
||||||
}`}
|
}`}
|
||||||
@@ -2690,11 +2647,93 @@ export function MainHeader({
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="app-header__settings-label">
|
<span className="app-header__settings-label">
|
||||||
자동화 실패
|
자동화 건수
|
||||||
<Text type="secondary"> {planShortcutCounts.automationFailed}건</Text>
|
<Text type="secondary"> {totalAutomationShortcutCount}건</Text>
|
||||||
|
</span>
|
||||||
|
<span className="app-header__settings-group-arrow" aria-hidden="true">
|
||||||
|
{automationGroupExpanded ? <DownOutlined /> : <RightOutlined />}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</>
|
{automationGroupExpanded ? (
|
||||||
|
<div className="app-header__settings-group-children">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="app-header__settings-item app-header__settings-item--nested"
|
||||||
|
onClick={() => {
|
||||||
|
setSettingsOpen(false);
|
||||||
|
setAutomationGroupExpanded(false);
|
||||||
|
onOpenPlanQuickFilter('working');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="app-header__settings-icon">
|
||||||
|
<ProfileOutlined />
|
||||||
|
<span
|
||||||
|
className={`app-header__status-dot ${
|
||||||
|
planShortcutCounts.working > 0
|
||||||
|
? 'app-header__status-dot--active'
|
||||||
|
: 'app-header__status-dot--inactive'
|
||||||
|
}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="app-header__settings-label">
|
||||||
|
작업중 항목
|
||||||
|
<Text type="secondary"> {planShortcutCounts.working}건</Text>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="app-header__settings-item app-header__settings-item--nested"
|
||||||
|
onClick={() => {
|
||||||
|
setSettingsOpen(false);
|
||||||
|
setAutomationGroupExpanded(false);
|
||||||
|
onOpenPlanQuickFilter('release-pending-main');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="app-header__settings-icon">
|
||||||
|
<ProfileOutlined />
|
||||||
|
<span
|
||||||
|
className={`app-header__status-dot ${
|
||||||
|
planShortcutCounts.releasePendingMain > 0
|
||||||
|
? 'app-header__status-dot--warning'
|
||||||
|
: 'app-header__status-dot--inactive'
|
||||||
|
}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="app-header__settings-label">
|
||||||
|
release 상태 작업
|
||||||
|
<Text type="secondary"> {planShortcutCounts.releasePendingMain}건</Text>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="app-header__settings-item app-header__settings-item--nested"
|
||||||
|
onClick={() => {
|
||||||
|
setSettingsOpen(false);
|
||||||
|
setAutomationGroupExpanded(false);
|
||||||
|
onOpenPlanQuickFilter('automation-failed');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="app-header__settings-icon">
|
||||||
|
<ReloadOutlined />
|
||||||
|
<span
|
||||||
|
className={`app-header__status-dot ${
|
||||||
|
planShortcutCounts.automationFailed > 0
|
||||||
|
? 'app-header__status-dot--warning'
|
||||||
|
: 'app-header__status-dot--inactive'
|
||||||
|
}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="app-header__settings-label">
|
||||||
|
자동화 실패
|
||||||
|
<Text type="secondary"> {planShortcutCounts.automationFailed}건</Text>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -174,10 +174,23 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-header__settings-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.app-header__settings-item:hover {
|
.app-header__settings-item:hover {
|
||||||
background: #f3f7ff;
|
background: #f3f7ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-header__settings-item--nested {
|
||||||
|
margin-left: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
padding-left: 14px;
|
||||||
|
background: #f8fbff;
|
||||||
|
}
|
||||||
|
|
||||||
.app-header__settings-icon {
|
.app-header__settings-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -241,10 +254,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-header__settings-label {
|
.app-header__settings-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex: 1;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-header__settings-group-arrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 16px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.app-header__update-progress {
|
.app-header__update-progress {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user