@ihyeon/flow-qa 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md CHANGED
@@ -16,7 +16,7 @@ CLI와 `127.0.0.1` 전용 웹 UI를 제공합니다. UI는 영어로 시작하
16
16
  Node.js 24.8 이상이 필요합니다. 브라우저는 첫 웹 실행 전에 설치합니다.
17
17
 
18
18
  ```sh
19
- npm install -g @ihyeon/flow-qa@0.2.1
19
+ npm install -g @ihyeon/flow-qa@0.2.3
20
20
  flow-qa browser install
21
21
  flow-qa ui --demo
22
22
  ```
package/README.md CHANGED
@@ -16,7 +16,7 @@ Flow QA is available as a CLI and a web UI bound to `127.0.0.1`. Provide a targe
16
16
  Node.js 24.8 or later is required. Install Chromium before the first web run:
17
17
 
18
18
  ```sh
19
- npm install -g @ihyeon/flow-qa@0.2.1
19
+ npm install -g @ihyeon/flow-qa@0.2.3
20
20
  flow-qa browser install
21
21
  flow-qa ui --demo
22
22
  ```
@@ -61,11 +61,17 @@
61
61
  function targetLabel(target) { return target?.kind === 'mobile' ? `${target.platform === 'ios' ? 'iOS' : 'Android'} · ${target.bundleId} · ${target.device}` : target?.url || ''; }
62
62
  function targetDisplay(target) { return `${target?.name || t('app.noTarget')} · ${targetLabel(target)}`; }
63
63
  function compatibleTarget() { const target = selectedTarget(); return target && targetPlatform(target) === suitePlatform(); }
64
+ function canDownloadSavedCases() {
65
+ return Boolean(app.draft && app.script && app.savedFile && app.savedHash && !app.dirty && !app.stale && !app.busy && !hasUnsavedScenario());
66
+ }
67
+ function canDownloadSourceMarkdown() { return canDownloadSavedCases() && Boolean(app.draft.source); }
64
68
  function readiness() {
65
69
  const active = app.state?.active;
66
70
  const ready = !!app.draft && !!app.script && !app.stale && !app.dirty;
67
71
  $('run-btn').disabled = !app.connected || !ready || !app.savedFile || !app.savedHash || !$('review-checkbox').checked || !compatibleTarget() || !!active || app.busy;
68
72
  $('save-btn').disabled = !ready || app.busy;
73
+ $('suite-download').disabled = !canDownloadSavedCases();
74
+ $('flow-download').disabled = !canDownloadSourceMarkdown();
69
75
  $('script-download').disabled = !ready;
70
76
  for (const id of ['stop-btn', 'global-stop-btn']) {
71
77
  $(id).hidden = !active;
@@ -253,6 +259,19 @@
253
259
  }); }
254
260
  async function prompt() { await work(async () => { const flow = $('flow-input').value.trim(); if (!flow) throw localizedError('app.enterFlow'); const result = await api('/api/prompt', { flow, platform: $('scenario-platform').value }); download('flow-qa-prompt.md', result.prompt, 'text/markdown'); noticeKey('app.promptDownloaded'); }); }
255
261
  function editableFlow(flow) { return flow.replace(/\r\n?/g, '\n'); }
262
+ function hasUnsavedScenario() {
263
+ const flow = $('flow-input').value;
264
+ const suite = $('suite-editor').value;
265
+ if (!app.savedFile) return !!(flow.trim() || suite.trim());
266
+ return flow !== editableFlow(app.sourceFlow)
267
+ || suite !== JSON.stringify(app.draft, null, 2)
268
+ || $('scenario-platform').value !== suitePlatform();
269
+ }
270
+ function warnBeforeUnload(event) {
271
+ if (!hasUnsavedScenario()) return;
272
+ event.preventDefault();
273
+ event.returnValue = '';
274
+ }
256
275
  async function importFiles() {
257
276
  const files = [...$('import-json').files, ...$('import-markdown').files];
258
277
  let imported = false;
@@ -368,6 +387,7 @@
368
387
  else if (!$('notice').hidden) notice(''); // A server error has no stable code to translate; the next request uses the selected language.
369
388
  }
370
389
  function wire() {
390
+ window.addEventListener('beforeunload', warnBeforeUnload);
371
391
  document.querySelectorAll('[data-view]').forEach((button) => button.addEventListener('click', () => view(button.dataset.view)));
372
392
  $('nav-toggle').addEventListener('click', () => { const open = $('sidebar').classList.toggle('nav-open'); $('nav-toggle').setAttribute('aria-expanded', String(open)); });
373
393
  $('library-shortcut').addEventListener('click', () => view('library'));
@@ -380,7 +400,7 @@
380
400
  noticeKey('app.newDraftNotice');
381
401
  $('suite-editor').focus();
382
402
  });
383
- $('flow-input').addEventListener('input', () => { if (app.origin === 'imported' ? $('flow-input').value !== editableFlow(app.sourceFlow) : (app.sourceFlow || app.boundSource) && $('flow-input').value.trim() !== editableFlow(app.sourceFlow).trim()) { app.stale = true; app.savedHash = ''; $('review-checkbox').checked = false; readiness(); } });
403
+ $('flow-input').addEventListener('input', () => { if (app.origin === 'imported' ? $('flow-input').value !== editableFlow(app.sourceFlow) : (app.sourceFlow || app.boundSource) && $('flow-input').value.trim() !== editableFlow(app.sourceFlow).trim()) { app.stale = true; app.savedHash = ''; $('review-checkbox').checked = false; } readiness(); });
384
404
  $('scenario-platform').addEventListener('change', () => { app.planRequest += 1; if (app.draft) { app.stale = true; app.savedHash = ''; } $('review-checkbox').checked = false; readiness(); });
385
405
  $('suite-editor').addEventListener('input', () => { app.dirty = true; app.script = ''; app.savedFile = ''; app.savedHash = ''; if (app.origin === 'none') app.origin = 'manual'; $('review-checkbox').checked = false; summarize(null); readiness(); });
386
406
  $('review-checkbox').addEventListener('change', readiness); $('target-select').addEventListener('change', () => { $('review-checkbox').checked = false; targetDescription(); });
@@ -388,6 +408,8 @@
388
408
  $('import-btn').addEventListener('click', importFiles);
389
409
  $('global-stop-btn').addEventListener('click', stop);
390
410
  $('reconnect-btn').addEventListener('click', () => refresh(true).catch(showError));
411
+ $('suite-download').addEventListener('click', () => { if (canDownloadSavedCases()) download(app.savedFile, `${JSON.stringify(app.draft, null, 2)}\n`, 'application/json'); });
412
+ $('flow-download').addEventListener('click', () => { if (canDownloadSourceMarkdown()) download(app.draft.source.file.split('/').pop(), app.sourceFlow, 'text/markdown'); });
391
413
  $('script-download').addEventListener('click', () => { if (app.draft && !app.dirty && !app.stale) download(`${app.draft.id || 'flow-qa'}.test.mjs`, app.script, 'text/javascript'); });
392
414
  $('report-md').addEventListener('click', downloadReportMarkdown);
393
415
  $('report-json').addEventListener('click', () => { if (app.report) download(`${app.selectedRun}.json`, JSON.stringify(app.report, null, 2), 'application/json'); });
@@ -45,7 +45,9 @@
45
45
  'scenario.sourceNone': ['Source: none', '원본: 없음'],
46
46
  'scenario.fromLibrary': ['Load from library', '보관함에서 불러오기'],
47
47
  'scenario.save': ['Save cases separately', '케이스 별도 저장'],
48
- 'scenario.saveHint': ['Only saved cases can be run.', '저장된 케이스만 실행할 수 있습니다.'],
48
+ 'scenario.downloadSuite': ['Download case JSON', '케이스 JSON 다운로드'],
49
+ 'scenario.downloadFlow': ['Download source Markdown', '원본 Markdown 다운로드'],
50
+ 'scenario.saveHint': ['Saved cases can be run or downloaded.', '저장된 케이스는 실행하거나 다운로드할 수 있습니다.'],
49
51
  'scenario.technical': ['Edit JSON · Validate · View script', 'JSON 편집 · 검증 · 스크립트 보기'],
