cabloy 5.1.147 → 5.1.149

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 (43) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-workflow/evals/evals.json +1 -1
  3. package/.claude/skills/cabloy-worktree-environment/SKILL.md +2 -5
  4. package/.claude/skills/cabloy-worktree-environment/evals/evals.json +1 -1
  5. package/CHANGELOG.md +17 -0
  6. package/package.json +1 -1
  7. package/repo-docs/.vitepress/config.mjs +4 -0
  8. package/repo-docs/backend/markdown-guide.md +1 -0
  9. package/repo-docs/backend/relations-guide.md +1 -0
  10. package/repo-docs/frontend/form-layout-guide.md +15 -1
  11. package/repo-docs/frontend/markdown-guide.md +1 -0
  12. package/repo-docs/frontend/scripts.md +11 -11
  13. package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
  14. package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
  15. package/repo-docs/fullstack/parallel-worktree-environment.md +4 -4
  16. package/repo-docs/fullstack/quickstart.md +4 -4
  17. package/repo-docs/reference/repo-scripts.md +12 -14
  18. package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
  19. package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
  20. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
  21. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
  22. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
  23. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
  24. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
  25. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +21 -2
  26. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
  27. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +21 -2
  28. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +21 -2
  29. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
  30. package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
  31. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
  32. package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
  33. package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +63 -6
  34. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +117 -7
  35. package/zova/packages-zova/zova/package.json +2 -2
  36. package/zova/pnpm-lock.yaml +19 -15
  37. package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
  38. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +40 -40
  39. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +358 -337
  40. package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +18 -7
  41. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  42. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +13 -5
  43. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -75,10 +75,10 @@ For privacy, the skill writes only to absent or empty permitted broad local file
75
75
 
76
76
  Use commands from the active repository root.
77
77
 
78
- | Edition | Marker | Frontend command | Managed clean E2E |
79
- | ------------ | ------------------ | -------------------------------------------------- | ------------------------------ |
80
- | Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
81
- | Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e:start:clean` |
78
+ | Edition | Marker | Frontend command | Managed clean E2E |
79
+ | ------------ | ------------------ | -------------------------------------------------- | ------------------ |
80
+ | Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
81
+ | Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
82
82
 
83
83
  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.
84
84
 
@@ -128,20 +128,20 @@ Cabloy Start is checked out from the licensed private repository rather than cre
128
128
  ```text
129
129
  repo-e2e/config/
130
130
  repo-e2e/scripts/
131
- repo-e2e/specs/cabloy-start/
131
+ repo-e2e/specs/
132
132
  ```
133
133
 
134
- The public Cabloy upgrade flow does not synchronize or repair those Start E2E files, the `test:e2e:start*` scripts, or `@playwright/test`. Keep project-owned browser scenarios outside the baseline paths, for example under `repo-e2e/specs/my-project/`.
134
+ The public Cabloy upgrade flow does not synchronize or repair those private Start E2E files, its root E2E scripts, or `@playwright/test`. Keep project-owned browser scenarios in the flat `repo-e2e/specs/` directory under distinct filenames, for example `repo-e2e/specs/my-project.spec.ts`.
135
135
 
136
136
  Prepare and run the managed Start baseline locally with:
137
137
 
138
138
  ```bash
139
139
  npm run build:zova
140
140
  npm run deps:vona
