Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ export const App = () => {
});
const [pomodoroSessionType, setPomodoroSessionType] = useState<"focus" | "break" | "longBreak">("focus");
const [pomodoroIsRunning, setPomodoroIsRunning] = useState(false);
const [customCss, setCustomCss] = useState(() => {
return localStorage.getItem("textodon.customCss") || "";
});
const [settingsOpen, setSettingsOpen] = useState(false);
const [settingsAccountId, setSettingsAccountId] = useState<string | null>(null);
const [reauthLoading, setReauthLoading] = useState(false);
Expand Down Expand Up @@ -448,7 +451,19 @@ export const App = () => {
localStorage.setItem("textodon.pomodoro.longBreak", String(pomodoroLongBreak));
}, [pomodoroLongBreak]);


useEffect(() => {
let styleTag = document.getElementById("textodon-custom-css");
if (customCss.trim()) {
if (!styleTag) {
styleTag = document.createElement("style");
styleTag.id = "textodon-custom-css";
document.head.appendChild(styleTag);
}
styleTag.textContent = customCss;
} else if (styleTag) {
styleTag.remove();
}
}, [customCss]);

const closeMobileMenu = useCallback(() => {
setMobileMenuOpen(false);
Expand Down Expand Up @@ -1392,6 +1407,11 @@ export const App = () => {
onPomodoroFocusChange={setPomodoroFocus}
onPomodoroBreakChange={setPomodoroBreak}
onPomodoroLongBreakChange={setPomodoroLongBreak}
customCss={customCss}
onCustomCssApply={(value) => {
localStorage.setItem("textodon.customCss", value);
window.location.reload();
}}
Comment on lines +1411 to +1414
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: onCustomCssApplyuseEffect 로직 단절

useEffect(L454-466)에서 customCss state를 감시하며 동적으로 <style> 태그를 관리하고 있지만, 이 콜백에서는 setCustomCss()를 호출하지 않고 window.location.reload()로 페이지를 리로드합니다.

현재 "우연히 동작하는" 구조입니다:

  1. 리로드 → useState(() => localStorage.getItem(...)) 초기값 로드 → useEffect 발화

문제점:

  • useEffect의 동적 CSS 주입 로직이 apply 시점에는 사용되지 않음 (dead path)
  • 리로드로 인해 스크롤 위치, 모달 상태 등 모든 UI 상태 손실
  • 두 가지 메커니즘(state 기반 주입 vs 리로드 기반 적용)이 혼재

수정 방향: setCustomCss(value)를 호출하여 useEffect가 실시간 반영하도록 통일하고, 리로드를 제거합니다. 이 경우 SettingsModal의 버튼 텍스트도 적용 (페이지 새로고침)적용으로 변경이 필요합니다.

Suggested change
onCustomCssApply={(value) => {
localStorage.setItem("textodon.customCss", value);
window.location.reload();
}}
onCustomCssApply={(value) => {
setCustomCss(value);
localStorage.setItem("textodon.customCss", value);
}}

/>

{profileTargets.map((target, index) => (
Expand Down
31 changes: 30 additions & 1 deletion src/ui/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React, { useState } from "react";
import type { AccountsState } from "../state/AppContext";
import type { ColorScheme, ThemeMode } from "../utils/theme";
import { AccountSelector } from "./AccountSelector";
Expand All @@ -24,6 +25,8 @@ type SettingsModalProps = {
onPomodoroFocusChange: (value: number) => void;
onPomodoroBreakChange: (value: number) => void;
onPomodoroLongBreakChange: (value: number) => void;
customCss: string;
onCustomCssApply: (value: string) => void;
};

export const SettingsModal = ({
Expand All @@ -47,8 +50,12 @@ export const SettingsModal = ({
pomodoroLongBreak,
onPomodoroFocusChange,
onPomodoroBreakChange,
onPomodoroLongBreakChange
onPomodoroLongBreakChange,
customCss,
onCustomCssApply
}: SettingsModalProps) => {
const [localCustomCss, setLocalCustomCss] = useState(customCss);

if (!open) {
return null;
}
Expand Down Expand Up @@ -207,6 +214,28 @@ export const SettingsModal = ({
모두 삭제
</button>
</div>
<div className="settings-item-divider" />
<details className="settings-custom-css">
<summary>
<strong>Custom CSS</strong>
<p>직접 CSS를 작성하여 스타일을 커스텀합니다.</p>
</summary>
<div className="settings-custom-css-content">
<textarea
value={localCustomCss}
onChange={(e) => setLocalCustomCss(e.target.value)}
placeholder="/* 여기에 CSS를 입력하세요 */"
rows={10}
/>
Comment on lines +224 to +229
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 접근성: aria-label 누락

AGENTS.md 원칙("버튼/아이콘에 aria-label, 텍스트 대체를 제공한다")에 따라 입력 요소에 접근성 레이블이 필요합니다. placeholder는 스크린 리더에서 일관되게 지원되지 않으므로 aria-label을 추가해주세요.

Suggested change
<textarea
value={localCustomCss}
onChange={(e) => setLocalCustomCss(e.target.value)}
placeholder="/* 여기에 CSS를 입력하세요 */"
rows={10}
/>
<textarea
value={localCustomCss}
onChange={(e) => setLocalCustomCss(e.target.value)}
placeholder="/* 여기에 CSS를 입력하세요 */"
rows={10}
aria-label="Custom CSS 입력"
/>

<button
type="button"
className="settings-apply-button"
onClick={() => onCustomCssApply(localCustomCss)}
>
적용 (페이지 새로고침)
</button>
Comment on lines +230 to +236
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Critical 수정 시 연동 필요: 버튼 텍스트 변경

App.tsx의 onCustomCssApply에서 window.location.reload()를 제거하고 setCustomCss()로 실시간 반영하도록 수정할 경우, 이 버튼의 텍스트에서 (페이지 새로고침) 안내도 함께 제거해야 합니다.

Suggested change
<button
type="button"
className="settings-apply-button"
onClick={() => onCustomCssApply(localCustomCss)}
>
적용 (페이지 새로고침)
</button>
<button
type="button"
className="settings-apply-button"
onClick={() => onCustomCssApply(localCustomCss)}
aria-label="Custom CSS 적용"
>
적용
</button>

</div>
</details>
</div>
</div>
</div>
Expand Down
71 changes: 71 additions & 0 deletions src/ui/styles/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,75 @@
color: var(--color-action-danger-text);
}

.settings-item-divider {
height: 1px;
background: var(--color-settings-item-border);
margin: 16px 0;
}

.settings-custom-css {
border: 1px solid var(--color-settings-item-border);
border-radius: 12px;
overflow: hidden;
margin-top: 10px;
}

.settings-custom-css summary {
padding: 12px 16px;
cursor: pointer;
background: var(--color-input-bg);
user-select: none;
list-style: none;
}

.settings-custom-css summary::-webkit-details-marker {
display: none;
}
Comment on lines +368 to +378
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 <details> 접힘/펼침 시각적 인디케이터 누락

기본 마커()를 display: nonelist-style: none으로 숨겼는데, 대체 시각적 인디케이터가 없어 사용자가 이 요소가 토글 가능하다는 것을 인지하기 어렵습니다.

summary::after pseudo-element로 chevron을 추가하는 것을 권장합니다:

Suggested change
.settings-custom-css summary {
padding: 12px 16px;
cursor: pointer;
background: var(--color-input-bg);
user-select: none;
list-style: none;
}
.settings-custom-css summary::-webkit-details-marker {
display: none;
}
.settings-custom-css summary {
padding: 12px 16px;
cursor: pointer;
background: var(--color-input-bg);
user-select: none;
list-style: none;
position: relative;
}
.settings-custom-css summary::after {
content: "▶";
position: absolute;
right: 16px;
top: 12px;
font-size: 10px;
transition: transform 0.2s;
}
.settings-custom-css[open] summary::after {
transform: rotate(90deg);
}
.settings-custom-css summary::-webkit-details-marker {
display: none;
}


.settings-custom-css summary p {
margin: 4px 0 0;
font-size: 12px;
color: var(--color-settings-item-text);
}

.settings-custom-css[open] summary {
border-bottom: 1px solid var(--color-settings-item-border);
}

.settings-custom-css-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}

.settings-custom-css-content textarea {
width: 100%;
padding: 12px;
border-radius: 8px;
border: 1px solid var(--color-input-border);
background: var(--color-input-bg);
color: var(--color-input-text);
font-family: monospace;
font-size: 12px;
resize: vertical;
}

.settings-apply-button {
height: 40px;
background: var(--color-action-bg);
color: var(--color-action-text);
border-radius: 8px;
font-weight: 600;
transition: opacity 0.2s;
border: none;
cursor: pointer;
}

.settings-apply-button:hover {
opacity: 0.9;
}

.settings-item .account-add-wrapper {
flex-shrink: 0;
}
Expand Down Expand Up @@ -775,6 +844,7 @@
border: none !important;
border-radius: 0 !important;
font-family: inherit;
font-size: 15px;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Nit: 폰트 사이즈 하드코딩

15px이 두 곳(L847, L929)에서 반복됩니다. 추후 변경 용이성을 위해 CSS 변수로 추출하는 것을 고려해보세요:

/* 예: theme variables에 추가 */
--font-size-compose: 15px;

단, 현재 코드베이스에서 사이즈 변수를 별도 관리하지 않는다면 현행대로 유지해도 무방합니다.

resize: vertical !important;
background: transparent !important;
min-height: 80px;
Expand Down Expand Up @@ -856,6 +926,7 @@
background: var(--color-input-bg);
color: var(--color-input-text);
font-family: inherit;
font-size: 15px;
}

.compose-actions {
Expand Down