Skip to content
Closed
8 changes: 8 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -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@<version>"` in `package.json` to prevent pipeline failures related to package runner resolution.
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 6 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)';
}

Expand Down Expand Up @@ -1219,6 +1223,7 @@ function saveEditor() {
if (errors.length > 0) {
state.editor.errors = errors;
renderEditorValidation();
showToast('์ž…๋ ฅ๊ฐ’์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜์ •ํ•ด์•ผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.');
return;
}

Expand Down
10 changes: 8 additions & 2 deletions cloud-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}>([^<]*)</${name}>`));
return m ? m[1].trim() : '';
const openTag = `<${name}>`;
const closeTag = `</${name}>`;
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(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"')
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" />
</head>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading