Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
8 changes: 6 additions & 2 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,14 @@
**Learning:** [When an element is removed from the DOM, focus naturally resets to the document body, breaking the keyboard navigation flow. It is critical to calculate the next logical focus target prior to deletion and programmatically restore focus post-render.]
**Action:** [In future components involving item deletion within lists or tables, proactively incorporate index calculations before removing items to manage focus restoration correctly.]

## $(date +%Y-%m-%d) - Add Confirmation Dialog for CSV Import
## 2024-05-31 - Add Confirmation Dialog for CSV Import
**Learning:** File import actions that completely overwrite existing application state can lead to severe data loss if triggered accidentally. In a WBS planner where users invest significant time building task hierarchies, destructive imports need explicit user confirmation.
**Action:** Always add a confirmation dialog (`window.confirm` or custom modal) for any import or sync action that wipes out the current in-memory or persisted state, especially when there's no undo mechanism.

## $(date +%Y-%m-%d) - Prevent accidental data loss in inline editors
## 2024-05-31 - Prevent accidental data loss in inline editors
**Learning:** Forms that take a long time to fill out (like a WBS editor) are prone to accidental closure by users pressing `Escape` or clicking cancel. This causes immediate data loss without any warning, resulting in frustration.
**Action:** When working on editors that can be dismissed, track whether the user has modified any fields compared to their initial state. If there are changes, intercept the close action and present a confirmation dialog (`window.confirm`) to ensure they really want to discard their edits. Bypass this for intentional saves or explicit data overrides.

## 2024-05-31 - 모달 닫기 버튼에 키보드 단축키 힌트 추가
**Learning:** `title` 속성으로만 제공되는 키보드 단축키 힌트는 화면 판독기나 키보드 사용자에게 일관성 있게 전달되지 않는다.
**Action:** `title` 툴팁과 함께 `aria-keyshortcuts` 속성을 사용하여, 마우스와 보조 기술 사용자 모두가 키보드 단축키(예: Escape)를 인지할 수 있도록 구현한다.
20 changes: 18 additions & 2 deletions cloud-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function ensureAuthUI() {
<div class="modal-panel cloud-panel">
<div class="modal-header">
<h2 id="cloud-modal-title">클라우드 로그인</h2>
<button type="button" class="icon-button close-button" data-cloud-close="true" aria-label="닫기"><span aria-hidden="true">✕</span></button>
<button type="button" class="icon-button close-button" data-cloud-close="true" aria-label="닫기" title="닫기 (Esc)" aria-keyshortcuts="Escape"><span aria-hidden="true">✕</span></button>
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
</div>
<form id="cloud-form" class="cloud-form">
<label class="meta-field"><span>이메일</span><input id="cloud-email" type="email" autocomplete="username" required /></label>
Expand Down Expand Up @@ -543,6 +543,8 @@ async function openShareModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '공유 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -685,6 +687,8 @@ function openReportModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '주간보고 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -875,6 +879,8 @@ async function openPortfolioModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '대시보드 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -1049,6 +1055,8 @@ async function openSprintModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '스프린트 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -1191,6 +1199,8 @@ async function openAttachmentsModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '산출물 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -1328,6 +1338,8 @@ async function openCommentsModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '코멘트 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -1440,6 +1452,8 @@ function openSearchModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '검색 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -1554,6 +1568,8 @@ async function openBaselineModal() {
close.type = 'button';
close.className = 'icon-button close-button';
close.setAttribute('aria-label', '기준선 닫기');
close.setAttribute('title', '닫기 (Esc)');
close.setAttribute('aria-keyshortcuts', 'Escape');
close.textContent = '✕';
close.addEventListener('click', () => modal.classList.add('hidden'));
head.append(h2, close);
Expand Down Expand Up @@ -1733,7 +1749,7 @@ async function openTeamModal() {
<div class="modal-panel cloud-panel">
<div class="modal-header">
<h2>팀 멤버</h2>
<button type="button" class="icon-button close-button" data-team-close="true" aria-label="닫기"><span aria-hidden="true">✕</span></button>
<button type="button" class="icon-button close-button" data-team-close="true" aria-label="닫기" title="닫기 (Esc)" aria-keyshortcuts="Escape"><span aria-hidden="true">✕</span></button>
</div>
<div id="team-body" class="team-body"></div>
<form id="team-invite" class="team-invite">
Expand Down
2 changes: 2 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'none'; form-action 'self';" />
<title>ScopeWeave Planner</title>
<link rel="preload" href="styles.css" as="style" />
<link rel="modulepreload" href="cloud-sync.js" />
<link rel="modulepreload" href="analytics.js" />
<link rel="modulepreload" href="app.js" />
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="toast-state.css" />
Expand Down
Loading