cabloy 5.1.201 → 5.1.203

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.
Files changed (55) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/CONTRIBUTING.md +18 -17
  4. package/README.md +2 -2
  5. package/package.json +2 -3
  6. package/repo-agent-governance/managed-assets.json +33 -33
  7. package/repo-agent-governance/policies/repository.md +1 -0
  8. package/repo-agent-governance/skills/cabloy-spec-execution/SKILL.md +3 -2
  9. package/repo-agent-governance/skills/cabloy-spec-execution/references/execution-protocol.md +1 -1
  10. package/repo-agent-governance/skills/cabloy-spec-execution/references/status-and-evidence.md +11 -10
  11. package/repo-agent-governance/skills/cabloy-spec-generation/SKILL.md +1 -1
  12. package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +4 -2
  13. package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +16 -13
  14. package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +18 -11
  15. package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +2 -2
  16. package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +1 -1
  17. package/repo-agent-governance/skills/cabloy-worktree-environment/SKILL.md +5 -5
  18. package/repo-agent-governance/tests/spec-audit.test.mjs +323 -0
  19. package/repo-agent-governance/tools/spec-audit/audit.mjs +129 -3
  20. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +26 -7
  21. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.test.mjs +82 -0
  22. package/repo-agent-governance/tools/spec-charts/spec-parser.mjs +40 -1
  23. package/repo-docs/ai/ai-spec-driven-development.md +1 -0
  24. package/repo-docs/ai/playbook-spec-execution.md +3 -2
  25. package/repo-docs/ai/playbook-spec-generation.md +1 -1
  26. package/repo-docs/assets/img/cabloy-admin-ssr-cover-en.png +0 -0
  27. package/repo-docs/assets/img/cabloy-admin-tabs-cover-en.png +0 -0
  28. package/repo-docs/assets/img/cabloy-ai-crud-cover-en.png +0 -0
  29. package/repo-docs/demo.md +14 -0
  30. package/repo-docs/frontend/scripts.md +10 -18
  31. package/repo-docs/fullstack/parallel-worktree-environment.md +2 -2
  32. package/repo-docs/index.md +2 -2
  33. package/repo-docs/reference/repo-scripts.md +15 -31
  34. package/repo-e2e/config/playwright.config.ts +14 -19
  35. package/repo-e2e/scripts/e2e.test.ts +17 -0
  36. package/repo-e2e/scripts/e2e.ts +5 -1
  37. package/repo-e2e/scripts/runE2e.ts +3 -15
  38. package/repo-e2e/scripts/runE2eArgs.test.ts +21 -17
  39. package/repo-e2e/scripts/runE2eArgs.ts +16 -29
  40. package/repo-e2e/scripts/startE2eVona.ts +20 -9
  41. package/repo-e2e/specs/a-commerce.spec.ts +232 -31
  42. package/repo-e2e/specs/cabloy-basic.spec.ts +39 -12
  43. package/repo-e2e/specs/home-user-account.spec.ts +9 -2
  44. package/scripts/upgrade.test.ts +147 -0
  45. package/scripts/upgrade.ts +49 -27
  46. package/vona/packages-cli/cli/package.json +1 -1
  47. package/vona/packages-cli/cli-set-api/package.json +1 -1
  48. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +51 -5
  49. package/vona/packages-cli/cli-set-api/src/lib/command/bin.dev.ts +6 -1
  50. package/vona/packages-cli/cli-set-api/test/binDevMode.test.ts +52 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +41 -0
  52. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/e2eFixture.ts +52 -0
  53. package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/e2eFixture.ts +224 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/e2eFixture.ts +694 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-trade/test/e2eFixture.test.ts +434 -0
@@ -2,12 +2,20 @@
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { basename, resolve } from 'node:path';
4
4
 
5
- import { markdownLines, parseAtpIds, parseProgress, parseWbs } from './spec-parser.mjs';
5
+ import {
6
+ isPlanningCompleteEligible,
7
+ isPrerequisiteSatisfied,
8
+ markdownLines,
9
+ parseAtpIds,
10
+ parseProgress,
11
+ parseWbs,
12
+ } from './spec-parser.mjs';
6
13
  export { parseAtpIds, parseProgress, parseWbs } from './spec-parser.mjs';
7
14
 
