Skip to content
Closed
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
## 2026-07-28 - 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
## 2026-07-28 - 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.

## 2026-07-28 - Replace disabled with aria-disabled on submit buttons
**Learning:** Native `disabled` attributes on submit buttons prevent focus and block clicks for keyboard and screen-reader users, making the reason for the disabled state inaccessible. However, replacing it with `aria-disabled="true"` will cause the button to trigger native form submission.
**Action:** When replacing `disabled` with `aria-disabled="true"` on a submit button, ensure the form's `submit` event listener explicitly checks for the `aria-disabled` attribute and calls `event.preventDefault()` to block unintended execution.
13 changes: 12 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ function bindTableEvents(renderDraftValidation, updateEditorDraftFromEvent) {
return;
}
event.preventDefault();

const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn && submitBtn.getAttribute('aria-disabled') === 'true') {
showToast(submitBtn.title || 'ν˜„μž¬ μ‚¬μš©ν•  수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.');
return;
}

Comment on lines +412 to +417

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ—„οΈ Data Integrity & Integration | 🟠 Major | ⚑ Quick win

검증을 κ°±μ‹ ν•œ λ’€ aria-disabledλ₯Ό 검사해야 ν•©λ‹ˆλ‹€.

μž…λ ₯ 직후 debounceκ°€ λλ‚˜κΈ° 전에 μ œμΆœν•˜λ©΄ 이 μ‹œμ μ—λŠ” aria-disabledκ°€ 아직 제거된 μƒνƒœμΌ 수 μžˆμŠ΅λ‹ˆλ‹€. 이후 renderDraftValidation.flush()κ°€ 였λ₯˜λ₯Ό 섀정해도 saveEditor()κ°€ 이미 ν˜ΈμΆœλ˜μ–΄ 잘λͺ»λœ 데이터가 μ €μž₯λ©λ‹ˆλ‹€. flush()λ₯Ό ν˜„μž¬ guard보닀 μ•žμ—μ„œ ν˜ΈμΆœν•˜μ„Έμš”. ν˜„μž¬ E2E ν…ŒμŠ€νŠΈλ„ 속성 갱신을 κΈ°λ‹€λ¦° λ’€ ν΄λ¦­ν•˜λ―€λ‘œ 이 경합을 κ²€μΆœν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€.

μˆ˜μ • μ˜ˆμ‹œ
    event.preventDefault();
+   renderDraftValidation.flush();

    const submitBtn = form.querySelector('button[type="submit"]');
    if (submitBtn && submitBtn.getAttribute('aria-disabled') === 'true') {
      showToast(submitBtn.title || 'ν˜„μž¬ μ‚¬μš©ν•  수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.');
      return;
    }

-   renderDraftValidation.flush();
    saveEditor();
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn && submitBtn.getAttribute('aria-disabled') === 'true') {
showToast(submitBtn.title || 'ν˜„μž¬ μ‚¬μš©ν•  수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.');
return;
}
renderDraftValidation.flush();
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn && submitBtn.getAttribute('aria-disabled') === 'true') {
showToast(submitBtn.title || 'ν˜„μž¬ μ‚¬μš©ν•  수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.');
return;
}
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 412 - 417, Move the renderDraftValidation.flush() call
before the submitBtn aria-disabled guard in the form submission handler, so
validation state is updated before checking whether submission is allowed. Keep
the existing showToast-and-return behavior for aria-disabled="true", and only
invoke saveEditor() after the refreshed validation state permits submission.

renderDraftValidation.flush();
saveEditor();
});
Expand Down Expand Up @@ -1047,7 +1054,11 @@ function renderEditorValidation() {

const saveButton = form.querySelector('button[type="submit"]');
if (saveButton) {
saveButton.disabled = errors.length > 0;
if (errors.length > 0) {
saveButton.setAttribute('aria-disabled', 'true');
} else {
saveButton.removeAttribute('aria-disabled');
}
saveButton.title = errors.length > 0 ? 'μž…λ ₯값을 μ˜¬λ°”λ₯΄κ²Œ μˆ˜μ •ν•΄μ•Ό μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.' : 'μ €μž₯ (Enter)';
}

Expand Down
5 changes: 5 additions & 0 deletions patch_spec.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const fs = require('fs');
let content = fs.readFileSync('tests/e2e/scopeweave.spec.js', 'utf8');
content = content.replace('});\n});\n\n test(\'ScopeWeave Planner - Palette UX Enhancements - blocks form submission when save button is aria-disabled\'', ' test(\'ScopeWeave Planner - Palette UX Enhancements - blocks form submission when save button is aria-disabled\'');
content += '});\n';
fs.writeFileSync('tests/e2e/scopeweave.spec.js', content);
Comment thread
github-actions[bot] marked this conversation as resolved.
Outdated
49 changes: 49 additions & 0 deletions tests/e2e/scopeweave.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1333,4 +1333,53 @@ test.describe('ScopeWeave Planner - Palette UX Enhancements', () => {
await expect(backBtn).toHaveAttribute('title', 'μž‘μ—… λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ° (Esc)');
await expect(backBtn).toHaveAttribute('aria-keyshortcuts', 'Escape');
});
test('ScopeWeave Planner - Palette UX Enhancements - blocks form submission when save button is aria-disabled', async ({ page }) => {
// Inject seed data to edit
await page.evaluate(() => {
localStorage.setItem('scopeweave:planner-state:v1', JSON.stringify({
projectName: 'ScopeWeave Planner',
baseDate: '2026-07-10',
tasks: [{
id: 'task-1',
phase: 'P1',
depth: 1,
expanded: true
}]
}));
});
await page.reload();

// Open editor for the task
await page.locator('tr[data-task-id="task-1"] [data-action="edit"]').click();

// Clear the required phase field to trigger validation errors
const phaseInput = page.locator('[data-testid="editor-phase"]');
await phaseInput.fill('');

// Wait for the save button to become aria-disabled
const saveBtn = page.locator('.editor-actions button[type="submit"]');
await expect(saveBtn).toHaveAttribute('aria-disabled', 'true');

// Ensure form is visible
const form = page.locator('form[data-editor-form="true"]');
await expect(form).toBeVisible();

// Click the submit button
await saveBtn.click();

// Check that toast shows the error message
const toast = page.locator('#toast');
await expect(toast).toContainText('μž…λ ₯값을 μ˜¬λ°”λ₯΄κ²Œ μˆ˜μ •ν•΄μ•Ό μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.');

// Form should still be visible because submission was prevented
await expect(form).toBeVisible();

// Fill it back and save to ensure normal submission works
await phaseInput.fill('Valid Phase');
await expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true');
await saveBtn.click();

// Editor should close
await expect(form).not.toBeVisible();
});
});
Loading