@ihyeon/flow-qa 0.2.2 → 0.2.4
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 +1 -1
- package/README.md +1 -1
- package/dist/src/ui/public/app.js +17 -2
- package/dist/src/ui/public/i18n.js +5 -1
- package/dist/src/ui/public/index.html +1 -1
- package/docs/mobile.md +1 -1
- package/docs/releases.md +18 -0
- package/docs/ui.md +10 -2
- package/package.json +1 -1
package/README.ko.md
CHANGED
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.
|
|
19
|
+
npm install -g @ihyeon/flow-qa@0.2.4
|
|
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;
|
|
@@ -237,7 +243,7 @@
|
|
|
237
243
|
if (state.lastError && state.lastError !== app.lastError) { app.lastError = state.lastError; notice(state.lastError, true); }
|
|
238
244
|
}
|
|
239
245
|
async function work(fn) { if (app.busy) return; app.busy = true; readiness(); notice(''); try { await fn(); } catch (error) { showError(error); } finally { app.busy = false; readiness(); } }
|
|
240
|
-
async function loadSuite(file) { await work(async () => { const result = await api(`/api/suite?file=${encodeURIComponent(file)}`); $('flow-input').value = result.flow || ''; setDraft(result.suite, result.script, 'library', result.flow || '', file, result.suiteHash || ''); noticeKey('app.suiteLoaded'); view('scenario'); }); }
|
|
246
|
+
async function loadSuite(file) { await work(async () => { const result = await api(`/api/suite?file=${encodeURIComponent(file)}`); if (!confirmScenarioReplacement('app.confirmReplaceDraft')) return; $('flow-input').value = result.flow || ''; setDraft(result.suite, result.script, 'library', result.flow || '', file, result.suiteHash || ''); noticeKey('app.suiteLoaded'); view('scenario'); }); }
|
|
241
247
|
async function generate() { await work(async () => {
|
|
242
248
|
const flow = $('flow-input').value.trim();
|
|
243
249
|
if (!flow) throw localizedError('app.enterFlow');
|
|
@@ -261,6 +267,9 @@
|
|
|
261
267
|
|| suite !== JSON.stringify(app.draft, null, 2)
|
|
262
268
|
|| $('scenario-platform').value !== suitePlatform();
|
|
263
269
|
}
|
|
270
|
+
function confirmScenarioReplacement(key) {
|
|
271
|
+
return !hasUnsavedScenario() || window.confirm(t(key));
|
|
272
|
+
}
|
|
264
273
|
function warnBeforeUnload(event) {
|
|
265
274
|
if (!hasUnsavedScenario()) return;
|
|
266
275
|
event.preventDefault();
|
|
@@ -280,6 +289,7 @@
|
|
|
280
289
|
const body = { suite, ...(flow === undefined ? {} : { flow }) };
|
|
281
290
|
if (new TextEncoder().encode(JSON.stringify(body)).byteLength > 256 * 1024) throw localizedError('app.importTooLarge');
|
|
282
291
|
const result = await api('/api/import', body);
|
|
292
|
+
if (!confirmScenarioReplacement('app.confirmReplaceDraft')) return;
|
|
283
293
|
$('flow-input').value = flow || '';
|
|
284
294
|
setDraft(result.suite, result.script, 'imported', flow || '');
|
|
285
295
|
noticeKey('app.importComplete');
|
|
@@ -387,6 +397,9 @@
|
|
|
387
397
|
$('library-shortcut').addEventListener('click', () => view('library'));
|
|
388
398
|
$('new-draft-btn').addEventListener('click', () => {
|
|
389
399
|
if (app.busy) return;
|
|
400
|
+
const json = $('suite-editor').value;
|
|
401
|
+
const unsavedJson = json.trim() && (!app.savedFile || json !== JSON.stringify(app.draft, null, 2));
|
|
402
|
+
if (unsavedJson && !confirmScenarioReplacement('app.confirmNewDraft')) return;
|
|
390
403
|
app.draft = null; app.script = ''; app.savedFile = ''; app.savedHash = '';
|
|
391
404
|
app.origin = 'none'; app.sourceFlow = ''; app.boundSource = null; app.stale = false; app.dirty = false;
|
|
392
405
|
$('suite-editor').value = ''; $('review-checkbox').checked = false;
|
|
@@ -394,7 +407,7 @@
|
|
|
394
407
|
noticeKey('app.newDraftNotice');
|
|
395
408
|
$('suite-editor').focus();
|
|
396
409
|
});
|
|
397
|
-
$('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(); }
|
|
410
|
+
$('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(); });
|
|
398
411
|
$('scenario-platform').addEventListener('change', () => { app.planRequest += 1; if (app.draft) { app.stale = true; app.savedHash = ''; } $('review-checkbox').checked = false; readiness(); });
|
|
399
412
|
$('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(); });
|
|
400
413
|
$('review-checkbox').addEventListener('change', readiness); $('target-select').addEventListener('change', () => { $('review-checkbox').checked = false; targetDescription(); });
|
|
@@ -402,6 +415,8 @@
|
|
|
402
415
|
$('import-btn').addEventListener('click', importFiles);
|
|
403
416
|
$('global-stop-btn').addEventListener('click', stop);
|
|
404
417
|
$('reconnect-btn').addEventListener('click', () => refresh(true).catch(showError));
|
|
418
|
+
$('suite-download').addEventListener('click', () => { if (canDownloadSavedCases()) download(app.savedFile, `${JSON.stringify(app.draft, null, 2)}\n`, 'application/json'); });
|
|
419
|
+
$('flow-download').addEventListener('click', () => { if (canDownloadSourceMarkdown()) download(app.draft.source.file.split('/').pop(), app.sourceFlow, 'text/markdown'); });
|
|
405
420
|
$('script-download').addEventListener('click', () => { if (app.draft && !app.dirty && !app.stale) download(`${app.draft.id || 'flow-qa'}.test.mjs`, app.script, 'text/javascript'); });
|
|
406
421
|
$('report-md').addEventListener('click', downloadReportMarkdown);
|
|
407
422
|
$('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.
|
|
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을 입력하세요.'],
|
|
@@ -224,6 +226,8 @@
|
|
|
224
226
|
'app.plannerCliOnly': ['This model can be used from the CLI. Connect an OpenAI-compatible model to generate cases in the UI.', '이 모델은 CLI에서 사용할 수 있습니다. UI에서 생성하려면 OpenAI 호환 모델을 연결하세요.'],
|
|
225
227
|
'app.plannerReady': ['Ready to generate. The actual model call and costs may occur when you select Generate.', '생성 준비가 됐습니다. 실제 모델 호출과 비용은 생성 버튼을 누를 때 발생할 수 있습니다.'],
|
|
226
228
|
'app.runEnded': ['Run ended. Check Run history for results.', '실행이 종료됐습니다. 실행 기록에서 결과를 확인하세요.'],
|
|
229
|
+
'app.confirmReplaceDraft': ['Unsaved changes to the current scenario or cases will be discarded. Saved files will remain unchanged. Cancel to keep editing.', '현재 시나리오 또는 케이스의 저장되지 않은 변경 사항이 사라집니다. 저장된 파일은 변경되지 않습니다. 취소하면 계속 편집할 수 있습니다.'],
|
|
230
|
+
'app.confirmNewDraft': ['Unsaved case JSON will be cleared. The scenario text and saved files will remain unchanged. Cancel to keep editing.', '저장되지 않은 케이스 JSON이 지워집니다. 시나리오 내용과 저장된 파일은 유지됩니다. 취소하면 계속 편집할 수 있습니다.'],
|
|
227
231
|
'app.suiteLoaded': ['Saved cases loaded. Editing the linked scenario requires generating or loading cases again.', '저장된 케이스를 불러왔습니다. 연결된 시나리오를 수정하면 케이스를 다시 생성하거나 불러와야 합니다.'],
|
|
228
232
|
'app.enterFlow': ['Enter a scenario with expected behavior.', '기대 동작을 포함한 시나리오를 입력하세요.'],
|
|
229
233
|
'app.configurePlanner': ['Configure an OpenAI-compatible AI planner and key environment variable in Connections.', '연결 설정에서 OpenAI 호환 AI 플래너와 키 환경 변수를 준비하세요.'],
|
|
@@ -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">
|
|
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.
|
|
12
|
+
npm install -g @ihyeon/flow-qa@0.2.4
|
|
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.4: confirm before replacing unsaved cases
|
|
6
|
+
|
|
7
|
+
Loading a library suite, importing valid case files, or starting a new JSON draft now asks before discarding unsaved content. Cancel preserves the current draft and selected import files. New JSON drafts keep the scenario text; unchanged saved cases, flow-only JSON resets, and ordinary screen navigation stay quiet. Failed imports still report validation errors without replacing the draft. Existing save, review, generation, and execution behavior remains unchanged.
|
|
8
|
+
|
|
9
|
+
Locally verified on macOS with Node.js 24.14.1 on 2026-10-21: 92 tests, type checking, UI syntax and design-system audit, real Chromium confirmation acceptance/cancellation in English and Korean, preserved inputs and import selections, unchanged navigation and validation errors, and a fresh package install with three passing browser cases. Existing file download, re-import, and browser leave-page checks passed without model calls.
|
|
10
|
+
|
|
11
|
+
- Package: [`@ihyeon/flow-qa@0.2.4`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.2.4)
|
|
12
|
+
- Release: [GitHub 0.2.4](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.2.4)
|
|
13
|
+
|
|
14
|
+
## 0.2.3: download saved case files
|
|
15
|
+
|
|
16
|
+
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.
|
|
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, 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.
|
|
19
|
+
|
|
20
|
+
- Package: [`@ihyeon/flow-qa@0.2.3`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.2.3)
|
|
21
|
+
- Release: [GitHub 0.2.3](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.2.3)
|
|
22
|
+
|
|
5
23
|
## 0.2.2: warn before leaving an unsaved draft
|
|
6
24
|
|
|
7
25
|
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.
|
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.
|
|
10
|
+
npm install -g @ihyeon/flow-qa@0.2.4
|
|
11
11
|
flow-qa browser install
|
|
12
12
|
flow-qa ui --demo
|
|
13
13
|
```
|
|
@@ -42,7 +42,9 @@ 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
|
|
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.
|
|
46
|
+
|
|
47
|
+
From version `0.2.4`, loading a suite from the library or importing valid case files also asks before replacing unsaved scenario or case content. **New JSON draft** asks before clearing unsaved JSON and keeps the scenario text. Cancel keeps the current content and selected import files. Unchanged saved cases and ordinary navigation between screens do not prompt; invalid imports still show their validation error without replacing anything.
|
|
46
48
|
|
|
47
49
|
### Import existing case files
|
|
48
50
|
|
|
@@ -52,6 +54,12 @@ Import validates the suite and checks the selected Markdown against its declared
|
|
|
52
54
|
|
|
53
55
|
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.
|
|
54
56
|
|
|
57
|
+
### Download saved cases
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
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.
|
|
62
|
+
|
|
55
63
|
## Display and language
|
|
56
64
|
|
|
57
65
|
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.
|