50
52
  'scenario.suiteJson': ['Suite JSON', '스위트 JSON'],
51
53
  'scenario.suitePlaceholder': ['Load a local demo case from the library or enter suite JSON.', '보관함의 로컬 데모 케이스를 불러오거나 스위트 JSON을 입력하세요.'],
@@ -64,7 +64,7 @@
64
64
  <div class="draft-actions import-actions"><label for="import-json"><span data-i18n="scenario.importJson">TC JSON</span><input id="import-json" type="file" accept=".json"></label><label for="import-markdown"><span data-i18n="scenario.importMarkdown">Source Markdown (optional)</span><input id="import-markdown" type="file" accept=".md"></label><button id="import-btn" type="button" class="button quiet" data-i18n="scenario.importFiles">Import case files</button></div>
65
65
  <p class="hint" data-i18n="scenario.importHint">Select one TC JSON and, if needed, its source Markdown separately. Import creates a copy to save and review.</p>
66
66
  <div id="draft-summary" class="summary" aria-live="polite"><p class="summary-empty" data-i18n="scenario.summaryEmpty">No cases to review yet. Load a local example from the library or generate cases from the scenario.</p></div>
67
- <div class="draft-actions"><button id="save-btn" type="button" class="button" data-i18n="scenario.save">Save cases separately</button><span data-i18n="scenario.saveHint">Only saved cases can be run.</span></div>
67
+ <div class="draft-actions"><button id="save-btn" type="button" class="button" data-i18n="scenario.save">Save cases separately</button><button id="suite-download" type="button" class="button quiet" disabled data-i18n="scenario.downloadSuite">Download case JSON</button><button id="flow-download" type="button" class="button quiet" disabled data-i18n="scenario.downloadFlow">Download source Markdown</button><span data-i18n="scenario.saveHint">Saved cases can be run or downloaded.</span></div>
68
68
  <details id="technical-details" class="technical-details">
69
69
  <summary><span data-i18n="scenario.technical">Edit JSON · Validate · View script</span><span aria-hidden="true">+</span></summary>
70
70
  <div class="technical-body">
package/docs/mobile.md CHANGED
@@ -9,7 +9,7 @@ Choose **Mobile** on the scenario screen, describe the flow, and generate or dow
9
9
  With the installed CLI:
10
10
 
11
11
  ```sh
12
- npm install -g @ihyeon/flow-qa@0.2.1
12
+ npm install -g @ihyeon/flow-qa@0.2.3
13
13
  flow-qa init ./my-qa
14
14
  # Write your mobile flow in my-qa/flows/my-flow.md first.
15
15
  flow-qa plan flows/my-flow.md --platform mobile --prompt-only --workspace ./my-qa
package/docs/releases.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  The alpha entries below are historical release records. They document earlier package versions and checks.
4
4
 
5
+ ## 0.2.3: download saved case files
6
+
7
+ Adds separate case JSON and source Markdown download buttons to case review. Saved cases can be reused without finding workspace files manually; source-less cases offer JSON only. Unsaved or outdated drafts are excluded. The source text and hash remain compatible with file import, while existing save, review, and execution gates stay in place.
8
+
9
+ Locally verified on macOS with Node.js 24.14.1 on 2026-10-20: 92 tests, type checking, UI syntax and design-system audit, real Chromium downloads and re-import with unchanged source hashes, JSON-only downloads for source-less cases, disabled controls for unsaved edits, and a fresh package install with three passing browser cases. Existing demo pass/failure/blocked classifications and English/Korean UI checks passed without model calls.
10
+
11
+ - Package: [`@ihyeon/flow-qa@0.2.3`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.2.3)
12
+ - Release: [GitHub 0.2.3](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.2.3)
13
+
14
+ ## 0.2.2: warn before leaving an unsaved draft
15
+
16
+ Requests the browser's confirmation before a refresh or tab close would discard an unsaved scenario or TC draft. Imported and manually edited cases are covered, including scenario edits to a saved case without a linked source. Unchanged saved cases stay quiet, and successful saving clears the warning. The browser controls the prompt; this does not add automatic saving or change review and execution gates.
17
+
18
+ Locally verified on macOS with Node.js 24.14.1 on 2026-10-20: 92 tests, type checking, UI syntax and design-system audit, a real Chromium reload prompt that preserves input when dismissed, clean/saved/imported/edited draft checks, existing browser and UI smoke tests, and a fresh tarball install with three passing browser cases. No model calls were used.
19
+
20
+ - Package: [`@ihyeon/flow-qa@0.2.2`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.2.2)
21
+ - Release: [GitHub 0.2.2](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.2.2)
22
+
5
23
  ## 0.2.1: import case files from the UI
6
24
 
7
25
  Adds a file picker for a TC JSON file and its optional source Markdown. Import checks the suite and source hash before replacing the current draft, preserves unresolved review questions, and creates a separate copy when saved. Source text retains its line endings and trailing whitespace. This does not import error-to-test provenance metadata; original bundles remain separate. See [case file import](ui.md#import-existing-case-files).
package/docs/ui.md CHANGED
@@ -7,7 +7,7 @@ Describe a user flow, review cases, and configure a target when ready to run. We
7
7
  Node.js 24.8 or later is required. Install Flow QA and start the web demo:
8
8
 
9
9
  ```sh
