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: 2 additions & 2 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
cache: 'npm'

- name: Install dependencies
run: npm ci
run: pnpm install

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== workflow files ==\n'
git ls-files '.github/workflows/*.yml'

printf '\n== fuzz.yml outline ==\n'
ast-grep outline .github/workflows/fuzz.yml --view expanded || true

printf '\n== server-tests.yml outline ==\n'
ast-grep outline .github/workflows/server-tests.yml --view expanded || true

printf '\n== relevant snippets ==\n'
nl -ba .github/workflows/fuzz.yml | sed -n '1,220p'
printf '\n--- server-tests.yml ---\n'
nl -ba .github/workflows/server-tests.yml | sed -n '1,220p'

printf '\n== lockfiles ==\n'
git ls-files 'pnpm-lock.yaml' 'package-lock.json' 'yarn.lock' || true

Repository: ContextualWisdomLab/scopeweave

Length of output: 594


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== pnpm bootstrap references ==\n'
rg -n --glob '.github/workflows/*.yml' 'pnpm/action-setup|corepack enable|cache:\s*pnpm|cache:\s*npm|frozen-lockfile|--frozen-lockfile|packageManager' .github/workflows || true

printf '\n== package manager metadata ==\n'
rg -n --glob 'package.json' '"packageManager"|"pnpm"' . || true

Repository: ContextualWisdomLab/scopeweave

Length of output: 285


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== fuzz.yml =='
sed -n '1,220p' .github/workflows/fuzz.yml | cat -n

echo
echo '== server-tests.yml =='
sed -n '1,260p' .github/workflows/server-tests.yml | cat -n

echo
echo '== package.json =='
sed -n '1,120p' package.json | cat -n

Repository: ContextualWisdomLab/scopeweave

Length of output: 6517


🌐 Web query:

actions/setup-node pnpm packageManager corepack pnpm install without pnpm/action-setup GitHub Actions

πŸ’‘ Result:

You can use pnpm in GitHub Actions without the pnpm/action-setup action by utilizing Corepack, which is bundled with modern Node.js distributions [1]. To do this, follow these steps in your workflow: 1. Setup Node.js using actions/setup-node [2]. 2. Enable Corepack within your workflow steps. 3. Run pnpm install. Example configuration: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - name: Enable Corepack run: corepack enable - name: Install dependencies run: pnpm install Key Considerations: - Corepack Versioning: It is recommended to update Corepack to the latest version before enabling it, as the version bundled with older Node.js releases may contain bugs or outdated signatures [3][1]. You can do this by running npm install --global corepack@latest before corepack enable [1]. - Caching: The actions/setup-node action provides a cache: 'pnpm' input [2]. While this is the official way to cache dependencies, it expects pnpm to be present in the environment. When using Corepack, you must ensure it is enabled before the actions/setup-node step or configure your workflow to handle the cache manually using actions/cache if needed, as setup-node might attempt to resolve the pnpm executable before it is available [2][4]. - Modern Alternative: The pnpm team has released a new action, pnpm/setup [5], which is designed to replace actions/setup-node for pnpm workflows by installing both the package manager and the required Node.js runtime (via devEngines.runtime) in one step, effectively making traditional setup actions unnecessary for many use cases [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== lockfiles =='
git ls-files 'pnpm-lock.yaml' 'package-lock.json' 'yarn.lock' || true

echo
echo '== setup-node cache usage =='
rg -n --glob '.github/workflows/*.yml' 'cache:\s*pnpm|cache:\s*npm|cache-dependency-path|corepack enable|pnpm/action-setup' .github/workflows || true

Repository: ContextualWisdomLab/scopeweave

Length of output: 247


pnpm을 λͺ…μ‹œμ μœΌλ‘œ μ΄ˆκΈ°ν™”ν•˜μ„Έμš”. package.json은 packageManager: "pnpm@10.30.3"λ₯Ό μ„ μ–Έν•˜μ§€λ§Œ, fuzz.ymlκ³Ό server-tests.yml 어디에도 pnpm bootstrap이 μ—†μ–΄ pnpm install이 runner μƒνƒœμ— μ˜μ‘΄ν•©λ‹ˆλ‹€.

  • .github/workflows/fuzz.yml:40-47 β€” cache: npm을 pnpm용으둜 λ§žμΆ”κ³  pnpm install --frozen-lockfile둜 κ³ μ •ν•˜μ„Έμš”.
  • .github/workflows/server-tests.yml:28-33,48-53 β€” 두 job λͺ¨λ‘ 같은 pnpm μ΄ˆκΈ°ν™”μ™€ frozen install이 ν•„μš”ν•©λ‹ˆλ‹€.
πŸ“ Affects 2 files
  • .github/workflows/fuzz.yml#L47-L47 (this comment)
  • .github/workflows/server-tests.yml#L33-L33
  • .github/workflows/server-tests.yml#L53-L53
πŸ€– 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 @.github/workflows/fuzz.yml at line 47, Explicitly bootstrap pnpm using the
repository’s declared package manager version before installation, replace npm
caching with pnpm-compatible caching, and use a frozen-lockfile install. Apply
this to the install flow in .github/workflows/fuzz.yml:40-47 and both jobs’
setup/install flows in .github/workflows/server-tests.yml:28-33 and :48-53.


- name: Select iteration budget
id: budget
Expand All @@ -63,4 +63,4 @@ jobs:
# Bounded per-property iteration count; the 10-minute job timeout is
# the hard ceiling. PRs stay short (~60-120s total), nightly digs deeper.
FUZZ_RUNS: ${{ steps.budget.outputs.runs }}
run: npm run fuzz
run: pnpm run fuzz
10 changes: 5 additions & 5 deletions .github/workflows/server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ jobs:
with:
node-version: 22
- name: Install
run: npm ci
run: pnpm install
- name: Unit tests (EVM Β· CPM Β· baseline Β· workload)
run: npm run test:unit
run: pnpm run test:unit
- name: API tests (auth Β· tenancy Β· RBAC Β· billing Β· webhooks Β· rate limit)
run: npm run test:api
run: pnpm run test:api
- name: app.js stays eval-safe (no top-level import/export)
run: node -e "new Function(require('fs').readFileSync('app.js','utf8')); console.log('eval-safe OK')"

Expand All @@ -50,8 +50,8 @@ jobs:
with:
node-version: 22
- name: Install
run: npm ci
run: pnpm install
- name: Install Playwright (chromium)
run: npx playwright install chromium --with-deps
- name: Cloud UI e2e
run: npm run test:e2e:cloud
run: pnpm run test:e2e:cloud
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.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
## Verification

- Serve locally with `python3 -m http.server 4173`.
- Run end-to-end verification with `npm run test:e2e`.
- Run end-to-end verification with `pnpm run test:e2e`.
- Run workflow ownership checks with `python3 -m pytest tests/config`.

## CI / security workflow notes
Expand Down
12 changes: 6 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ python3 -m http.server 4173 # open http://127.0.0.1:4173

# Cloud server (Node >= 22 β€” uses node:sqlite)
npm install
npm run server # API + static client on :8787
pnpm run server # API + static client on :8787

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟑 Minor | ⚑ Quick win

λ¬Έμ„œμ˜ μ„€μΉ˜ λͺ…령도 pnpm κΈ°μ€€μœΌλ‘œ 톡일해 μ£Όμ„Έμš”.

μ‹€ν–‰ λͺ…λ Ήλ§Œ pnpm으둜 λ³€κ²½λ˜κ³  각 Cloud 개발 μ•ˆλ‚΄μ˜ μ„€μΉ˜ λͺ…령은 npm install으둜 남아 μžˆμ–΄, λ¬Έμ„œ μ‚¬μš©μžκ°€ 잘λͺ»λœ νŒ¨ν‚€μ§€ κ΄€λ¦¬μžμ™€ lockfile을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • CLAUDE.md#L29-L29: μ•žμ„  μ„€μΉ˜ λͺ…령을 pnpm install둜 λ³€κ²½.
  • README.md#L86-L86: μ•žμ„  μ„€μΉ˜ λͺ…령을 pnpm install둜 λ³€κ²½.
πŸ“ Affects 2 files
  • CLAUDE.md#L29-L29 (this comment)
  • README.md#L86-L86
πŸ€– 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 `@CLAUDE.md` at line 29, ν†΅μΌλœ νŒ¨ν‚€μ§€ κ΄€λ¦¬μž μ•ˆλ‚΄λ₯Ό μœ„ν•΄ CLAUDE.md의 29ν–‰κ³Ό README.md의 86ν–‰μ—μ„œ 각각
μ•žμ„  npm install λͺ…령을 pnpm install둜 λ³€κ²½ν•˜μ„Έμš”.


# Tests
npm run test:unit # pure-math: EVM/S-curve, CPM, baselines, workload, …
npm run test:api # API smoke (authΒ·tenancyΒ·RBACΒ·billingΒ·webhooks) + rate limit
npm run test:e2e # Playwright UI suite (auto-starts http.server on :4173)
npm run test:e2e:cloud # cloud UI spec only
npm run fuzz # fast-check property fuzz (node --test tests/fuzz/*.mjs)
pnpm run test:unit # pure-math: EVM/S-curve, CPM, baselines, workload, …
pnpm run test:api # API smoke (authΒ·tenancyΒ·RBACΒ·billingΒ·webhooks) + rate limit
pnpm run test:e2e # Playwright UI suite (auto-starts http.server on :4173)
pnpm run test:e2e:cloud # cloud UI spec only
pnpm run fuzz # fast-check property fuzz (node --test tests/fuzz/*.mjs)
python3 -m pytest tests/config # workflow-ownership / governance checks

# Single test: unit tests are plain Node scripts
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Cloud (Node β‰₯ 22):

```bash
npm install
npm run server # serves the API + the static client on :8787
pnpm run server # serves the API + the static client on :8787
```

Docker: `docker compose up` (see `Dockerfile.server` / `docs/deploy.md`).
Expand All @@ -103,9 +103,9 @@ Docker: `docker compose up` (see `Dockerfile.server` / `docs/deploy.md`).
## Verification

```bash
npm run test:api # API smoke (auth, tenancy, RBAC, billing, webhooks, …) + rate limit
npm run test:unit # EVM/S-curve, CPM, baseline-compare (pure math)
npm run test:e2e # Playwright UI suite
pnpm run test:api # API smoke (auth, tenancy, RBAC, billing, webhooks, …) + rate limit
pnpm run test:unit # EVM/S-curve, CPM, baseline-compare (pure math)
pnpm run test:e2e # Playwright UI suite
python3 -m pytest tests/config
```

Expand Down
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
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 startIdx = block.indexOf(startTag);
if (startIdx === -1) return '';
const valStart = startIdx + startTag.length;
const endIdx = block.indexOf(endTag, valStart);
if (endIdx === -1) return '';
return block.substring(valStart, endIdx).trim();
};
const unescape = (s) => s
.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"')
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"description": "Production-grade pure HTML/CSS/JS WBS planner",
"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",
"coverage": "node scripts/ci/static_coverage_evidence.mjs coverage && pnpm run test:fuzz",
"server": "node server/server.mjs",
"test:api": "node tests/api/smoke.mjs && node tests/api/ratelimit.test.mjs",
"test:unit": "node tests/unit/analytics.test.mjs && node tests/unit/cpm.test.mjs && node tests/unit/baseline-compare.test.mjs && node tests/unit/workload.test.mjs && node tests/unit/cost-evm.test.mjs && node tests/unit/msproject.test.mjs && node tests/unit/dep-types.test.mjs && node tests/unit/weekly-report.test.mjs && node tests/unit/clearfolio.test.mjs && node tests/unit/sprint-stats.test.mjs && node tests/unit/burndown.test.mjs && node tests/unit/pm-analysis.test.mjs && node tests/unit/cloud-sync-security.test.mjs",
Expand All @@ -17,11 +17,12 @@
"fuzz": "node --test tests/fuzz/*.mjs"
},
"dependencies": {
"@hono/node-server": "^1.19.14",
"hono": "^4.12.27"
"@hono/node-server": "^2.0.12",
"hono": "^4.12.32"
},
"devDependencies": {
"@playwright/test": "1.61.1",
"fast-check": "4.9.0"
}
},
"packageManager": "pnpm@10.30.3"
}
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

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

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