cabloy 5.1.163 → 5.1.164
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/.cabloy-version +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/repo-docs/backend/shared-rbac-architecture.md +4 -0
- package/repo-docs/blogs/ai-spec-driven-development/cover-en-v1.png +0 -0
- package/repo-docs/blogs/ai-spec-driven-development/implementation-burndown.png +0 -0
- package/repo-docs/blogs/ai-spec-driven-development/implementation-gantt.png +0 -0
- package/repo-docs/blogs/ai-spec-driven-development/index.md +217 -0
- package/repo-docs/blogs/index.md +15 -0
- package/repo-docs/editions/overview.md +2 -0
- package/repo-docs/frontend/environment-config-guide.md +43 -10
- package/repo-docs/frontend/resource-list-page-deep-dive.md +14 -1
- package/repo-docs/frontend/table-action-visibility-permission-flow-guide.md +20 -0
- package/repo-docs/frontend/table-guide.md +42 -0
- package/repo-docs/frontend/table-resource-crud-cookbook.md +25 -1
- package/repo-e2e/docs/specs/blogs-index.spec.ts +45 -9
- package/repo-e2e/specs/cabloy-basic.spec.ts +246 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +17 -6
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}DeleteBulk.tsx_ +14 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}SelectResItem.tsx_ +7 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/service/{{resourceName}}.ts_ +6 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +12 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +12 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +8 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}DeleteBulk.tsx_ +14 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}SelectResItem.tsx_ +7 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/service/{{resourceName}}.ts_ +6 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +1 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +1 -0
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +9 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +9 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentDeleteBulk.tsx +14 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +7 -1
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +6 -0
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +114 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/.metadata/index.ts +24 -3
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/.metadata/locales.ts +18 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/bean/bean.rbacResourceBulk.ts +50 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/bean/index.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/config/errors.ts +18 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/config/locale/en-us.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/config/locale/zh-cn.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/test/resourceBulk.test.ts +128 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/create/component/boilerplateTableActionBulk/controller.tsx_ +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +16 -12
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +66 -7
- package/zova/src/suite/a-training/modules/training-student/src/api/trainingStudent.ts +18 -0
- package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-commands/src/.metadata/index.ts +16 -0
- package/zova/src/suite/cabloy-basic/modules/basic-commands/src/bean/command.deleteBulk.tsx +35 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +143 -7
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +3 -2
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockToolbarBulk/controller.tsx +76 -4
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/config/locale/en-us.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/config/locale/zh-cn.ts +6 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/lib/selection.ts +84 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/test/lib/selection.test.ts +99 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/component/actionDeleteBulk.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionCreate/controller.tsx +5 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionDeleteBulk/controller.tsx +57 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +35 -7
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/en-us.ts +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/zh-cn.ts +4 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/lib/selectedIds.ts +15 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +57 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/lib/selectedIds.test.ts +20 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-command/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-command/src/lib/beanCommandSelectedBase.ts +33 -0
- package/zova/src/suite-vendor/a-zova/modules/a-command/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-command/src/types/command.ts +5 -0
- package/zova/src/suite-vendor/a-zova/modules/a-command/test/lib/beanCommandSelectedBase.test.ts +47 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +3 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/tableActionBulk.ts +19 -0
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +86 -26
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx +30 -6
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/types/table.ts +2 -0
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.164
|
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ For fuller guidance on choosing an edition and working in an existing checkout,
|
|
|
74
74
|
| E-commerce | A complete Admin site, Web site, personal center, payment flows, Markdown editing, and related capabilities. | ✅ | — |
|
|
75
75
|
| System Management | User management, role management, department management, RBAC authorization, and menu authorization. | — | ✅ |
|
|
76
76
|
|
|
77
|
+
Included demonstration suites give AI vibe coding agents high-quality, project-native code examples. They improve development efficiency while reducing token use. For production deployments, set `PROJECT_DISABLED_SUITES` to a comma-separated list of unneeded suite names to disable them; see the [Environment and Config Guide](https://cabloy.com/frontend/environment-config-guide#built-in-env-variables).
|
|
78
|
+
|
|
77
79
|
> “Not included” means that the suite is not part of the default edition baseline. It does not limit what can be built with Cabloy.
|
|
78
80
|
|
|
79
81
|
## Highlights
|
package/package.json
CHANGED
|
@@ -129,6 +129,10 @@ After guard admission, a controller must consume the current typed scope. The co
|
|
|
129
129
|
| Bulk mutation | `checkEntries(entries)` | Ensure every intended target is inside scope |
|
|
130
130
|
| Create | `ownerValues()` | Derive trusted owner/scope fields instead of accepting widening input |
|
|
131
131
|
|
|
132
|
+
For an atomic multi-ID Resource command, have the command DTO validate its request-list maximum and use `a-rbac` to validate that the list is nonempty and has no duplicate normalized identities. Then load every requested record through the ordinary instance-scoped model boundary. Require complete one-for-one resolution before calling `checkEntries(entries)` and before any mutation. This treats missing, soft-deleted, and other-instance records uniformly as absent; do not use an unscoped query merely to distinguish them. Run the full preflight and mutation inside the domain transaction.
|
|
133
|
+
|
|
134
|
+
`a-rbac` exposes `this.bean.rbacResourceBulk.entries(...)` for the reusable integrity and authorization sequence. Pass the facade a fresh, ordinary instance-scoped loader (rather than a cache-backed lookup) from the domain transaction. It preserves the requested order, invokes `checkEntries(...)` only after complete normal-scoped loading, and emits localized `a-rbac` scoped errors for invalid ID lists and absent entries.
|
|
135
|
+
|
|
132
136
|
A representative list shape is:
|
|
133
137
|
|
|
134
138
|
```typescript
|
|
Binary file
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts
|
|
3
|
+
titleZh: AI Spec 驱动开发:用 CabloyJS 的两个 Skills 把 Spec 变成可核验交付
|
|
4
|
+
titleEn: CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts
|
|
5
|
+
subtitle: From specification generation to bounded execution, see how CabloyJS helps AI work within explicit business boundaries, technical contracts, and acceptance evidence.
|
|
6
|
+
summary: CabloyJS provides two complementary AI Skills—cabloy-spec-generation and cabloy-spec-execution. The first establishes a suite’s specification and traceability authority; the second delivers one confirmed WBS increment and records verification evidence. Using a-commerce as an example, this article explains the generated Spec files, the two derived charts, and how they connect.
|
|
7
|
+
tags:
|
|
8
|
+
- AI Spec-Driven Development
|
|
9
|
+
- AI Coding
|
|
10
|
+
- Spec-Driven Development
|
|
11
|
+
- Cabloy
|
|
12
|
+
- Vona
|
|
13
|
+
- Zova
|
|
14
|
+
- Software Architecture
|
|
15
|
+
- Fullstack
|
|
16
|
+
slug: ai-spec-driven-development-en
|
|
17
|
+
cover: ./cover-en-v1.png
|
|
18
|
+
date: 2026-09-11
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts
|
|
22
|
+
|
|
23
|
+
The easiest way to start AI coding is to describe a page or an API in a chat window. But once a project has multiple modules, multiple audiences, and an evolving frontend-backend contract, the genuinely difficult questions become: where does this change belong? Which boundaries have already been confirmed? How can we prove that it is actually complete?
|
|
24
|
+
|
|
25
|
+
CabloyJS includes two complementary Claude Code Skills that divide the work into two phases: first establish specification authority inside the repository, then execute a bounded delivery increment. This article introduces the two Skills, explains the Spec files they produce and consume, and uses real charts from the built-in `a-commerce` suite to show the result.
|
|
26
|
+
|
|
27
|
+
## Meet the two Skills first
|
|
28
|
+
|
|
29
|
+
The two Skills are invoked as follows:
|
|
30
|
+
|
|
31
|
+
- `cabloy-spec-generation`: creates or maintains the suite planning records under `repo-specs/<suite>/`, establishing traceability among product, technical, delivery, and acceptance concerns.
|
|
32
|
+
- `cabloy-spec-execution`: works from an existing specification to execute one explicit `WBS-*` item—or a finite, approved phase with a closed boundary—and records the actual verification results.
|
|
33
|
+
|
|
34
|
+
The following table summarizes the difference:
|
|
35
|
+
|
|
36
|
+
| Dimension | `cabloy-spec-generation` | `cabloy-spec-execution` |
|
|
37
|
+
| ------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| Problem addressed | What should be built, and how should boundaries and acceptance be recorded? | How should one approved item be implemented and proven complete? |
|
|
39
|
+
| Input | A new suite description or a request to maintain an existing specification | An explicit `WBS-*` item, or a finite and approved phase |
|
|
40
|
+
| Primary outputs | PRD, SRS, PDP/WBS, ATP, ADRs, derived status, and charts | Source changes, test results, Evidence, derived progress, and charts |
|
|
41
|
+
| Confirmation gate | Confirms identity, scope, topology, site strategy, and more before planning records are written | Confirms the execution dossier, dependencies, scope, commands, and evidence requirements before implementation |
|
|
42
|
+
| What it does not do | Does not write business source code or treat planning as implementation | Does not invent requirements, replace specification generation, or automatically expand into adjacent tasks |
|
|
43
|
+
|
|
44
|
+
### 1. Establish a specification baseline with generation
|
|
45
|
+
|
|
46
|
+
In Claude Code, begin with a business description:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
/cabloy-spec-generation Plan an equipment-maintenance suite for field technicians and operations managers: include equipment records, work orders, asset history, role-based access control, and an Admin dashboard.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The Skill first performs read-only discovery and asks for missing inputs. Its typical steps are:
|
|
53
|
+
|
|
54
|
+
1. inspect the repository root, `package.json`, `CLAUDE.md`, and the edition marker;
|
|
55
|
+
2. distinguish a new suite from an extension of an existing suite, and check whether `repo-specs/<suite>/` already exists;
|
|
56
|
+
3. independently evaluate whether Web and Admin should reuse an existing site or use a dedicated site;
|
|
57
|
+
4. collect product goals, roles, scope, module topology, tenancy and authorization, states, transactions, verification, and release constraints;
|
|
58
|
+
5. show a generation confirmation gate before writing planning records;
|
|
59
|
+
6. generate files in authority order, run exact-ID traceability checks, and produce two derived charts.
|
|
60
|
+
|
|
61
|
+
### 2. Execute a bounded increment with execution
|
|
62
|
+
|
|
63
|
+
Once the specification is ready and a task’s dependencies are satisfied, invoke execution:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/cabloy-spec-execution WBS-40-03
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`WBS-40-03` is only an example. A real project should use an ID that actually exists in the target suite. The AI then follows this process:
|
|
70
|
+
|
|
71
|
+
1. read `README.md`, the PRD, SRS, applicable ADRs, the complete WBS, ATP/test plan, progress, Evidence, and derived charts;
|
|
72
|
+
2. check the edition, current revision, workspace state, dependencies, blockers, unresolved `TODO`s, and whether previous evidence is still valid;
|
|
73
|
+
3. prepare an execution dossier listing the objective, scope, exclusions, authority IDs, source ownership, specialist-Skill routing, verification commands, and evidence format;
|
|
74
|
+
4. obtain explicit confirmation before routing into backend, frontend, or Contract Loop specialist workflows;
|
|
75
|
+
5. run the narrowest meaningful check first, followed by the ATP-required verification;
|
|
76
|
+
6. save Evidence containing the revision, environment, steps, results, and locations of sanitized artifacts;
|
|
77
|
+
7. update derived `progress.md`, regenerate and check the charts, and leave one unambiguous next action.
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
/cabloy-spec-execution Execute the next task in the equipment-maintenance suite
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If the next incremental WBS ID is unknown, ask the AI to first read the suite’s `progress.md` and combine it with the WBS, ATP, and existing Evidence to list executable `WBS-*` IDs whose dependencies are satisfied. The user can then confirm the specific item to execute next.
|
|
84
|
+
|
|
85
|
+
## What generation produces: a connected set of Spec files
|
|
86
|
+
|
|
87
|
+
For a long-lived business suite, the default core output of `cabloy-spec-generation` is located at:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
repo-specs/<suite>/
|
|
91
|
+
├── README.md
|
|
92
|
+
├── prd.md
|
|
93
|
+
├── srs.md
|
|
94
|
+
├── pdp-wbs.md
|
|
95
|
+
├── test-plan.md
|
|
96
|
+
├── progress.md
|
|
97
|
+
├── implementation-gantt.svg
|
|
98
|
+
├── implementation-burndown.svg
|
|
99
|
+
└── decisions/
|
|
100
|
+
└── 0001-<suite-boundary-slug>.md
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This is not simply one large document split into several files. It is a model of **separated authority**: each record owns one kind of fact, while the others refer to it through stable IDs and links.
|
|
104
|
+
|
|
105
|
+
| File | Facts it owns | What it should not replace |
|
|
106
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
107
|
+
| `README.md` | Index, reading order, suite baseline, topology summary, and authority map | Detailed PRD, SRS, or WBS content |
|
|
108
|
+
| `prd.md` | Product goals, roles, scope, journeys, business rules, product requirements, and product acceptance | Table names, DTO syntax, routes, or test commands |
|
|
109
|
+
| `srs.md` | Technical contracts; data and capability ownership; tenancy; identity; authorization; states; transactions; concurrency; APIs/DTOs; SSR; and non-functional boundaries | API authority or authorization determined by a UI layout |
|
|
110
|
+
| `pdp-wbs.md` | Delivery phases, WBS tasks, dependencies, completion checks, and delivery traceability | Upstream product requirements or technical contracts |
|
|
111
|
+
| `test-plan.md` | ATP acceptance scenarios, test levels, fixtures, verification procedures, Evidence format, and release gates | Planned commands presented as results that have already passed |
|
|
112
|
+
| `progress.md` | WBS state, blockers, Evidence pointers, decisions, and the next proof | Requirements, contracts, or acceptance rules |
|
|
113
|
+
| `decisions/*.md` | Long-lived scope, architecture, security, ownership, or integration decisions | A duplicate SRS or status log |
|
|
114
|
+
| `implementation-gantt.svg` | A WBS-derived view of phases, task order, dependencies, and status | Planning authority, date commitments, or duration commitments |
|
|
115
|
+
| `implementation-burndown.svg` | A scope-and-status-derived count of remaining items | A velocity trend or forecast when no historical snapshots exist |
|
|
116
|
+
|
|
117
|
+
## From business intent to evidence: how the files connect
|
|
118
|
+
|
|
119
|
+
The core traceability chain is:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
PRD requirement
|
|
123
|
+
↓
|
|
124
|
+
SRS contract
|
|
125
|
+
↓
|
|
126
|
+
PDP/WBS task
|
|
127
|
+
↓
|
|
128
|
+
ATP scenario
|
|
129
|
+
↓
|
|
130
|
+
observed Evidence
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
It can also be written more compactly:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
PRD → SRS → WBS → ATP → Evidence
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For example, a product requirement that “an authorized operator can view orders in the current tenant” must be made concrete in the SRS as identity, tenancy, authorization, data ownership, and API contracts. The WBS then defines the implementation boundary and dependencies; the ATP provides executable happy-path, unauthorized, and cross-tenant scenarios; finally, the actual runtime observations are saved as sanitized evidence.
|
|
140
|
+
|
|
141
|
+
When a task is executed, the document-state update order is:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
Upstream authority changes
|
|
145
|
+
→ traceability matrix
|
|
146
|
+
→ WBS dependencies and completion checks
|
|
147
|
+
→ ATP procedures and expected Evidence
|
|
148
|
+
→ progress / Evidence
|
|
149
|
+
→ two derived charts
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`progress.md` and both SVGs are downstream derived outputs. They help people and AI understand the current state quickly.
|
|
153
|
+
|
|
154
|
+
## How to read the two charts
|
|
155
|
+
|
|
156
|
+
The charts are automatically generated by built-in scripts from `pdp-wbs.md`, `test-plan.md`, and `progress.md`.
|
|
157
|
+
|
|
158
|
+
### Gantt: inspect delivery order, not a calendar schedule
|
|
159
|
+
|
|
160
|
+
The Gantt chart reads the formal phases, WBS tasks, dependencies, and progress statuses. It is useful for answering: what delivery packages exist? What is their approximate order? Which tasks depend on preceding phases?
|
|
161
|
+
|
|
162
|
+
The current chart for the built-in `a-commerce` suite contains 23 formal WBS tasks, all shown as 23/23 `verified`.
|
|
163
|
+
|
|
164
|
+

|
|
165
|
+
|
|
166
|
+
### Burndown: inspect scope counts, not velocity forecasts
|
|
167
|
+
|
|
168
|
+
In the current `a-commerce` snapshot, the Burndown chart shows 23 active WBS items, 23 `verified`, 0 remaining, and 0 deferred.
|
|
169
|
+
|
|
170
|
+

|
|
171
|
+
|
|
172
|
+
## When this approach is worthwhile
|
|
173
|
+
|
|
174
|
+
This approach is best suited to enterprise systems that evolve over time, span modules, involve multiple collaborators, and include both Vona backend and Zova Admin/Web/SSR work—especially where tenancy, authorization, transactions, concurrency, auditability, or external integrations are important.
|
|
175
|
+
|
|
176
|
+
For a one-off page, a simple website, or a very small utility, a lightweight prompt-to-code workflow may be more economical.
|
|
177
|
+
|
|
178
|
+
## Start with a small increment
|
|
179
|
+
|
|
180
|
+
Choose one clearly bounded capability rather than trying to plan an entire system at once:
|
|
181
|
+
|
|
182
|
+
1. Use generation to plan the requirement.
|
|
183
|
+
- Record goals, scope, and exclusions.
|
|
184
|
+
- Confirm technical contracts, ownership, dependencies, and ATP.
|
|
185
|
+
2. Use execution to deliver one WBS ID.
|
|
186
|
+
- Run applicable verification and save sanitized Evidence.
|
|
187
|
+
- Update progress and the derived charts.
|
|
188
|
+
3. Proceed to the next increment.
|
|
189
|
+
|
|
190
|
+
Create a Cabloy project from the public scaffolding:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npm create cabloy
|
|
194
|
+
npm run dev
|
|
195
|
+
npm run dev:zova:admin
|
|
196
|
+
npm run dev:zova:web
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Conclusion
|
|
200
|
+
|
|
201
|
+
AI Spec-Driven Development in CabloyJS is not another way of saying “prompts become code.” It is a more concrete delivery path:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
Business intent → specification authority → bounded WBS → acceptance procedure → observed Evidence
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`cabloy-spec-generation` organizes intent into a traceable planning chain. `cabloy-spec-execution` advances one increment within confirmed boundaries. Together, they reduce the AI’s need to guess about business scope, technical ownership, and the definition of done.
|
|
208
|
+
|
|
209
|
+
## Further reading
|
|
210
|
+
|
|
211
|
+
- [AI Spec-Driven Development](https://cabloy.com/ai/ai-spec-driven-development)
|
|
212
|
+
- [Generate a Cabloy Suite Specification](https://cabloy.com/ai/playbook-spec-generation)
|
|
213
|
+
- [Execute an Approved Cabloy Specification Increment](https://cabloy.com/ai/playbook-spec-execution)
|
|
214
|
+
- [Verification](https://cabloy.com/ai/verification)
|
|
215
|
+
- [Contract Loop Playbook](https://cabloy.com/fullstack/contract-loop-playbook)
|
|
216
|
+
- [Vona + Zova Integration](https://cabloy.com/fullstack/vona-zova-integration)
|
|
217
|
+
- [Cabloy Fullstack Quick Start Tutorials](https://cabloy.com/fullstack/tutorials-overview)
|
package/repo-docs/blogs/index.md
CHANGED
|
@@ -9,6 +9,21 @@ aside: false
|
|
|
9
9
|
Selected articles on Cabloy, Vona, Zova, and the architectural questions that shape full-stack applications.
|
|
10
10
|
|
|
11
11
|
<div class="cabloy-blog-grid">
|
|
12
|
+
<article class="cabloy-blog-card">
|
|
13
|
+
<a class="cabloy-blog-card__cover" href="/blogs/ai-spec-driven-development/" aria-label="Read CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts">
|
|
14
|
+
<img src="/blogs/ai-spec-driven-development/cover-en-v1.png" alt="CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts" />
|
|
15
|
+
</a>
|
|
16
|
+
<div class="cabloy-blog-card__content">
|
|
17
|
+
<p class="cabloy-blog-card__date">September 11, 2026</p>
|
|
18
|
+
<h2><a href="/blogs/ai-spec-driven-development/">CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts</a></h2>
|
|
19
|
+
<p>Learn how two CabloyJS Skills establish specification authority, execute bounded WBS increments, and derive Gantt and Burndown status views from the same traceability chain.</p>
|
|
20
|
+
<div class="cabloy-blog-card__tags" aria-label="Article topics">
|
|
21
|
+
<span>AI Spec-Driven Development</span><span>AI Coding</span><span>Specification Workflow</span><span>Fullstack</span>
|
|
22
|
+
</div>
|
|
23
|
+
<a class="cabloy-blog-card__read" href="/blogs/ai-spec-driven-development/">Read article <span aria-hidden="true">→</span></a>
|
|
24
|
+
</div>
|
|
25
|
+
</article>
|
|
26
|
+
|
|
12
27
|
<article class="cabloy-blog-card">
|
|
13
28
|
<a class="cabloy-blog-card__cover" href="/blogs/nextjs-integrated-fullstack-cabloy-contract-loop/" aria-label="Read Next.js Integrated Full Stack and Cabloy’s Bidirectional Contract Loop">
|
|
14
29
|
<img src="/blogs/nextjs-integrated-fullstack-cabloy-contract-loop/cover-en-v1.png" alt="Next.js Integrated Full Stack and Cabloy’s Bidirectional Contract Loop" />
|
|
@@ -33,6 +33,8 @@ Both editions share Vona + Zova, suite-based modular delivery, CLI-first workflo
|
|
|
33
33
|
| E-commerce | A complete Admin site, Web site, personal center, payment flows, Markdown editing, and related capabilities. | ✅ | — |
|
|
34
34
|
| System Management | User management, role management, department management, RBAC authorization, and menu authorization. | — | ✅ |
|
|
35
35
|
|
|
36
|
+
Included demonstration suites give AI vibe coding agents high-quality, project-native code examples. They improve development efficiency while reducing token use. For production deployments, set `PROJECT_DISABLED_SUITES` to a comma-separated list of unneeded suite names to disable them; see the [Environment and Config Guide](https://cabloy.com/frontend/environment-config-guide#built-in-env-variables).
|
|
37
|
+
|
|
36
38
|
> “Not included” means that the suite is not part of the default edition baseline. It does not limit what can be built with Cabloy.
|
|
37
39
|
|
|
38
40
|
## Choosing an edition
|
|
@@ -196,16 +196,49 @@ This distinction is central to writing Zova code that behaves correctly across b
|
|
|
196
196
|
|
|
197
197
|
## Built-in env variables
|
|
198
198
|
|
|
199
|
-
Zova
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
Zova provides the following built-in environment variables. Configure them in `zova/env/.env` or in an applicable meta-specific env file.
|
|
200
|
+
|
|
201
|
+
### App identity and presentation
|
|
202
|
+
|
|
203
|
+
`SITE_ID`, `APP_NAME`, `APP_TITLE`, `APP_DESCRIPTION`, `APP_VERSION`, `APP_META_VIEWPORT`, `APP_PUBLIC_PATH`, `APP_LOCALE_DEFAULT`, `APP_LOCALE_HEADER_KEY`, `APP_TZ_HEADER_KEY`
|
|
204
|
+
|
|
205
|
+
### Router settings
|
|
206
|
+
|
|
207
|
+
`ROUTER_MODE`, `ROUTER_PAGE_HOME`, `ROUTER_PAGE_LOGIN`, `ROUTER_KEY_RETURNTO`
|
|
208
|
+
|
|
209
|
+
### Development-server settings
|
|
210
|
+
|
|
211
|
+
`DEV_SERVER_HOSTNAME`, `DEV_SERVER_PORT`, `DEV_SERVER_HMR_PORT`
|
|
212
|
+
|
|
213
|
+
### Project-disabled suites and modules
|
|
214
|
+
|
|
215
|
+
`PROJECT_DISABLED_SUITES`, `PROJECT_DISABLED_MODULES`
|
|
216
|
+
|
|
217
|
+
`PROJECT_DISABLED_SUITES` disables entire suites, including every module in each disabled suite. `PROJECT_DISABLED_MODULES` disables individual modules without disabling their containing suite. Both accept comma-separated relative names.
|
|
218
|
+
|
|
219
|
+
For example, a production deployment that does not need the e-commerce demonstration suite can set:
|
|
220
|
+
|
|
221
|
+
```dotenv
|
|
222
|
+
PROJECT_DISABLED_SUITES=a-commerce
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Build output settings
|
|
226
|
+
|
|
227
|
+
`LOGGER_CLIENT_DEFAULT`, `BUILD_OUTDIR`, `BUILD_MINIFY`, `BUILD_SOURCEMAP`, `BUILD_TARGET_BROWSER`, `BUILD_TARGET_NODE`, `BUILD_ANALYZE`, `BUILD_COPY_DIST`, `BUILD_COPY_RELEASE`, `BUILD_REST_COPY_DIST`
|
|
228
|
+
|
|
229
|
+
### API and proxy configuration
|
|
230
|
+
|
|
231
|
+
`API_BASE_URL`, `API_PREFIX`, `API_JWT`, `OPENAPI_BASE_URL_DEFAULT`, `OPENAPI_BASE_URL_<MODULE>`, `PROXY_API_ENABLED`, `PROXY_API_BASE_URL`, `PROXY_API_PREFIX`
|
|
232
|
+
|
|
233
|
+
Use `OPENAPI_BASE_URL_<MODULE>` to override the OpenAPI base URL for a specific module. Replace `<MODULE>` with the module's uppercase underscore-separated name, such as `OPENAPI_BASE_URL_HOME_API`.
|
|
234
|
+
|
|
235
|
+
### SSR-specific values
|
|
236
|
+
|
|
237
|
+
`SSR_API_BASE_URL`, `SSR_PROD_PORT`, `SSR_PROD_PROTOCOL`, `SSR_PROD_HOST`, `SSR_WITH_VONA`, `SSR_PROFILE`, `SSR_PROFILE_PUBLIC_RESPONSE_CACHE_EXPIRES`, `SSR_PROFILE_SESSION_RESPONSE_CACHE_EXPIRES`, `SSR_COOKIE_THEMEDARK_DEFAULT`
|
|
238
|
+
|
|
239
|
+
### Mock-server configuration
|
|
240
|
+
|
|
241
|
+
`MOCK_ENABLED`, `MOCK_LOGGER`, `MOCK_BASE_NAME`, `MOCK_BUILD`, `MOCK_BUILD_PORT`, `MOCK_BUILD_OUTPUT`, `MOCK_BUILD_CORS`
|
|
209
242
|
|
|
210
243
|
That means many common project-level knobs already exist and should be reused before inventing project-specific patterns.
|
|
211
244
|
|
|
@@ -151,6 +151,15 @@ This controller becomes the deeper owner for:
|
|
|
151
151
|
- `schemaFilter`, `schemaRow`, and `permissions`
|
|
152
152
|
- the shared page JSX/render context (`$$page`)
|
|
153
153
|
- permission-driven table-meta refresh through `tableRef.refreshMeta()`
|
|
154
|
+
- transient selected IDs and snapshots of selected loaded rows
|
|
155
|
+
|
|
156
|
+
### Page-owned row selection
|
|
157
|
+
|
|
158
|
+
Selection is page-local interaction state, not `ModelResource` cache state. `blockPage` keeps canonical selected IDs and loaded-row snapshots, while `ZTable` receives only a controlled TanStack selection map for the current page.
|
|
159
|
+
|
|
160
|
+
Consequently, normal paging and same-query refetches preserve selected IDs and refresh a snapshot when its row reappears. The header checkbox selects or clears only **currently loaded** rows; it never selects all rows matching a server-side filter. Selection clears when the result universe materially changes: fixed query, submitted/reset filter, sort, page size, resource identity, or a successful selected-row mutation. Page-size changes also return to page 1, and **Done** clears selection before hiding the selection column.
|
|
161
|
+
|
|
162
|
+
Snapshots exist for browser UX only. A selected-row command sends IDs; the backend reloads and authorizes every persisted target through the normal instance-scoped model boundary.
|
|
154
163
|
|
|
155
164
|
This is the second key architectural boundary:
|
|
156
165
|
|
|
@@ -184,8 +193,12 @@ This controller consumes `$$page` and passes the canonical page-owned values int
|
|
|
184
193
|
- `data`
|
|
185
194
|
- `schemaRow`
|
|
186
195
|
- `tableScope`
|
|
196
|
+
- controlled sorting
|
|
197
|
+
- controlled row selection
|
|
198
|
+
|
|
199
|
+
It captures the table controller ref back onto `$$page.tableRef`; it does not configure selection policy.
|
|
187
200
|
|
|
188
|
-
|
|
201
|
+
`blockPage.selectionPolicy` is the page-owned configuration: `undefined` is automatic, `'always'` shows checkboxes immediately, `'onDemand'` exposes **Select / Done** even without a selected action, and `false` prevents selection. With the automatic policy, a sibling bulk-toolbar action that declares `requiresSelection: true` makes **Select / Done** available. The toolbar's action requirement remains independent from page policy.
|
|
189
202
|
|
|
190
203
|
This is the clearest proof that `blockTable` is a bridge into table runtime, not the main owner of list state.
|
|
191
204
|
|
|
@@ -169,6 +169,24 @@ This is the clearest source-confirmed bulk-action rule:
|
|
|
169
169
|
- bulk action visibility is permission-driven
|
|
170
170
|
- it is resolved in the bulk-toolbar block
|
|
171
171
|
|
|
172
|
+
### Selected-action enablement is stricter than discovery
|
|
173
|
+
|
|
174
|
+
A bulk action with `requiresSelection: true` remains visible when its coarse permission check succeeds, but it is disabled unless all of these conditions hold:
|
|
175
|
+
|
|
176
|
+
1. at least one ID is selected;
|
|
177
|
+
2. every selected ID still has a loaded row snapshot; and
|
|
178
|
+
3. `$passport.checkPermission(...)` permits **every** selected snapshot with the action's permission hint.
|
|
179
|
+
|
|
180
|
+
The final condition is intentionally an all-record check. A constrained RBAC projection may allow an action for some rows while denying it for others; a mixed selection must not appear enabled.
|
|
181
|
+
|
|
182
|
+
The bulk action contract separates authored metadata from toolbar runtime props:
|
|
183
|
+
|
|
184
|
+
- DTO authors configure static `requiresSelection`, `selectedMaxIds`, and optional unconditional `disabled` in action `options`.
|
|
185
|
+
- `blockToolbarBulk` supplies current-page runtime values as `dynamicSelection`, `dynamicDisabled`, and `dynamicDisabledReason`.
|
|
186
|
+
- A component disables itself when either static `disabled` or toolbar-owned `dynamicDisabled` is true. Custom action components consume the `dynamic*` values as UI state; command implementations should not depend on them as mutation policy.
|
|
187
|
+
|
|
188
|
+
This is still only a browser projection. Direct selected-row API requests must reload the targets and enforce authorization on the server.
|
|
189
|
+
|
|
172
190
|
## Row action hints vs bulk action hints
|
|
173
191
|
|
|
174
192
|
The permission hint contracts live in:
|
|
@@ -240,6 +258,8 @@ If a list-page action is missing unexpectedly, ask:
|
|
|
240
258
|
5. for row actions, is the operations-row cell bean actually resolving a visible renderer set?
|
|
241
259
|
6. for bulk actions, is the bulk toolbar reading the expected action config?
|
|
242
260
|
7. are you accidentally expecting entry-page `formScene` behavior in a list-page action surface?
|
|
261
|
+
8. for a selected action, is selection nonempty, complete, and permitted for every selected snapshot?
|
|
262
|
+
9. does the backend endpoint independently reload and authorize all submitted IDs?
|
|
243
263
|
|
|
244
264
|
## Where to read next
|
|
245
265
|
|
|
@@ -119,6 +119,48 @@ A practical reading takeaway is:
|
|
|
119
119
|
- **the visible wrapper component is thin**
|
|
120
120
|
- **the controller bean is the real runtime owner**
|
|
121
121
|
|
|
122
|
+
## Row selection and selected actions
|
|
123
|
+
|
|
124
|
+
Row selection is opt-in. Direct `ZTable` use remains unchanged unless it receives the controlled TanStack selection surface:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
<ZTable
|
|
128
|
+
data={this.students}
|
|
129
|
+
schema={this.schemaRow}
|
|
130
|
+
enableRowSelection={true}
|
|
131
|
+
rowSelection={this.rowSelection}
|
|
132
|
+
onRowSelectionChange={updater => {
|
|
133
|
+
this.rowSelection = functionalUpdate(updater, this.rowSelection);
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The framework-owned selection column is not a schema field: it is left-pinned, non-sortable, and uses the stable `getRowId(...)` identity. The header checkbox selects only rows currently loaded in the table; under server pagination it never means every row matching the filter.
|
|
139
|
+
|
|
140
|
+
For standard resource list pages, `basic-page:blockPage` owns selected IDs, loaded row snapshots, and the `selectionPolicy` configuration. Its default `undefined` policy is automatic: a `tableActionBulk` action that opts in with `requiresSelection: true` makes the localized **Select** control available:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
ZovaRender.block('basic-page:blockPage', {
|
|
144
|
+
blocks: [
|
|
145
|
+
ZovaRender.block('basic-page:blockToolbarBulk', {
|
|
146
|
+
actions: [
|
|
147
|
+
ZovaRender.tableActionBulk('basic-table:actionCreate'),
|
|
148
|
+
ZovaRender.tableActionBulk('basic-table:actionDeleteBulk', {
|
|
149
|
+
requiresSelection: true,
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
152
|
+
}),
|
|
153
|
+
ZovaRender.block('basic-page:blockTable'),
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use `selectionPolicy: 'onDemand'` to expose **Select / Done** without a selected action, `'always'` to show row selection continuously, or `false` to disable row selection for the page. In automatic and on-demand modes, **Select** keeps the checkbox column hidden until the user enters selection mode; **Done** clears the selection and hides the column. Required actions remain discoverable but disabled until every selected row has a current snapshot and passes the projected row-permission check.
|
|
159
|
+
|
|
160
|
+
Schema metadata remains declarative: author `requiresSelection`, `selectedMaxIds`, and an unconditional `disabled` in action options. A custom bulk-action component receives current toolbar state through `dynamicSelection`, `dynamicDisabled`, and `dynamicDisabledReason`; it should disable when either `disabled` or `dynamicDisabled` is true. Frontend snapshots provide UX only—server actions must reload and authorize all submitted IDs.
|
|
161
|
+
|
|
162
|
+
Selection persists while paging the same result universe and during ordinary refetches. It clears on a material fixed-query change, filter submit/reset, sorting, page-size change, resource change, or a successful selection-consuming mutation. Page-size changes also return to page 1.
|
|
163
|
+
|
|
122
164
|
## Step 3: Let schema metadata drive the default columns
|
|
123
165
|
|
|
124
166
|
In the default path, `ZTable` reads table-scene metadata from the row schema.
|
|
@@ -165,6 +165,7 @@ Its controller is responsible for:
|
|
|
165
165
|
- creating query state
|
|
166
166
|
- loading schema and paged data
|
|
167
167
|
- exposing `data`, `schemaRow`, `schemaFilter`, `permissions`, and `paged`
|
|
168
|
+
- owning selected IDs, loaded-row snapshots, and the page-level `selectionPolicy`
|
|
168
169
|
- refreshing table metadata when permissions change
|
|
169
170
|
|
|
170
171
|
This is one of the most important architecture facts about CRUD pages.
|
|
@@ -258,6 +259,26 @@ A practical rule is:
|
|
|
258
259
|
- if the page only needs standard create or other bulk actions, keep the existing block and adjust the metadata
|
|
259
260
|
- if an action is reusable, prefer a reusable action render resource rather than page-local ad hoc code
|
|
260
261
|
|
|
262
|
+
### Declare selected-row actions explicitly
|
|
263
|
+
|
|
264
|
+
`tableActionBulk` describes toolbar placement, not necessarily a batch mutation. Keep Create selection-independent. An action that consumes selected rows must declare `requiresSelection: true`:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
ZovaRender.block('basic-page:blockToolbarBulk', {
|
|
268
|
+
actions: [
|
|
269
|
+
ZovaRender.tableActionBulk('basic-table:actionCreate'),
|
|
270
|
+
ZovaRender.tableActionBulk('basic-table:actionDeleteBulk', {
|
|
271
|
+
requiresSelection: true,
|
|
272
|
+
selectedMaxIds: 100,
|
|
273
|
+
}),
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
With `blockPage`'s default automatic `selectionPolicy`, this exposes an on-demand **Select** control. It reveals the checkbox column; **Done** clears selection before hiding it. Coarse permission controls action discoverability. A selected action is disabled until the selection is nonempty, all selected IDs have loaded snapshots, and every snapshot passes the browser permission matcher. `selectedMaxIds` is optional action policy: when declared, the toolbar disables only that action after the configured count is exceeded; when omitted for a selection-requiring action, the frontend fallback applies.
|
|
279
|
+
|
|
280
|
+
The DTO declares static policy only. Custom bulk-action components receive the current selection and eligibility as `dynamicSelection`, `dynamicDisabled`, and `dynamicDisabledReason`. Those snapshots are only UX evidence: the command must reload, authorize, and mutate every submitted ID atomically on the backend.
|
|
281
|
+
|
|
261
282
|
## Step 6: Let `blockTable` bridge page state into `ZTable`
|
|
262
283
|
|
|
263
284
|
The standard table block is:
|
|
@@ -271,10 +292,13 @@ Its bridge role is very clear:
|
|
|
271
292
|
- pass row schema into `schema`
|
|
272
293
|
- pass the resource order schema into `schemaOrder`
|
|
273
294
|
- pass controlled `sorting` and `onSortingChange`
|
|
295
|
+
- pass controlled `rowSelection` and `onRowSelectionChange`
|
|
274
296
|
- pass page CEL scope into `tableScope`
|
|
275
297
|
- capture the table controller through `controllerRef`
|
|
276
298
|
|
|
277
|
-
|
|
299
|
+
`blockPage.selectionPolicy` controls row-selection behavior: `undefined` uses automatic behavior, `'always'` shows selection immediately, `'onDemand'` exposes Select/Done without a selected action, and `false` disables selection. `blockTable` only consumes the resulting page state. The header checkbox operates only on the loaded page under server pagination.
|
|
300
|
+
|
|
301
|
+
That means `blockTable` does not own list fetching, pagination, or selection policy.
|
|
278
302
|
|
|
279
303
|
It owns the handoff from page resource state to the reusable table runtime.
|
|
280
304
|
|
|
@@ -25,6 +25,7 @@ interface IArticleGeometry {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const blogArticlePaths = [
|
|
28
|
+
'/blogs/ai-spec-driven-development/',
|
|
28
29
|
'/blogs/ai-react-nextjs-enterprise-architecture-cabloy/',
|
|
29
30
|
'/blogs/cabloy-fullstack-resource-addressing/',
|
|
30
31
|
'/blogs/nextjs-integrated-fullstack-cabloy-contract-loop/',
|
|
@@ -118,29 +119,46 @@ test(
|
|
|
118
119
|
await expect(page.locator('.VPDoc.has-aside')).toHaveCount(0);
|
|
119
120
|
|
|
120
121
|
const cards = page.locator('.cabloy-blog-card');
|
|
121
|
-
await expect(cards).toHaveCount(
|
|
122
|
-
for (let index = 0; index <
|
|
122
|
+
await expect(cards).toHaveCount(5);
|
|
123
|
+
for (let index = 0; index < 5; index++) {
|
|
123
124
|
await expect(cards.nth(index)).toBeVisible();
|
|
124
125
|
await expect(cards.nth(index).locator('.cabloy-blog-card__cover img')).toBeVisible();
|
|
125
126
|
}
|
|
127
|
+
await expect(
|
|
128
|
+
cards.nth(0).getByRole('link', {
|
|
129
|
+
name: 'CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts',
|
|
130
|
+
exact: true,
|
|
131
|
+
}),
|
|
132
|
+
).toBeVisible();
|
|
133
|
+
await expect(cards.nth(0).locator('.cabloy-blog-card__date')).toHaveText('September 11, 2026');
|
|
134
|
+
await expect(cards.nth(0).locator('.cabloy-blog-card__cover')).toHaveAttribute(
|
|
135
|
+
'href',
|
|
136
|
+
'/blogs/ai-spec-driven-development/',
|
|
137
|
+
);
|
|
126
138
|
|
|
127
139
|
const geometry = await getBlogsGeometry(page);
|
|
128
140
|
expect(geometry.grid.display).toBe('grid');
|
|
129
|
-
expect(geometry.cards).toHaveLength(
|
|
141
|
+
expect(geometry.cards).toHaveLength(5);
|
|
130
142
|
|
|
131
|
-
const [firstCard,
|
|
143
|
+
const [firstCard, secondCard, thirdCard, fourthCard, fifthCard] = geometry.cards;
|
|
144
|
+
const firstRow = [firstCard, secondCard, thirdCard, fourthCard];
|
|
132
145
|
expect(firstCard.width).toBeGreaterThanOrEqual(279);
|
|
133
|
-
for (const card of
|
|
146
|
+
for (const card of firstRow) {
|
|
134
147
|
expect(Math.abs(card.top - firstCard.top)).toBeLessThanOrEqual(1);
|
|
135
148
|
expect(Math.abs(card.width - firstCard.width)).toBeLessThanOrEqual(1);
|
|
136
|
-
expect(card.left).toBeGreaterThan(firstCard.left);
|
|
137
149
|
expect(card.left).toBeGreaterThanOrEqual(geometry.grid.left - 1);
|
|
138
150
|
expect(card.right).toBeLessThanOrEqual(geometry.grid.right + 1);
|
|
139
151
|
expect(card.right).toBeLessThanOrEqual(1441);
|
|
140
152
|
}
|
|
141
|
-
expect(
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
153
|
+
expect(secondCard.left).toBeGreaterThan(firstCard.left);
|
|
154
|
+
expect(thirdCard.left).toBeGreaterThan(secondCard.left);
|
|
155
|
+
expect(fourthCard.left).toBeGreaterThan(thirdCard.left);
|
|
156
|
+
expect(fifthCard.top).toBeGreaterThan(firstCard.bottom);
|
|
157
|
+
expect(fifthCard.left).toBeCloseTo(firstCard.left, 0);
|
|
158
|
+
expect(fifthCard.width).toBeCloseTo(firstCard.width, 0);
|
|
159
|
+
expect(fifthCard.left).toBeGreaterThanOrEqual(geometry.grid.left - 1);
|
|
160
|
+
expect(fifthCard.right).toBeLessThanOrEqual(geometry.grid.right + 1);
|
|
161
|
+
expect(fifthCard.right).toBeLessThanOrEqual(1441);
|
|
144
162
|
|
|
145
163
|
await expect.poll(() => getDocumentHorizontalOverflow(page)).toBeLessThanOrEqual(1);
|
|
146
164
|
expect(pageErrors).toEqual([]);
|
|
@@ -163,6 +181,24 @@ test(
|
|
|
163
181
|
await expect(page.locator('.VPDoc.has-sidebar')).toHaveCount(0);
|
|
164
182
|
await expect(page.locator('.VPDoc.has-aside')).toHaveCount(1);
|
|
165
183
|
await expect(page.locator('.aside')).toBeVisible();
|
|
184
|
+
if (path === '/blogs/ai-spec-driven-development/') {
|
|
185
|
+
await expect(
|
|
186
|
+
page.getByRole('heading', {
|
|
187
|
+
name: 'CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts',
|
|
188
|
+
level: 1,
|
|
189
|
+
}),
|
|
190
|
+
).toBeVisible();
|
|
191
|
+
await expect(
|
|
192
|
+
page.getByAltText(
|
|
193
|
+
'A-Commerce implementation roadmap: WBS phases, dependencies, and derived status',
|
|
194
|
+
),
|
|
195
|
+
).toBeVisible();
|
|
196
|
+
await expect(
|
|
197
|
+
page.getByAltText(
|
|
198
|
+
'A-Commerce WBS scope-count burndown: approved scope and remaining-item counts',
|
|
199
|
+
),
|
|
200
|
+
).toBeVisible();
|
|
201
|
+
}
|
|
166
202
|
|
|
167
203
|
const geometry = await getArticleGeometry(page);
|
|
168
204
|
expect(geometry.container.width).toBeCloseTo(1216, 0);
|