8
15
  const STATUS_COLORS = {
9
16
  'not-started': 'var(--blue)',
10
17
  'in-progress': 'var(--orange)',
18
+ 'planning-complete': 'var(--teal)',
11
19
  'implementation-complete': 'var(--violet)',
12
20
  'verified': 'var(--aqua)',
13
21
  'blocked': 'var(--red)',
@@ -47,6 +55,7 @@ const COPY = {
47
55
  statuses: {
48
56
  'not-started': 'Not started',
49
57
  'in-progress': 'In progress',
58
+ 'planning-complete': 'Planning complete',
50
59
  'implementation-complete': 'Implementation complete',
51
60
  'verified': 'Verified',
52
61
  'blocked': 'Blocked',
@@ -104,6 +113,7 @@ const COPY = {
104
113
  statuses: {
105
114
  'not-started': '未开始',
106
115
  'in-progress': '进行中',
116
+ 'planning-complete': '规划完成',
107
117
  'implementation-complete': '实施完成',
108
118
  'verified': '已核验',
109
119
  'blocked': '受阻',
@@ -248,6 +258,11 @@ export function createChartModel({ readme, wbs, progress, testPlan }) {
248
258
  }
249
259
  task.recordedStatus = task.status;
250
260
  task.status = progressStatus;
261
+ if (task.status === 'planning-complete' && !isPlanningCompleteEligible(task)) {
262
+ throw new Error(
263
+ `${task.id} has planning-complete status without Completion mode: planning-only.`,
264
+ );
265
+ }
251
266
  if (task.deferred && task.status !== 'deferred') {
252
267
  throw new Error(
253
268
  `${task.id} is marked deferred in pdp-wbs.md but has progress status ${task.status}.`,
@@ -274,10 +289,10 @@ export function createChartModel({ readme, wbs, progress, testPlan }) {
274
289
  };
275
290
  }
276
291
 
277
- function style() {
292
+ function style(planningComplete) {
278
293
  return `<style>
279
- :root { --surface:#fcfcfb;--page:#f9f9f7;--ink:#0b0b0b;--secondary:#52514e;--muted:#898781;--grid:#e1e0d9;--axis:#c3c2b7;--blue:#2a78d6;--orange:#eb6834;--aqua:#1baf7a;--violet:#4a3aa7;--red:#e34948;--yellow:#eda100;--blue-wash:#eaf3fd;--aqua-wash:#e7f7f0; }
280
- @media (prefers-color-scheme:dark) { :root { --surface:#1a1a19;--page:#0d0d0d;--ink:#fff;--secondary:#c3c2b7;--muted:#a09f99;--grid:#2c2c2a;--axis:#383835;--blue:#3987e5;--orange:#d95926;--aqua:#199e70;--violet:#9085e9;--red:#e66767;--yellow:#c98500;--blue-wash:#172435;--aqua-wash:#122b23; } }
294
+ :root { --surface:#fcfcfb;--page:#f9f9f7;--ink:#0b0b0b;--secondary:#52514e;--muted:#898781;--grid:#e1e0d9;--axis:#c3c2b7;--blue:#2a78d6;--orange:#eb6834;${planningComplete ? '--teal:#087f83;' : ''}--aqua:#1baf7a;--violet:#4a3aa7;--red:#e34948;--yellow:#eda100;--blue-wash:#eaf3fd;--aqua-wash:#e7f7f0; }
295
+ @media (prefers-color-scheme:dark) { :root { --surface:#1a1a19;--page:#0d0d0d;--ink:#fff;--secondary:#c3c2b7;--muted:#a09f99;--grid:#2c2c2a;--axis:#383835;--blue:#3987e5;--orange:#d95926;${planningComplete ? '--teal:#29aeb0;' : ''}--aqua:#199e70;--violet:#9085e9;--red:#e66767;--yellow:#c98500;--blue-wash:#172435;--aqua-wash:#122b23; } }
281
296
  .canvas{fill:var(--page)}.card{fill:var(--surface);stroke:var(--grid);stroke-width:1}.title{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:27px;font-weight:700}.subtitle{fill:var(--secondary);font-family:Arial,Helvetica,sans-serif;font-size:14px}.heading{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:13px;font-weight:700}.phase{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:10px;font-weight:700}.task{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:10px}.small{fill:var(--muted);font-family:Arial,Helvetica,sans-serif;font-size:9px}.caption{fill:var(--secondary);font-family:Arial,Helvetica,sans-serif;font-size:11px}.num{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:700;font-variant-numeric:tabular-nums}.grid{stroke:var(--grid);stroke-width:1;shape-rendering:crispEdges}.axis{stroke:var(--axis);stroke-width:1;shape-rendering:crispEdges}.row:focus{outline:2px solid var(--ink);outline-offset:2px}
282
297
  </style>`;
283
298
  }
@@ -302,13 +317,13 @@ function renderChartTextLines(className, x, y, lines, lineHeight) {
302
317
  return `<text class="${className}" x="${x}" y="${y}">${lines.map((line, lineIndex) => `<tspan x="${x}" dy="${lineIndex ? lineHeight : 0}">${escapeXml(line)}</tspan>`).join('')}</text>`;
303
318
  }
304
319
 
305
- function svgDocument(title, desc, body, height, metadata) {
320
+ function svgDocument(title, desc, body, height, metadata, planningComplete = false) {
306
321
  return `<?xml version="1.0" encoding="UTF-8"?>
307
322
  <svg xmlns="http://www.w3.org/2000/svg" width="1600" height="${height}" viewBox="0 0 1600 ${height}" role="img" aria-labelledby="chart-title chart-desc">
308
323
  <title id="chart-title">${escapeXml(title)}</title>
309
324
  <desc id="chart-desc">${escapeXml(desc)}</desc>
310
325
  <metadata>${escapeXml(metadata)}</metadata>
311
- ${style()}
326
+ ${style(planningComplete)}
312
327
  ${body}
313
328
  </svg>
314
329
  `;
@@ -392,9 +407,12 @@ export function renderGantt(model, suiteName) {
392
407
  },
393
408
  )
394
409
  .join('\n');
395
- const completed = new Set(tasks.filter(task => task.status === 'verified').map(task => task.id));
410
+ const completed = new Set(
411
+ tasks.filter(task => isPrerequisiteSatisfied(task, task.status)).map(task => task.id),
412
+ );
396
413
  const nextTask = tasks.find(
397
414
  task =>
415
+ task.status !== 'planning-complete' &&
398
416
  task.status !== 'verified' &&
399
417
  task.status !== 'deferred' &&
400
418
  task.status !== 'waived' &&
@@ -418,6 +436,7 @@ export function renderGantt(model, suiteName) {
418
436
  body,
419
437
  height,
420
438
  t.metadata,
439
+ tasks.some(task => task.status === 'planning-complete'),
421
440
  );
422
441
  }
423
442
 
@@ -152,6 +152,88 @@ test('creates a mixed-status model with deferred scope separated', () => {
152
152
  assert.equal(model.activeTasks.length, 2);
153
153
  assert.equal(model.deferredTasks.length, 1);
154
154
  assert.equal(model.verified, 0);
155
+ assert.doesNotMatch(renderGantt(model, 'Demo'), /--teal:/);
156
+ });
157
+
158
+ test('planning completion requires an explicit task mode and preserves verified-only metrics', () => {
159
+ const wbs = files['pdp-wbs.md'].replace(
160
+ '#### WBS-10-01: Freeze the baseline',
161
+ '#### WBS-10-01: Freeze the baseline\n\nCompletion mode: planning-only.',
162
+ );
163
+ const progress = files['progress.md'].replace('`not-started`', '`planning-complete`');
164
+ assert.throws(
165
+ () =>
166
+ createChartModel({
167
+ readme: files['README.md'],
168
+ wbs: files['pdp-wbs.md'],
169
+ progress,
170
+ testPlan: files['test-plan.md'],
171
+ }),
172
+ /planning-complete status without Completion mode/,
173
+ );
174
+ const model = createChartModel({
175
+ readme: files['README.md'],
176
+ wbs,
177
+ progress,
178
+ testPlan: files['test-plan.md'],
179
+ });
180
+ assert.equal(model.tasks[0].completionMode, 'planning-only');
181
+ assert.equal(model.verified, 0);
182
+ const gantt = renderGantt(model, 'Demo');
183
+ assert.match(gantt, /Planning complete/);
184
+ assert.match(gantt, /--teal:#087f83/);
185
+ assert.doesNotMatch(renderBurndown(model, 'Demo'), /--teal:/);
186
+ assert.match(gantt, /class="caption" x="96" y="195">WBS-20-01: Deliver/);
187
+ assert.doesNotMatch(gantt, /class="caption" x="96" y="195">WBS-10-01/);
188
+ assert.match(renderBurndown(model, 'Demo'), />2 remaining · 0 verified</);
189
+ const blocked = createChartModel({
190
+ readme: files['README.md'],
191
+ wbs,
192
+ progress: progress.replace('`in-progress`', '`blocked`'),
193
+ testPlan: files['test-plan.md'],
194
+ });
195
+ assert.match(renderGantt(blocked, 'Demo'), /No dependency-ready WBS candidate is recorded/);
196
+ assert.equal(blocked.verified, 0);
197
+ const chinese = createChartModel({
198
+ readme: '# 中文规划\n\n完整中文记录。',
199
+ wbs,
200
+ progress,
201
+ testPlan: files['test-plan.md'],
202
+ });
203
+ assert.match(renderGantt(chinese, '示例'), /规划完成/);
204
+ assert.match(renderBurndown(chinese, '示例'), /2 剩余 · 0 已核验/);
205
+ });
206
+
207
+ test('completion mode must be unique, supported and task-local', () => {
208
+ const wbs = files['pdp-wbs.md'];
209
+ for (const value of ['unknown.', '', 'planning-only.\nCompletion mode: planning-only.']) {
210
+ assert.throws(
211
+ () => parseWbs(wbs.replace('Tasks:', `Completion mode: ${value}\n\nTasks:`)),
212
+ /Completion mode/,
213
+ );
214
+ }
215
+ assert.throws(
216
+ () =>
217
+ parseWbs(
218
+ wbs.replace('Dependencies: none.', 'Dependencies: none.\nCompletion mode: planning-only.'),
219
+ ),
220
+ /Completion mode must belong to a formal WBS task/,
221
+ );
222
+ assert.throws(
223
+ () => parseWbs(`Completion mode: planning-only.\n${wbs}`),
224
+ /Completion mode must belong to a formal WBS task/,
225
+ );
226
+ const progress = files['progress.md'].replace('`not-started`', '`planning-complete`');
227
+ assert.throws(
228
+ () =>
229
+ createChartModel({
230
+ readme: files['README.md'],
231
+ wbs: wbs.replace('Tasks:', 'Status: in-progress.\nCompletion mode: planning-only.\nTasks:'),
232
+ progress,
233
+ testPlan: files['test-plan.md'],
234
+ }),
235
+ /explicit.*Status in-progress.*planning-complete/,
236
+ );
155
237
  });
156
238
 
157
239
  test('selects the earliest dependency-ready WBS item', async t => {
@@ -2,6 +2,7 @@
2
2
  export const STATUS_ORDER = [
3
3
  'not-started',
4
4
  'in-progress',
5
+ 'planning-complete',
5
6
  'implementation-complete',
6
7
  'verified',
7
8
  'blocked',
@@ -306,6 +307,16 @@ function normalizeStatus(value) {
306
307
  return normalized;
307
308
  }
308
309
 
310
+ export function isPlanningCompleteEligible(task) {
311
+ return task.completionMode === 'planning-only';
312
+ }
313
+
314
+ export function isPrerequisiteSatisfied(task, status) {
315
+ return (
316
+ status === 'verified' || (status === 'planning-complete' && isPlanningCompleteEligible(task))
317
+ );
318
+ }
319
+
309
320
  function reviewDateFromLine(line) {
310
321
  return (
311
322
  line.match(/(?:last reviewed|最后审查日期)\s*(?:[::]\s*)?(\d{4}-\d{2}-\d{2})/i)?.[1] ?? null
@@ -360,7 +371,7 @@ export function parseProgress(markdown, { file } = {}) {
360
371
  }
361
372
 
362
373
  function labeledValue(record, labels) {
363
- const match = record.text.trim().match(/^([A-Z]+)\s*:(.*)$/i);
374
+ const match = record.text.trim().match(/^([A-Z]+(?:\s+[A-Z]+)*)\s*:(.*)$/i);
364
375
  return match && labels.includes(match[1].toLowerCase()) ? match[2].trim() : null;
365
376
  }
366
377
 
@@ -502,6 +513,11 @@ export function parseWbs(markdown, { file } = {}) {
502
513
  task = null;
503
514
  inPhase = false;
504
515
  }
516
+ if (!task && labeledValue(record, ['completion mode']) !== null) {
517
+ throw new Error(
518
+ `Completion mode must belong to a formal WBS task at ${atLocation(record, file)}.`,
519
+ );
520
+ }
505
521
  if (!inPhase) continue;
506
522
  if (task) task.body.push(record);
507
523
  else if (!phase.tasks.length) phase.preamble.push(record);
@@ -519,6 +535,29 @@ export function parseWbs(markdown, { file } = {}) {
519
535
  ? { line: dependency.line, ...(file ? { file } : {}) }
520
536
  : undefined;
521
537
  task.phase = { number: phase.number, title: phase.title };
538
+ const modeRecords = task.body.filter(
539
+ record => labeledValue(record, ['completion mode']) !== null,
540
+ );
541
+ if (modeRecords.length > 1) {
542
+ throw new Error(
543
+ `Multiple ${task.id} Completion mode fields at ${atLocation(modeRecords[1], file)}.`,
544
+ );
545
+ }
546
+ const mode = modeRecords.length
547
+ ? labeledValue(modeRecords[0], ['completion mode'])
548
+ .replace(/`/g, '')
549
+ .replace(/\.$/, '')
550
+ .toLowerCase()
551
+ : undefined;
552
+ if (mode && mode !== 'planning-only') {
553
+ throw new Error(
554
+ `Unsupported ${task.id} Completion mode at ${atLocation(modeRecords[0], file)}.`,
555
+ );
556
+ }
557
+ if (modeRecords.length && !mode) {
558
+ throw new Error(`Empty ${task.id} Completion mode at ${atLocation(modeRecords[0], file)}.`);
559
+ }
560
+ task.completionMode = mode;
522
561
  const statusRecords = task.body.filter(record => labeledValue(record, ['status']) !== null);
523
562
  if (statusRecords.length > 1) {
524
563
  throw new Error(
@@ -53,6 +53,7 @@ For the detailed authority table, identifiers, and chart boundary, see [Generate
53
53
 
54
54
  **Evidence-gated delivery** means delivery status stays precise about what is known and what has been proven.
55
55
 
56
+ - `planning-complete` means an explicitly `planning-only` WBS task's documentary checks have a named-reviewer, revision-scoped closure disposition and retained planning proof. It satisfies that task's prerequisite edge, not implementation, ATP, successor approval, or release.
56
57
  - `implementation-complete` means the source work is complete, while required ATP or release proof remains.
57
58
  - `verified` means all applicable WBS checks and ATPs have durable, linked, redacted observed evidence.
58
59
 
@@ -109,11 +109,12 @@ Use the narrowest approved check first, then follow the linked ATP procedures an
109
109
 
110
110
  Use status precisely:
111
111
 
112
- - `in-progress` — approved implementation or verification has actually begun
112
+ - `in-progress` — approved work or verification has actually begun
113
+ - `planning-complete` — only an opted-in documentary/design WBS task with a named-reviewer, revision-scoped closure disposition and linked planning proof; no source or ATP closure
113
114
  - `implementation-complete` — source work is complete, but required ATP or release proof remains
114
115
  - `verified` — all applicable WBS checks and ATPs have durable, linked, redacted observed evidence
115
116
 
116
- A successful build, generation command, hook, manual walkthrough, screenshot, or unrelated test run is not by itself `verified` unless the authoritative test plan defines it as sufficient retained proof. Evidence is revision- and authority-scoped; mark old proof superseded or requiring rerun when relevant source or authority changes.
117
+ `planning-complete` satisfies a predecessor dependency edge but does not unblock a task still recorded `blocked` or grant an execution dossier. Charts count only `verified` toward verified/remaining metrics. A successful build, generation command, hook, manual walkthrough, screenshot, or unrelated test run is not by itself `verified` unless the authoritative test plan defines it as sufficient retained proof. Evidence is revision- and authority-scoped; mark old proof superseded or requiring rerun when relevant source or authority changes.
117
118
 
118
119
  ## Refresh derived charts last
119
120
 
@@ -115,7 +115,7 @@ PRD → SRS → WBS → ATP → Evidence
115
115
 
116
116
  A product or technical change belongs in its PRD, SRS, or accepted ADR before its WBS, acceptance, progress, evidence, and chart implications are updated. A progress entry or chart cannot introduce a requirement, resolve a contract conflict, or accept an ADR.
117
117
 
118
- Planning records and derived charts do not establish `implementation-complete` or `verified`. `verified` requires the applicable acceptance procedure and retained, redacted observed evidence. A generated plan, planned command, scaffold, screenshot, or unrelated check is not automatically sufficient proof.
118
+ A WBS task may declare `Completion mode: planning-only.` only for documentary/design-only checks. It can reach `planning-complete` after an explicit named-reviewer, revision-scoped closure disposition and linked planning proof; this satisfies its dependency edge without executing source, passing future ATPs, unblocking a recorded `blocked` successor, or counting as `verified` in charts. Planning records and derived charts do not establish `implementation-complete` or `verified`. `verified` requires the applicable acceptance procedure and retained, redacted observed evidence. A generated plan, planned command, scaffold, screenshot, or unrelated check is not automatically sufficient proof.
119
119
 
120
120
  ## Check planning without claiming implementation
121
121
 
package/repo-docs/demo.md CHANGED
@@ -10,3 +10,17 @@ Explore the Cabloy demonstration sites:
10
10
  | ---------- | ---------------------------------------------------------------- |
11
11
  | Web site | [https://demo.cabloy.com/](https://demo.cabloy.com/) |
12
12
  | Admin site | [https://demo.cabloy.com/admin/](https://demo.cabloy.com/admin/) |
13
+
14
+ ## Videos
15
+
16
+ ### 1. Can an Admin Site Use SSR? CabloyJS in Three Practical Demos (Duration: 1:16)
17
+
18
+ [![CabloyJS Admin SSR video](./assets/img/cabloy-admin-ssr-cover-en.png)](https://youtu.be/786IQhRdr1I)
19
+
20
+ ### 2. A First: Second-Level Tabs for Admin Multitasking (Duration: 1:50)
21
+
22
+ [![CabloyJS Admin Tabs video](./assets/img/cabloy-admin-tabs-cover-en.png)](https://youtu.be/L6DxD-JfztQ)
23
+
24
+ ### 3. AI Coding in Action: One Prompt for CRUD, SSR & Multitasking (Duration: 3:50)
25
+
26
+ [![CabloyJS AI CRUD video](./assets/img/cabloy-ai-crud-cover-en.png)](https://youtu.be/PV9_By08hDM)
@@ -68,23 +68,16 @@ npm run deps:vona
68
68
  npm run test:e2e cabloy-basic home-user-account
69
69
  ```
70
70
 
71
- The managed clean command resets Vona-managed test data and the local Redis namespace, starts one development Vona worker, and runs the selected specs. Browser commands consume already-built artifacts; they do not rebuild them.
72
-
73
- The unified runner uses two commands:
74
-
75
- ```text
76
- npm run test:e2e clean local run with database reset
77
- npm run test:e2e:fast fast run without automatic reset
78
- ```
71
+ The sole managed `npm run test:e2e` command starts a fresh local Vona worker with `--workers=1 --flavor=normal --mode=test`, then runs the selected specs against Vona integrated SSR. Test-mode startup initializes test resources without a separate `db:reset`; browser commands consume already-built artifacts and do not rebuild them. Confirm exclusive ownership of the effective test database, `_local` Redis namespace, public/runtime paths, `APP_NAME`, API origin, and listener before startup; a free port alone is insufficient.
79
72
 
80
73
  Place flat spec basenames directly after the npm script name. Use npm's `--` delimiter before Playwright options. With no names, every spec in `repo-e2e/specs` runs. Native `--grep` and `--grep-invert` remain available, and repeatable `--tag` values require all listed tags:
81
74
 
82
75
  ```bash
83
76
  npm run test:e2e cabloy-basic home-user-account -- --grep @flow
84
- npm run test:e2e:fast a-commerce -- --tag @web --tag @smoke
77
+ npm run test:e2e a-commerce -- --tag @web --tag @smoke
85
78
  ```
86
79
 
87
- For the complete tag vocabulary, managed-runner argument boundaries, and externally managed-target examples, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks). A separately managed target uses `E2E_BASE_URL` with `test:e2e:fast`; the caller owns external-target data, cache, and artifact freshness.
80
+ For the complete Basic tag vocabulary and managed-runner argument boundaries, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks). `E2E_BASE_URL` must be unset: externally managed targets are unsupported.
88
81
 
89
82
  ## Cabloy Start root wrappers
90
83
 
@@ -102,7 +95,7 @@ Cabloy Start does not expose the Basic Commerce wrapper family.
102
95
 
103
96
  ## Start SSR browser acceptance
104
97
 
105
- The Start Web and Admin browser baseline also exercises Vona SSR dispatch at the effective local server port. Prepare current SSR and REST artifacts, then use the managed local command:
98
+ The Start Web and Admin browser baseline exercises Vona integrated SSR dispatch at the effective local test-mode server port. Prepare current SSR and REST artifacts, then use the managed local command:
106
99
 
107
100
  ```bash
108
101
  npm run build:zova
@@ -110,23 +103,22 @@ npm run deps:vona
110
103
  npm run test:e2e
111
104
  ```
112
105
 
113
- Start uses the same unified E2E command pair as Basic:
106
+ Start uses one E2E command and one fresh managed local test-mode lifecycle:
114
107
 
115
108
  ```text
116
- npm run test:e2e managed clean local suite run
117
- npm run test:e2e:fast fast rerun without an automatic reset
109
+ npm run test:e2e fresh managed local test-mode suite run
118
110
  ```
119
111
 
120
112
  Select flat spec basenames and filter scenarios or surfaces with runner tags and native Playwright arguments. Current Start scenarios use tags including `@web`, `@admin`, `@smoke`, `@layout`, `@cabloy-admin`, `@account`, `@ssr`, and `@flow`:
121
113
 
122
114
  ```bash
123
115
  npm run test:e2e cabloy-start
124
- npm run test:e2e:fast cabloy-start -- --tag @web
125
- npm run test:e2e:fast cabloy-admin -- --tag @admin --tag @cabloy-admin
126
- npm run test:e2e:fast cabloy-start -- --grep ATP-START-FLOW-01
116
+ npm run test:e2e cabloy-start -- --tag @web
117
+ npm run test:e2e cabloy-admin -- --tag @admin --tag @cabloy-admin
118
+ npm run test:e2e cabloy-start -- --grep ATP-START-FLOW-01
127
119
  ```
128
120
 
129
- For a separately managed Start target, set `E2E_BASE_URL` and use `test:e2e:fast`. The target owner is responsible for data, cache, artifact freshness, and process lifecycle.
121
+ Start does not support externally managed E2E targets: `E2E_BASE_URL` must be unset. The command requires the configured local port to be available and starts one Vona worker with `--flavor=normal --mode=test`; Playwright does not reuse an existing process. Test-mode startup initializes test state. Confirm exclusive ownership of the test database, Redis namespace, and public/runtime paths before running it. The command does not replace the paired build/dependency-sync prerequisite; do not change shared environment identity or ports to avoid a busy resource.
130
122
 
131
123
  ## Zova script model
132
124
 
@@ -77,12 +77,12 @@ For privacy, the skill writes only to absent or empty permitted broad local file
77
77
 
78
78
  Use commands from the active repository root.
79
79
 
80
- | Edition | Marker | Frontend command | Managed clean E2E |
80
+ | Edition | Marker | Frontend command | Managed E2E |
81
81
  | ------------ | ------------------ | -------------------------------------------------- | ------------------ |
82
82
  | Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
83
83
  | Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
84
84
 
85
- Run one frontend command, not both, in each worktree. Both managed clean E2E workflows read Vona's effective local `SERVER_LISTEN_PORT`, then reset, start, and target that local runtime.
85
+ Run one frontend command, not both, in each worktree. Both managed E2E workflows read Vona's effective local `SERVER_LISTEN_PORT` and start one fresh Vona test-mode worker; startup initializes test resources without a separate reset. Before either command, confirm exclusive ownership of the effective test database, `_local` Redis namespace, public/runtime paths, `APP_NAME`, API origin, and listener. A free port alone does not establish ownership. Do not change shared identity or ports to bypass a collision, and leave `E2E_BASE_URL` unset: externally managed targets are unsupported.
86
86
 
87
87
  ## What this isolates
88
88
 
@@ -32,9 +32,9 @@ features:
32
32
 
33
33
  ## Demonstrations(Videos)
34
34
 
35
- ### 1. Can an Admin Site Use SSR? CabloyJS in Three Practical Demos (Duration: 1:16)
35
+ ### 1. AI Coding in Action: One Prompt for CRUD, SSR & Multitasking (Duration: 3:50)
36
36
 
37
- [![CabloyJS Admin SSR video](./assets/img/cabloy-admin-ssr-cover-en.png)](https://youtu.be/786IQhRdr1I)
37
+ [![CabloyJS AI CRUD video](./assets/img/cabloy-ai-crud-cover-en.png)](https://youtu.be/PV9_By08hDM)
38
38
 
39
39
  ## Choose a reading path
40
40
 
@@ -40,9 +40,7 @@ Cabloy Basic exposes these shared root scripts:
40
40
  - `npm run start`
41
41
  - `npm run start:one`
42
42
  - `npm run test`
43
- - `npm run db:reset`
44
43
  - `npm run test:e2e`
45
- - `npm run test:e2e:fast`
46
44
  - `npm run tsc`
47
45
  - `npm run docs:dev`
48
46
  - `npm run docs:build`
@@ -117,7 +115,6 @@ Cabloy Start exposes the equivalent Start repository surface:
117
115
  - `npm run test`
118
116
  - `npm run db:reset`
119
117
  - `npm run test:e2e`
120
- - `npm run test:e2e:fast`
121
118
  - `npm run tsc`
122
119
 
123
120
  Cabloy Start does not expose Basic Commerce or root documentation wrappers.
@@ -138,7 +135,7 @@ repo-e2e/specs/home-user-account.spec.ts
138
135
  repo-e2e/specs/a-commerce.spec.ts
139
136
  ```
140
137
 
141
- It also reconciles the two framework E2E scripts and the `@playwright/test` development dependency. Keep additional project browser specs under other filenames in `repo-e2e/specs`; the upgrader updates only the listed framework files. The current fresh baseline is required and is not repaired for unsupported legacy project layouts.
138
+ It reconciles the single framework-owned `test:e2e` script and the `@playwright/test` development dependency. The retired `test:e2e:fast` script is removed only if its value matches the known old framework command; a customized project alias is preserved. Keep additional project browser specs under other filenames in `repo-e2e/specs`; the upgrader updates only the listed framework files. The current fresh baseline is required and is not repaired for unsupported legacy project layouts.
142
139
 
143
140
  ### Cabloy Start repository
144
141
 
@@ -154,31 +151,28 @@ The public-package upgrade flow does not source or reconcile the Start-owned bas
154
151
 
155
152
  ## SSR browser checks
156
153
 
157
- The unified runner has two modes:
158
-
159
- - `npm run test:e2e`: clean local run; checks the managed Vona port, resets the database, starts one development Vona worker, and runs Playwright.
160
- - `npm run test:e2e:fast`: skips the reset for quick reruns and may target either the local managed Vona server or an externally managed `E2E_BASE_URL`.
154
+ Cabloy Basic and Cabloy Start each use a single managed local `npm run test:e2e` command. In Basic, the runner checks the effective `normal/test/local` Vona listener port and starts one fresh `--workers=1 --flavor=normal --mode=test` worker. Test-mode startup initializes test resources; the runner does not invoke a separate `db:reset` or a development-mode server. Playwright does not reuse an existing process. Confirm exclusive ownership of the effective test database, `_local` Redis namespace, public/runtime paths, `APP_NAME`, API origin, and listener before starting it; port availability alone is insufficient.
161
155
 
162
- These browser checks target Vona integrated SSR. In the Cabloy Basic default environment, the Vona listener is `7102`; the Zova standalone SSR development server uses `9000` and is not an acceptance target.
156
+ Both editions' browser checks target Vona integrated SSR. In the Cabloy Basic default environment, the Vona listener is `7102`; the Zova standalone SSR development server uses `9000` and is not an acceptance target.
163
157
 
164
158
  Place spec basenames directly after the npm script name; use npm's `--` delimiter only before Playwright options. Multiple spec names are allowed. With no names, every spec in `repo-e2e/specs` is discovered:
165
159
 
166
160
  ```bash
167
161
  npm run test:e2e cabloy-basic home-user-account
168
162
  npm run test:e2e a-commerce
169
- npm run test:e2e:fast home-user-account
170
- npm run test:e2e:fast a-commerce -- --grep ATP-SSR
171
- npm run test:e2e:fast a-commerce -- --grep-invert @admin
163
+ npm run test:e2e home-user-account
164
+ npm run test:e2e a-commerce -- --grep ATP-SSR
165
+ npm run test:e2e a-commerce -- --grep-invert @admin
172
166
  ```
173
167
 
174
168
  Tags remain independent from filenames. Repeat `--tag` to require all tags, while native `--grep` and `--grep-invert` remain available:
175
169
 
176
170
  ```bash
177
- npm run test:e2e:fast a-commerce -- --tag @web --tag @smoke
178
- npm run test:e2e:fast home-user-account -- --grep @flow --tag @web
171
+ npm run test:e2e a-commerce -- --tag @web --tag @smoke
172
+ npm run test:e2e home-user-account -- --grep @flow --tag @web
179
173
  ```
180
174
 
181
- The existing tags include `@web`, `@admin`, `@smoke`, `@flow`, `@ssr`, `@theme`, and the business tags used by Commerce such as `@cart`, `@payment`, `@shipment`, and `@refund`. No suite tag is required. Clean runs are local-only and reject `E2E_BASE_URL`; fast runs against an external target do not reset, start, stop, or rebuild that target.
175
+ The existing Basic tags include `@web`, `@admin`, `@smoke`, `@flow`, `@ssr`, `@theme`, and the business tags used by Commerce such as `@cart`, `@payment`, `@shipment`, and `@refund`. No suite tag is required. In Cabloy Basic, `E2E_BASE_URL` must be unset: external targets and a fast/no-reset mode are unsupported.
182
176
 
183
177
  ### Cabloy Basic and Commerce
184
178
 
@@ -198,15 +192,9 @@ npm run deps:vona
198
192
  npm run test:e2e a-commerce
199
193
  ```
200
194
 
201
- For a separately managed target, use the fast command:
202
-
203
- ```bash
204
- E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast a-commerce -- --tag @smoke
205
- ```
206
-
207
195
  ### Cabloy Start
208
196
 
209
- The Start suite exercises Web at `/` and Admin at `/admin` through Vona integrated SSR dispatch. Prepare current Start artifacts before a managed local run:
197
+ The Start suite exercises Web at `/` and Admin at `/admin` through Vona integrated SSR dispatch. Prepare current Start artifacts before a managed local run. The runner uses the effective local `normal`-flavor, test-mode Vona listener, rather than assuming a fixed port:
210
198
 
211
199
  ```bash
212
200
  npm run build:zova
@@ -216,20 +204,16 @@ npm run test:e2e
216
204
 
217
205
  ```bash
218
206
  # Exact acceptance scenario
219
- npm run test:e2e:fast cabloy-start -- --grep ATP-START-FLOW-01
207
+ npm run test:e2e cabloy-start -- --grep ATP-START-FLOW-01
220
208
 
221
209
  # Category or surface selection
222
- npm run test:e2e:fast cabloy-start -- --tag @smoke
223
- npm run test:e2e:fast cabloy-admin -- --tag @admin --tag @cabloy-admin
210
+ npm run test:e2e cabloy-start -- --tag @smoke
211
+ npm run test:e2e cabloy-admin -- --tag @admin --tag @cabloy-admin
224
212
  ```
225
213
 
226
- For an externally managed Start target, set `E2E_BASE_URL` and use `npm run test:e2e:fast`. The target owner is responsible for data, cache, artifact freshness, and process lifecycle:
214
+ In Cabloy Start, the sole `npm run test:e2e` command checks the configured local port and starts a fresh runner-managed Vona target with `--workers=1 --flavor=normal --mode=test`. Playwright never reuses an existing server. Test-mode startup initializes test resources; the runner does not invoke a separate `db:reset`. Confirm ownership of the test database, Redis namespace, and public/runtime paths before running it; do not change shared environment identity or ports to bypass a busy resource.
227
215
 
228
- ```bash
229
- E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast
230
- E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-admin -- --tag @admin
231
- E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-start -- --tag @web
232
- ```
216
+ `E2E_BASE_URL` is unsupported in Cabloy Start and must be unset, even for a local URL. Externally managed targets are not supported.
233
217
 
234
218
  Browser commands consume existing SSR and REST artifacts; they never rebuild them. Install Chromium once when needed with `npx playwright install chromium`.
235
219
 
@@ -2,9 +2,6 @@ import { defineConfig } from '@playwright/test';
2
2
 
3
3
  import { E2E_LOCAL_BASE_URL, E2E_ROOT_DIR } from '../scripts/e2e.ts';
4
4
 
5
- const externalBaseURL = process.env.E2E_BASE_URL;
6
- const baseURL = externalBaseURL || E2E_LOCAL_BASE_URL;
7
-
8
5
  export default defineConfig({
9
6
  testDir: `${E2E_ROOT_DIR}/repo-e2e/specs`,
10
7
  fullyParallel: false,
@@ -13,22 +10,20 @@ export default defineConfig({
13
10
  retries: process.env.CI ? 2 : 0,
14
11
  reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'list',
15
12
  use: {
16
- baseURL,
13
+ baseURL: E2E_LOCAL_BASE_URL,
17
14
  trace: 'on-first-retry',
18
15
  },
19
- webServer: externalBaseURL
20
- ? undefined
21
- : {
22
- command: 'node repo-e2e/scripts/startE2eVona.ts',
23
- cwd: E2E_ROOT_DIR,
24
- url: `${baseURL}/health/ready`,
25
- timeout: 180_000,
26
- reuseExistingServer: false,
27
- stdout: 'pipe',
28
- stderr: 'pipe',
29
- gracefulShutdown: {
30
- signal: 'SIGINT',
31
- timeout: 10_000,
32
- },
33
- },
16
+ webServer: {
17
+ command: 'node repo-e2e/scripts/startE2eVona.ts',
18
+ cwd: E2E_ROOT_DIR,
19
+ url: `${E2E_LOCAL_BASE_URL}/health/ready`,
20
+ timeout: 180_000,
21
+ reuseExistingServer: false,
22
+ stdout: 'pipe',
23
+ stderr: 'pipe',
24
+ gracefulShutdown: {
25
+ signal: 'SIGINT',
26
+ timeout: 10_000,
27
+ },
28
+ },
34
29
  });
@@ -0,0 +1,17 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import test from 'node:test';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ test('rejects a defined E2E_BASE_URL before loading local configuration', () => {
7
+ const entrypoint = fileURLToPath(new URL('./e2e.ts', import.meta.url));
8
+ for (const value of ['', 'http://127.0.0.1:7102']) {
9
+ const result = spawnSync(process.execPath, [entrypoint], {
10
+ env: { ...process.env, E2E_BASE_URL: value },
11
+ encoding: 'utf8',
12
+ });
13
+ assert.notEqual(result.status, 0);
14
+ assert.match(result.stderr, /E2E_BASE_URL is unsupported/);
15
+ assert.doesNotMatch(result.stderr, /SERVER_LISTEN_PORT/);
16
+ }
17
+ });
@@ -4,8 +4,12 @@ import { fileURLToPath } from 'node:url';
4
4
 
5
5
  export const E2E_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
6
6
 
7
+ if (process.env.E2E_BASE_URL !== undefined) {
8
+ throw new Error('E2E_BASE_URL is unsupported; the E2E runner manages a local test-mode target.');
9
+ }
10
+
7
11
  const vonaEnv = dotenv.loadEnvs(
8
- { flavor: 'normal', mode: 'dev', local: 'local' },
12
+ { flavor: 'normal', mode: 'test', local: 'local' },
9
13
  resolve(E2E_ROOT_DIR, 'vona', 'env'),
10
14
  '.env',
11
15
  );