141
- npm run test:e2e:start:clean
141
+ npm run test:e2e
142
142
  ```
143
143
 
144
- The managed command requires port `7102` to be available and owns the local reset and Vona worker lifecycle. For a separately managed target, set `START_E2E_BASE_URL` and use `test:e2e:start`, `test:e2e:start:web`, or `test:e2e:start:admin`; those commands do not reset, build, start, or stop the target. Install Chromium once when needed with `npx playwright install chromium`. See [Repo Scripts](/reference/repo-scripts#ssr-browser-checks) for the complete command variants.
144
+ The managed command checks the effective local server port and owns the local reset and Vona worker lifecycle. For a separately managed target, set `E2E_BASE_URL` and use `npm run test:e2e:fast`; this mode does not reset, build, start, or stop the target. Select surfaces with `--tag @web` or `--tag @admin`, and exact scenarios with native `--grep` arguments. Install Chromium once when needed with `npx playwright install chromium`. See [Repo Scripts](/reference/repo-scripts#ssr-browser-checks) for the complete command variants.
145
145
 
146
146
  ## 7. Next steps for framework-aware development
147
147
 
@@ -63,10 +63,8 @@ Cabloy Start exposes the equivalent Start repository surface:
63
63
  - `npm run start:one`
64
64
  - `npm run test`
65
65
  - `npm run db:reset`
66
- - `npm run test:e2e:start`
67
- - `npm run test:e2e:start:web`
68
- - `npm run test:e2e:start:admin`
69
- - `npm run test:e2e:start:clean`
66
+ - `npm run test:e2e`
67
+ - `npm run test:e2e:fast`
70
68
  - `npm run tsc`
71
69
 
72
70
  Cabloy Start does not expose Basic Commerce or root documentation wrappers.
@@ -96,10 +94,10 @@ The Start E2E baseline is maintained in the private repository:
96
94
  ```text
97
95
  repo-e2e/config/
98
96
  repo-e2e/scripts/
99
- repo-e2e/specs/cabloy-start/
97
+ repo-e2e/specs/
100
98
  ```
101
99
 
102
- The public-package upgrade flow does not source or reconcile the Start baseline, its `test:e2e:start*` scripts, or `@playwright/test`. Keep project browser tests outside the baseline paths, for example under `repo-e2e/specs/my-project/`.
100
+ The public-package upgrade flow does not source or reconcile the private Start baseline, its root E2E scripts, or `@playwright/test`. Keep project browser tests in the flat `repo-e2e/specs/` directory under distinct filenames, for example `repo-e2e/specs/my-project.spec.ts`.
103
101
 
104
102
  ## SSR browser checks
105
103
 
@@ -158,24 +156,24 @@ The Start suite exercises Web at `/` and Admin at `/admin` through Vona's SSR di
158
156
  ```bash
159
157
  npm run build:zova
160
158
  npm run deps:vona
161
- npm run test:e2e:start:clean
159
+ npm run test:e2e
162
160
  ```
163
161
 
164
162
  ```bash
165
163
  # Exact acceptance scenario
166
- npm run test:e2e:start -- --grep ATP-START-FLOW-01
164
+ npm run test:e2e:fast cabloy-start -- --grep ATP-START-FLOW-01
167
165
 
168
166
  # Category or surface selection
169
- npm run test:e2e:start:clean -- --grep @smoke
170
- npm run test:e2e:start:clean -- --grep @admin
167
+ npm run test:e2e:fast cabloy-start -- --tag @smoke
168
+ npm run test:e2e:fast cabloy-admin -- --tag @admin --tag @cabloy-admin
171
169
  ```
172
170
 
173
- For an externally managed Start target, set `START_E2E_BASE_URL` and use matching aggregate, surface, or forwarded-tag commands. The target owner is responsible for data, cache, and artifact freshness:
171
+ 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:
174
172
 
175
173
  ```bash
