Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@
## $(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-29 - Replace native disabled with aria-disabled for save button
**Learning:** Native \`disabled\` attributes prevent the element from receiving focus. In the inline editor, replacing it with \`aria-disabled="true"\` preserves focusability and allows intercepting clicks to provide helpful toast message feedback.
**Action:** When working on form submit buttons, use \`aria-disabled="true"\` and handle validation in JavaScript instead of relying on native \`disabled\` attribute.
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-29 - Prevent ReDoS in XML Parsing
**Learning:** Using \`new RegExp\` with dynamically constructed strings (even if simple variables) can trigger SAST alerts for Regular Expression Denial-of-Service (ReDoS), especially when used on parsed file content.
**Action:** Replace dynamic regex construction for simple tag parsing with native string methods like \`indexOf\` and \`substring\`. This resolves the Semgrep warning \`javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp\` and improves safety.
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 saveButton = form.querySelector('button[type="submit"]');
if (saveButton && saveButton.getAttribute('aria-disabled') === 'true') {
showToast(saveButton.title || 'μž…λ ₯값을 μ˜¬λ°”λ₯΄κ²Œ μˆ˜μ •ν•΄μ•Ό μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.');
return;
}

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
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 startTag = "<" + name + ">";
const endTag = "</" + name + ">";
const startIndex = block.indexOf(startTag);
if (startIndex === -1) return '';
const contentStart = startIndex + startTag.length;
const endIndex = block.indexOf(endTag, contentStart);
if (endIndex === -1) return '';
return block.substring(contentStart, endIndex).trim();
};
const unescape = (s) => s
.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"fuzz": "node --test tests/fuzz/*.mjs"
},
"dependencies": {
"@hono/node-server": "^1.19.14",
"@hono/node-server": "^1.19.17",
"hono": "^4.12.27"
},
"devDependencies": {
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions tests/e2e/scopeweave.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1333,4 +1333,29 @@ test.describe('ScopeWeave Planner - Palette UX Enhancements', () => {
await expect(backBtn).toHaveAttribute('title', 'μž‘μ—… λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ° (Esc)');
await expect(backBtn).toHaveAttribute('aria-keyshortcuts', 'Escape');
});

test('preserves save button focusability by using aria-disabled and shows feedback on click', async ({ page }) => {
await page.goto('./');

// Open editor
await page.getByRole('button', { name: 'μ΅œμƒμœ„ μž‘μ—… μΆ”κ°€' }).click();

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

const saveButton = page.locator('.editor-panel button[type="submit"]');

// Should use aria-disabled instead of native disabled
await expect(saveButton).toHaveAttribute('aria-disabled', 'true');
await expect(saveButton).not.toHaveAttribute('disabled', '');
Comment on lines +1348 to +1350

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target lines and nearby context.
sed -n '1328,1360p' tests/e2e/scopeweave.spec.js

# Find other disabled-related assertions in the file for consistency.
rg -n "toBeEnabled|toHaveAttribute\\('disabled'|aria-disabled|disabled" tests/e2e/scopeweave.spec.js

Repository: ContextualWisdomLab/scopeweave

Length of output: 2144


λ„€μ΄ν‹°λΈŒ disabledκΉŒμ§€ 막도둝 λ°”κΏ” μ£Όμ„Έμš”. not.toHaveAttribute('disabled', '')λŠ” disabled="disabled"처럼 λ‹€λ₯Έ 값이 뢙은 경우λ₯Ό 놓칠 수 μžˆμœΌλ‹ˆ, toBeEnabled() λ˜λŠ” 속성 자체 λΆ€μž¬λ₯Ό ν™•μΈν•˜λŠ” assertion으둜 λ°”κΎΈλŠ” 편이 μ•ˆμ „ν•©λ‹ˆλ‹€.

πŸ€– 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 `@tests/e2e/scopeweave.spec.js` around lines 1348 - 1350, Update the saveButton
assertions in the scopeweave test to verify the native disabled state is absent
regardless of its attribute value. Replace the exact-value disabled attribute
check with toBeEnabled() or an assertion that the disabled attribute itself is
not present, while preserving the aria-disabled="true" assertion.


// Should still be focusable
await saveButton.focus();
await expect(saveButton).toBeFocused();

// Should show toast feedback when clicked
await saveButton.click({ force: true });
await expect(page.locator('#toast')).toHaveText('μž…λ ₯값을 μ˜¬λ°”λ₯΄κ²Œ μˆ˜μ •ν•΄μ•Ό μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.');
await expect(page.locator('#toast')).toHaveClass(/show/);
});
});
Loading