diff --git a/.jules/palette.md b/.jules/palette.md index 0bbf5248..8ca79a09 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -115,3 +115,11 @@ ## $(date +%Y-%m-%d) - 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-14 - Replace native disabled with aria-disabled for form submit buttons +**Learning:** Native `disabled` attributes on submit buttons prevent keyboard navigation and click events, causing accessibility issues. Users tabbing through the page skip the element entirely, and the button cannot provide inline feedback when clicked. +**Action:** Use `aria-disabled="true"` instead of `disabled` for interactive buttons when the UI should preserve focusability or show inline feedback. Keep CSS in mind to style `[aria-disabled="true"]` buttons correctly (e.g., lower opacity, not-allowed cursor). Ensure form logic respects the `aria-disabled` attribute and shows appropriate feedback instead of blindly saving invalid state. + +## 2026-07-14 - Fix CI coverage pnpm runner missing packageManager constraint +**Learning:** For continuous integration pipelines verifying static analysis and evidence gates, `pnpm` will strictly enforce that a `packageManager` key (e.g. `"packageManager": "pnpm@10.30.3"`) is declared in `package.json` to avoid mutable toolchain dependencies. +**Action:** When working in repositories using `pnpm` under strict coverage/CI environments, include `"packageManager": "pnpm@"` in `package.json` to prevent pipeline failures related to package runner resolution. diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 17f338fe..8f1d588a 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -128,3 +128,7 @@ **Vulnerability:** The backend CSV export for audit logs neutralized `=`, `+`, `-`, and `@` but failed to neutralize `|` (pipe) characters, allowing potential DDE (Dynamic Data Exchange) injection if exported logs were opened in spreadsheet software. **Learning:** Spreadsheet formula defenses must cover all command-style prefixes including `|` across all CSV export boundaries, both frontend and backend. **Prevention:** Update the sanitization regex in the backend export function to `/^[=+\-@|]/` so that all potentially executable spreadsheet payloads are prefixed with a single quote. + +## 2026-07-14 - Replace dynamic RegExp with string methods for parsed inputs +**Learning:** `RegExp` objects built from untrusted or arbitrarily complex inputs (like XML/HTML tags) can be vulnerable to Regular Expression Denial of Service (ReDoS) if the input creates backtracking paths. Security scanners like Semgrep will flag dynamic instantiation of `RegExp` objects. +**Action:** When extracting simple patterns like tag blocks where the boundaries are deterministic, prefer string manipulation functions (`indexOf`, `substring`) over dynamic `RegExp` construction. This eliminates the ReDoS risk entirely and resolves SAST warnings. diff --git a/app.js b/app.js index b8c62279..a267a5a4 100644 --- a/app.js +++ b/app.js @@ -1047,7 +1047,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)'; } @@ -1219,6 +1223,7 @@ function saveEditor() { if (errors.length > 0) { state.editor.errors = errors; renderEditorValidation(); + showToast('입력값을 올바르게 수정해야 저장할 수 있습니다.'); return; } diff --git a/cloud-sync.js b/cloud-sync.js index 7e44932b..a9a8c577 100644 --- a/cloud-sync.js +++ b/cloud-sync.js @@ -740,8 +740,14 @@ function openReportModal() { // hand-edited files ever matter. export function parseMsProjectXml(xml) { const tag = (block, name) => { - const m = block.match(new RegExp(`<${name}>([^<]*)`)); - return m ? m[1].trim() : ''; + const openTag = `<${name}>`; + const closeTag = ``; + const start = block.indexOf(openTag); + if (start === -1) return ''; + const contentStart = start + openTag.length; + const end = block.indexOf(closeTag, contentStart); + if (end === -1) return ''; + return block.substring(contentStart, end).trim(); }; const unescape = (s) => s .replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') diff --git a/index.html b/index.html index a7f4b49c..cda50f78 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,8 @@ ScopeWeave Planner + + diff --git a/package.json b/package.json index 9ae8b292..7c120ccb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "type": "module", "description": "Production-grade pure HTML/CSS/JS WBS planner", + "packageManager": "pnpm@10.30.3", "scripts": { "check:python-docstrings": "node scripts/ci/static_coverage_evidence.mjs docstrings", "coverage": "node scripts/ci/static_coverage_evidence.mjs coverage && npm run test:fuzz",