10
- npm install -g @ihyeon/flow-qa@0.2.1
10
+ npm install -g @ihyeon/flow-qa@0.2.3
11
11
  flow-qa browser install
12
12
  flow-qa ui --demo
13
13
  ```
@@ -42,6 +42,8 @@ For each attempt, the report can open or download an approved screenshot or text
42
42
 
43
43
  To enter fresh JSON after changing a scenario, open the JSON editor in case review and choose **New JSON draft**. Existing saved files remain; validate and save the new JSON before reviewing it for a run.
44
44
 
45
+ From version `0.2.2`, the UI requests the browser's leave-page confirmation when refreshing or closing a tab with an unsaved scenario or case draft. Unchanged saved cases do not trigger it; saving clears the warning. Choose to stay on the page to keep editing. The browser controls the prompt wording and availability. This is not automatic saving, and it does not guard actions within the UI that replace the draft.
46
+
45
47
  ### Import existing case files
46
48
 
47
49
  From version `0.2.1`, select a `flow-qa/suite-1` JSON file and, when it declares a source, its Markdown file in case review, then choose **Import case files**. The two file controls let you select files from different folders. For an error-to-test export, select `cases/error-to-test.json` and `flows/error-to-test.md`. A JSON file without a source can be imported alone or with a Markdown file.
@@ -50,6 +52,12 @@ Import validates the suite and checks the selected Markdown against its declared
50
52
 
51
53
  This creates an unsaved review copy. Choose **Save cases separately**, review the cases, and confirm review before running. Unresolved questions remain blocked. Saving assigns a new workspace source path and hash; it does not overwrite the input files or import `provenance.json`. If you need error-to-test `verify` / `link-run` provenance, keep the original bundle and use its original suite instead of this saved copy.
52
54
 
55
+ ### Download saved cases
56
+
57
+ From version `0.2.3`, case review offers **Download case JSON** and **Download source Markdown** for the saved case currently shown. Save or reload the case first; unsaved or outdated drafts cannot be downloaded with these controls. A case without a linked source offers JSON only. Downloading needs no run target or execution review confirmation and does not change either.
58
+
59
+ The JSON keeps the case content and source reference. The Markdown retains its exact text, line endings, and trailing whitespace so its SHA-256 still matches. Select both downloaded files through **Import case files** to reuse them in another workspace, then save and review the new copy before running. These downloads contain the case and source, not run reports or an error-to-test provenance bundle.
60
+
53
61
  ## Display and language
54
62
 
55
63
  The UI uses role tokens and preference utilities from `@hyeonse/design-system`, pinned to GitHub commit `38978c6a86f422fc0e63e919f600bc2dd3451e5c`. Flow QA implements its own workflow layout, buttons, and fields with those tokens.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ihyeon/flow-qa",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "license": "MIT",
5
5
  "author": "Hyeonse Im",
6
6
  "repository": {