176
- START_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:start
177
- START_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:start:admin
178
- START_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:start -- --grep @web
174
+ E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast
175
+ E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-admin -- --tag @admin
176
+ E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-start -- --tag @web
179
177
  ```
180
178
 
181
179
  Browser commands consume existing SSR and REST artifacts; they never rebuild them. Install Chromium once when needed with `npx playwright install chromium`.
@@ -1,4 +1,4 @@
1
- import type { Locator, Page } from '@playwright/test';
1
+ import type { Locator, Page, Route } from '@playwright/test';
2
2
 
3
3
  import { expect, test } from '@playwright/test';
4
4
 
@@ -56,6 +56,39 @@ function waitForStudentSelect(page: Page) {
56
56
  });
57
57
  }
58
58
 
59
+ function installStudentCreateResponseCapture(page: Page) {
60
+ let studentId: string | number | undefined;
61
+ const handler = async (route: Route) => {
62
+ const request = route.request();
63
+ const url = new URL(request.url());
64
+ if (
65
+ request.method() !== 'POST' ||
66
+ url.pathname !== '/api/training/student' ||
67
+ request.headers()['x-vona-openapi-schema']
68
+ ) {
69
+ await route.continue();
70
+ return;
71
+ }
72
+ const response = await route.fetch();
73
+ const payload = await response.json();
74
+ if (response.ok() && ['string', 'number'].includes(typeof payload.data)) {
75
+ studentId = payload.data;
76
+ }
77
+ await route.fulfill({ response });
78
+ };
79
+ return {
80
+ async install() {
81
+ await page.route('**/api/training/student', handler);
82
+ },
83
+ async uninstall() {
84
+ await page.unroute('**/api/training/student', handler);
85
+ },
86
+ get studentId() {
87
+ return studentId;
88
+ },
89
+ };
90
+ }
91
+
59
92
  async function getFieldGeometry(locator: Locator): Promise<IFieldGeometry> {
60
93
  return locator.evaluate(element => {
61
94
  const field = element.closest('label')?.parentElement;
@@ -348,3 +381,145 @@ test(
348
381
  expect(pageErrors).toEqual([]);
349
382
  },
350
383
  );
384
+
385
+ test(
386
+ 'ATP-BASIC-FORM-01: Training Student content remains in the Basic Information tab',
387
+ { tag: ['@admin', '@flow'] },
388
+ async ({ page }) => {
389
+ const pageErrors = collectPageErrors(page);
390
+ await loginAsAdmin(page);
391
+
392
+ const response = await page.goto('/admin/rest/resource/training-student%3Astudent/create', {
393
+ waitUntil: 'load',
394
+ });
395
+ expect(response?.ok()).toBeTruthy();
396
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
397
+
398
+ const basicInformationTab = page.getByRole('tab', { name: 'Basic Information', exact: true });
399
+ const studentContentGroup = page
400
+ .locator('fieldset')
401
+ .filter({ has: page.locator('legend').filter({ hasText: /^Student Content$/ }) });
402
+ await expect(studentContentGroup).toHaveCount(1);
403
+ await expect(
404
+ studentContentGroup.getByText('Description Markdown', { exact: true }),
405
+ ).toBeVisible();
406
+
407
+ const studentContentPanel = studentContentGroup.locator('xpath=ancestor::*[@role="tabpanel"]');
408
+ await expect(studentContentPanel).toHaveCount(1);
409
+ const basicInformationTabId = await basicInformationTab.getAttribute('id');
410
+ expect(basicInformationTabId).not.toBeNull();
411
+ await expect(studentContentPanel).toHaveAttribute('aria-labelledby', basicInformationTabId!);
412
+ expect(pageErrors).toEqual([]);
413
+ },
414
+ );
415
+
416
+ test(
417
+ 'ATP-BASIC-SUMMARY-01: Training Student summary renders Markdown HTML in a dialog',
418
+ { tag: ['@admin', '@flow'] },
419
+ async ({ page }) => {
420
+ const pageErrors = collectPageErrors(page);
421
+ await loginAsAdmin(page);
422
+
423
+ const studentName = `Summary E2E ${Date.now()}`;
424
+ const createResponseCapture = installStudentCreateResponseCapture(page);
425
+ let studentId: string | number | undefined;
426
+ await createResponseCapture.install();
427
+ try {
428
+ const createPageResponse = await page.goto(
429
+ '/admin/rest/resource/training-student%3Astudent/create',
430
+ { waitUntil: 'load' },
431
+ );
432
+ expect(createPageResponse?.ok()).toBeTruthy();
433
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
434
+ await page
435
+ .getByRole('group', { name: 'Student Name *', exact: true })
436
+ .getByRole('textbox')
437
+ .fill(studentName);
438
+ await page
439
+ .getByRole('group', { name: 'Mobile *', exact: true })
440
+ .getByRole('textbox')
441
+ .fill('13812345678');
442
+ await page
443
+ .locator('.ProseMirror')
444
+ .last()
445
+ .fill(`## Summary heading\n\nSummary paragraph ${studentName}`);
446
+ await page.getByRole('tab', { name: 'Student Training Records', exact: true }).click();
447
+ await page.getByText('Foundation Track', { exact: true }).click();
448
+
449
+ const submitButton = page.getByRole('button', { name: 'Submit', exact: true });
450
+ await expect(submitButton).toBeEnabled();
451
+ await submitButton.click();
452
+ await expect(page).toHaveURL(/\/admin\/(?:\?|$)/);
453
+ studentId = createResponseCapture.studentId;
454
+ expect(['string', 'number']).toContain(typeof studentId);
455
+
456
+ const response = await page.goto('/admin/rest/resource/training-student%3Astudent', {
457
+ waitUntil: 'load',
458
+ });
459
+ expect(response?.ok()).toBeTruthy();
460
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
461
+
462
+ const filterName = page.getByLabel('Student Name');
463
+ await expect(filterName).toBeVisible();
464
+ await filterName.fill(studentName);
465
+ const searchResponse = waitForStudentSelect(page);
466
+ await page.getByRole('button', { name: 'Search', exact: true }).click();
467
+ await searchResponse;
468
+
469
+ const row = page.locator('tr').filter({ hasText: studentName });
470
+ await expect(row).toHaveCount(1);
471
+ const summaryResponse = page.waitForResponse(response => {
472
+ const url = new URL(response.url());
473
+ return (
474
+ response.request().method() === 'GET' &&
475
+ response.ok() &&
476
+ url.pathname === `/api/training/student/summary/${studentId}` &&
477
+ !response.request().headers()['x-vona-openapi-schema']
478
+ );
479
+ });
480
+ await row.getByRole('button', { name: 'Summary', exact: true }).click();
481
+ await summaryResponse;
482
+
483
+ const description = page.locator('.student-summary-description');
484
+ await expect(description).toBeVisible();
485
+ await expect(description.locator('h2')).toHaveText('Summary heading');
486
+ await expect(description).toContainText(`Summary paragraph ${studentName}`);
487
+ expect(pageErrors).toEqual([]);
488
+ } finally {
489
+ try {
490
+ if (studentId !== undefined) {
491
+ const description = page.locator('.student-summary-description');
492
+ if (await description.count()) {
493
+ await description.locator('xpath=../..').getByRole('button').click();
494
+ }
495
+ const row = page.locator('tr').filter({ hasText: studentName });
496
+ if (!(await row.count())) {
497
+ const response = await page.goto('/admin/rest/resource/training-student%3Astudent', {
498
+ waitUntil: 'load',
499
+ });
500
+ expect(response?.ok()).toBeTruthy();
501
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
502
+ await page.getByLabel('Student Name').fill(studentName);
503
+ const searchResponse = waitForStudentSelect(page);
504
+ await page.getByRole('button', { name: 'Search', exact: true }).click();
505
+ await searchResponse;
506
+ }
507
+ const deleteResponse = page.waitForResponse(response => {
508
+ const url = new URL(response.url());
509
+ return (
510
+ response.request().method() === 'DELETE' &&
511
+ response.ok() &&
512
+ url.pathname === `/api/training/student/deleteForce/${studentId}` &&
513
+ !response.request().headers()['x-vona-openapi-schema']
514
+ );
515
+ });
516
+ await row.getByRole('button', { name: 'Force Delete', exact: true }).click();
517
+ await page.getByRole('button', { name: 'Yes', exact: true }).click();
518
+ await deleteResponse;
519
+ }
520
+ } finally {
521
+ await createResponseCapture.uninstall();
522
+ }
523
+ }
524
+ },
525
+ );
@@ -46,6 +46,7 @@
46
46
  "title": "training-student",
47
47
  "vonaModule": {
48
48
  "dependencies": {
49
+ "a-markdown": "5.0.0",
49
50
  "a-vona": "5.0.0"
50
51
  },
51
52
  "fileVersion": 1