Plan Combined Task Detail Documentation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
Goal: Add complete bilingual request/response definitions and examples for planFusionTaskExternal and planFusionTaskMain in the standard schedule-plan APIs.
Architecture: Keep the existing YApi JSON files as the API table source. Expand the two object fields inline with properties and nested items.properties in each affected endpoint because the current renderer has no shared-schema or $ref contract. Keep Markdown examples as the copyable payload source and repeat the same structural object in create/update, get, list, and day-list pages.
Tech Stack: JSON, Markdown/MDX, Node.js assertions, Docusaurus, Yarn.
Global Constraints
- Work only in open-platform on feature/a6-plan-explain, based on the freshly fetched origin/master.
- Update both zh_CN and en_US; keep field names, types, nesting, and example values structurally identical between locales.
- Use the stable fields from the supplied response and sch-tasks-server-v5 Fusion DTOs; optional fields remain optional.
- Do not change backend repositories, generated Swagger artifacts, simple schedule APIs, the calendar API, navigation, or rendering components.
- Do not add $ref, shared-schema syntax, legacy planTasks, switchStatus, or isFusionTask fields to public V3 documentation.
- Use existing two-space JSON indentation and existing Markdown code-block style.
- The repository has no automated test suite; inline Node.js assertions plus yarn build are the regression gates.
- The baseline yarn build exits 0 but reports existing Docusaurus broken-anchor warnings; record that warning baseline and ensure changed API pages introduce no JSON/MDX/build errors.
Task 1: Expand standard create/update request schemas
Files:
- Modify: openApiJson/zh_CN/OpenAPI V3 Service/Schedules/Create Schedule Plan.json
- Modify: openApiJson/en_US/OpenAPI V3 Service/Schedules/Create Schedule Plan.json
- Modify: openApiJson/zh_CN/OpenAPI V3 Service/Schedules/Update Schedule Plan.json
- Modify: openApiJson/en_US/OpenAPI V3 Service/Schedules/Update Schedule Plan.json
- Test: inline Node.js assertion below; no new test file
Interfaces:
-
Consumes: the existing req_body_other YApi schema and the approved design document.
-
Produces: nested request definitions at req_body_other.planFusionTaskExternal and req_body_other.planFusionTaskMain in all four files.
-
Step 1: Write and run the failing request-schema assertion
Run from open-platform before editing:
node - <<'NODE'
const assert = require('node:assert/strict');
const fs = require('node:fs');
const files = [
'openApiJson/zh_CN/OpenAPI V3 Service/Schedules/Create Schedule Plan.json',
'openApiJson/en_US/OpenAPI V3 Service/Schedules/Create Schedule Plan.json',
'openApiJson/zh_CN/OpenAPI V3 Service/Schedules/Update Schedule Plan.json',
'openApiJson/en_US/OpenAPI V3 Service/Schedules/Update Schedule Plan.json',
];
const externalKeys = ['fusionTaskId', 'fusionTaskType', 'taskAPIVersion', 'siteId', 'siteName', 'mapList'];
const mainKeys = ['fusionTaskId', 'fusionTaskType', 'taskAPIVersion', 'fusionName', 'relatedTaskId',
'user', 'createdAt', 'updatedUser', 'updatedAt', 'loopCount', 'persistence',
'taskAdvanceConfig', 'siteInfo', 'tasks', 'workMode'];
for (const file of files) {
const api = JSON.parse(fs.readFileSync(file, 'utf8'));
const external = api.req_body_other.planFusionTaskExternal;
const main = api.req_body_other.planFusionTaskMain;
assert.equal(external.type, 'object', file + ': external type');
assert.ok(external.properties, file + ': external properties');
assert.deepEqual(Object.keys(external.properties), externalKeys, file + ': external keys');
assert.equal(main.type, 'object', file + ': main type');
assert.ok(main.properties, file + ': main properties');
assert.deepEqual(Object.keys(main.properties), mainKeys, file + ': main keys');
assert.equal(main.properties.tasks.items.properties.workMode.type, 'object');
}
NODE
Expected: FAIL because the current request objects have no properties member.
- Step 2: Add the external nested schema
Replace each empty planFusionTaskExternal definition with this property order and YApi shape:
{
"type": "object",
"description": "组合任务外部配置。",
"required": "否",
"properties": {
"fusionTaskId": {"type": "string", "example": "00000000-0000-4000-8000-000000000001", "description": "组合任务 UUID。", "required": "否"},
"fusionTaskType": {"type": "integer", "example": 1, "description": "组合任务类型:1 持久化临时任务,2 排班任务,3 临时任务。", "required": "否"},
"taskAPIVersion": {"type": "string", "example": "3.0", "description": "组合任务接口版本。", "required": "否"},
"siteId": {"type": "string", "example": "", "description": "站点 UUID,非站点任务为空。", "required": "否"},
"siteName": {"type": "string", "example": "", "description": "站点名称,非站点任务为空。", "required": "否"},
"mapList": {
"type": "array",
"description": "任务使用的地图列表。",
"required": "否",
"items": {
"type": "object",
"description": "地图配置。",
"required": "否",
"properties": {
"mapId": {"type": "string", "example": "00000000-0000-4000-8000-000000000002", "description": "地图 UUID。", "required": "否"},
"mapName": {"type": "string", "example": "Example map", "description": "地图名称。", "required": "否"},
"floorIndex": {"type": "integer", "example": 0, "description": "楼层索引。", "required": "否"},
"floorName": {"type": "string", "example": "", "description": "楼层名称。", "required": "否"},
"mapResourceList": {
"type": "array",
"description": "地图资源列表。",
"required": "否",
"items": {
"type": "object",
"description": "地图资源。",
"required": "否",
"properties": {
"mapResourceId": {"type": "string", "example": "00000000-0000-4000-8000-000000000003", "description": "地图分区或路径 UUID。", "required": "否"},
"mapResourceType": {"type": "string", "example": "region", "description": "地图资源类型:region 分区,path 路径。", "required": "否"},
"mapResourceName": {"type": "string", "example": "example-area", "description": "地图分区 或路径名称。", "required": "否"},
"mapResourceAreaId": {"type": "string", "example": "1", "description": "旧版本分区编号;路径为空。", "required": "否"}
}
}
}
}
}
}
}
}
For English files use the same keys, types, order, and examples. Translate descriptions to: Combined Task UUID.; Combined Task type: 1 persistent temporary, 2 schedule, 3 temporary.; Combined Task API version.; Site UUID; empty for a non-site task.; Site name; empty for a non-site task.; Maps used by the task.; Map configuration.; Map UUID.; Map name.; Floor index.; Floor name.; Map resource list.; Map resource.; Region or path UUID.; Resource type: region or path.; Region or path name.; Legacy region number; empty for a path.
- Step 3: Add the main nested schema
Replace each empty planFusionTaskMain definition with these top-level properties, in order: fusionTaskId, fusionTaskType, taskAPIVersion, fusionName, relatedTaskId, user, createdAt, updatedUser, updatedAt, loopCount, persistence, taskAdvanceConfig, siteInfo, tasks, workMode.
Nested definitions must be exact:
taskAdvanceConfig:
activeCleanSwitch boolean false
activeCleanMode integer 0
autoPlanning boolean true
coverageMode string standard
taskRunningMode string normal
walkingPattern string 0
subTaskMode boolean false
tempTaskPriority boolean false
siteInfo:
siteId string ""
tasks[]:
id string
mapResourceId string
mapId string
type string (region or path)
workMode object with id, name, strength, subType, type
workMode:
id string
name string
strength string
subType string
type integer
Every leaf must have type, example, localized description, and required. Use the approved descriptions: task metadata uses the Fusion DTO meanings; timestamps are seconds; loopCount defaults to 1; advanced fields explain their enum values; siteId is empty for non-site tasks; workMode is optional for a subtask. Use the same UUID and values as the external schema and the supplied sample: fusionTaskId 00000000-0000-4000-8000-000000000001, createdAt/updatedAt 1704067200, fusionName Example schedule task, user/updatedUser demo-user, mapId 00000000-0000-4000-8000-000000000002, mapResourceId 00000000-0000-4000-8000-000000000003, and workMode name sweep.
- Step 4: Run the assertion and commit request schemas
Run the Step 1 assertion; expect exit 0. Then run git diff --check, stage only the four listed JSON files, run git diff --cached --check, and commit:
git commit -m "update: document Combined Task request fields"
Expected: one focused commit containing only the four request JSON sources.
Task 2: Expand get/list/day-list response schemas
Files:
- Modify: the Chinese and English Get Schedule Plan.json files
- Modify: the Chinese and English List Schedule Plans.json files
- Modify: the Chinese and English List Schedule Pre Tasks By Day.json files
- Test: inline Node.js assertion below; no new test file
Interfaces:
-
Consumes: the nested object contract from Task 1 and the existing outer plan response fields.
-
Produces: nested response definitions at the correct data location for each response shape.
-
Step 1: Write and run the failing response-schema assertion
node - <<'NODE'
const assert = require('node:assert/strict');
const fs = require('node:fs');
const groups = {
get: ['openApiJson/zh_CN/OpenAPI V3 Service/Schedules/Get Schedule Plan.json',
'openApiJson/en_US/OpenAPI V3 Service/Schedules/Get Schedule Plan.json'],
list: ['openApiJson/zh_CN/OpenAPI V3 Service/Schedules/List Schedule Plans.json',
'openApiJson/en_US/OpenAPI V3 Service/Schedules/List Schedule Plans.json'],
day: ['openApiJson/zh_CN/OpenAPI V3 Service/Schedules/List Schedule Pre Tasks By Day.json',
'openApiJson/en_US/OpenAPI V3 Service/Schedules/List Schedule Pre Tasks By Day.json'],
};
function item(api, kind) {
if (kind === 'get') return api.res_body.data.properties;
if (kind === 'list') return api.res_body.data.items.properties;
return api.res_body.data.properties.preTasks.items.properties;
}
for (const [kind, files] of Object.entries(groups)) {
for (const file of files) {
const properties = item(JSON.parse(fs.readFileSync(file, 'utf8')), kind);
for (const name of ['planFusionTaskExternal', 'planFusionTaskMain']) {
assert.equal(properties[name].type, 'object', file + ': ' + name + ' type');
assert.ok(properties[name].properties, file + ': ' + name + ' properties');
}
assert.ok(properties.planFusionTaskMain.properties.taskAdvanceConfig, file + ': advance config');
assert.ok(properties.planFusionTaskMain.properties.tasks.items.properties.workMode, file + ': task work mode');
assert.ok(properties.planFusionTaskExternal.properties.mapList.items.properties.mapResourceList, file + ': map resources');
}
}
NODE
Expected: FAIL because list/day-list objects lack nested properties and get objects contain empty fusion definitions.
- Step 2: Insert the detailed schemas at each response location
Use these exact paths and do not move or remove existing scalar plan fields:
Get Schedule Plan: res_body.data.properties.planFusionTask*
List Schedule Plans: res_body.data.items.properties.planFusionTask*
List Schedule Pre Tasks By Day: res_body.data.properties.preTasks.items.properties.planFusionTask*
Copy the full external and main nested shape from Task 1. In responses, set outer descriptions to Chinese: 组合任务外部数据,供前端展示使用的 JSON 数据。 and 组合任务主数据,供车端执行的完整 JSON 数据。; English: Combined Task external JSON data for frontend display. and Combined Task main JSON data sent to the robot. Keep all nested leaf descriptions, examples, and required flags. The response schema must show mapList, mapResourceList, taskAdvanceConfig, siteInfo, tasks, tasks[].workMode, and workMode in the API field table.
- Step 3: Verify bilingual structural parity and commit response schemas
Run the Step 1 assertion. Then compare the JSON structure while ignoring localized description, example, and required values:
node - <<'NODE'
const assert = require('node:assert/strict');
const fs = require('node:fs');
const pairs = [
['openApiJson/zh_CN/OpenAPI V3 Service/Schedules/Get Schedule Plan.json', 'openApiJson/en_US/OpenAPI V3 Service/Schedules/Get Schedule Plan.json'],
['openApiJson/zh_CN/OpenAPI V3 Service/Schedules/List Schedule Plans.json', 'openApiJson/en_US/OpenAPI V3 Service/Schedules/List Schedule Plans.json'],
['openApiJson/zh_CN/OpenAPI V3 Service/Schedules/List Schedule Pre Tasks By Day.json', 'openApiJson/en_US/OpenAPI V3 Service/Schedules/List Schedule Pre Tasks By Day.json'],
];
function shape(value) {
if (Array.isArray(value)) return value.map(shape);
if (!value || typeof value !== 'object') return typeof value;
return Object.fromEntries(Object.entries(value)
.filter(([key]) => !['description', 'example', 'required'].includes(key))
.map(([key, child]) => [key, shape(child)]));
}
for (const [zhFile, enFile] of pairs) {
const zh = JSON.parse(fs.readFileSync(zhFile, 'utf8'));
const en = JSON.parse(fs.readFileSync(enFile, 'utf8'));
assert.deepEqual(shape(zh.res_body), shape(en.res_body), zhFile + ' differs from ' + enFile);
}
NODE
git diff --check
git commit -m "update: document Combined Task response fields"
Expected: all six response JSON files have matching structural shapes and the response-only commit is created.
Task 3: Add representative bilingual Markdown examples
Files:
- Modify: Create Schedule Plan.md and Update Schedule Plan.md in zh_CN and en_US
- Modify: Get Schedule Plan.md, List Schedule Plans.md, and List Schedule Pre Tasks By Day.md in zh_CN and en_US
- Test: inline Markdown assertion; no new test file
Interfaces:
-
Consumes: the request/response JSON contracts from Tasks 1 and 2 and the supplied sample response.
-
Produces: copyable nested request and response examples with identical JSON structure across locales.
-
Step 1: Write and run the failing Markdown assertion
Run this from open-platform before editing. It extracts the existing code blocks and checks the nested fields that are currently missing:
node - <<'NODE'
const assert = require('node:assert/strict');
const fs = require('node:fs');
const requestFiles = [
'md/zh_CN/OpenAPI V3 Service/Schedules/Create Schedule Plan.md',
'md/en_US/OpenAPI V3 Service/Schedules/Create Schedule Plan.md',
'md/zh_CN/OpenAPI V3 Service/Schedules/Update Schedule Plan.md',
'md/en_US/OpenAPI V3 Service/Schedules/Update Schedule Plan.md',
];
const responseFiles = [
['md/zh_CN/OpenAPI V3 Service/Schedules/Get Schedule Plan.md', 'get', '输出'],
['md/en_US/OpenAPI V3 Service/Schedules/Get Schedule Plan.md', 'get', 'Response'],
['md/zh_CN/OpenAPI V3 Service/Schedules/List Schedule Plans.md', 'list', '输出'],
['md/en_US/OpenAPI V3 Service/Schedules/List Schedule Plans.md', 'list', 'Response'],
['md/zh_CN/OpenAPI V3 Service/Schedules/List Schedule Pre Tasks By Day.md', 'day', '输出'],
['md/en_US/OpenAPI V3 Service/Schedules/List Schedule Pre Tasks By Day.md', 'day', 'Response'],
];
const fence = String.fromCharCode(96);
function requestJson(file) {
const content = fs.readFileSync(file, 'utf8');
const start = content.indexOf("--data '") + 8;
const end = content.indexOf("\n}'", start);
assert.ok(start >= 8 && end > start, file + ': request block');
return JSON.parse(content.slice(start, end + 2));
}
function responseJson(file, title) {
const content = fs.readFileSync(file, 'utf8');
const marker = '<CodeBlock language="jsx" title="' + title + '">{' + fence;
const start = content.indexOf(marker);
const bodyStart = content.indexOf('\n', start) + 1;
const end = content.indexOf('\n' + fence + '}</CodeBlock>', bodyStart);
assert.ok(start >= 0 && end > bodyStart, file + ': response block');
return JSON.parse(content.slice(bodyStart, end));
}
for (const file of requestFiles) {
const payload = requestJson(file);
assert.equal(payload.planFusionTaskExternal.mapList[0].mapResourceList[0].mapResourceType, 'region', file);
assert.equal(payload.planFusionTaskMain.taskAdvanceConfig.autoPlanning, true, file);
}
for (const [file, kind, title] of responseFiles) {
const response = responseJson(file, title);
const plan = kind === 'get' ? response.data : kind === 'list' ? response.data[0] : response.data.preTasks[0];
const expectedResourceName = file.includes('/zh_CN/') ? '示例区域' : 'example-area';
assert.equal(plan.planFusionTaskExternal.mapList[0].mapResourceList[0].mapResourceName, expectedResourceName, file);
assert.equal(plan.planFusionTaskMain.tasks[0].type, 'region', file);
assert.equal(plan.planFusionTaskMain.workMode.name, 'sweep', file);
}
NODE
Expected: FAIL against the current and fusionTaskId-only examples.
- Step 2: Replace create/update request examples
In all four standard request blocks, replace both empty objects with the same representative data:
"planFusionTaskExternal": {
"fusionTaskId": "00000000-0000-4000-8000-000000000001",
"fusionTaskType": 1,
"mapList": [{"floorIndex": 0, "floorName": "", "mapId": "00000000-0000-4000-8000-000000000002", "mapName": "Example map",
"mapResourceList": [{"mapResourceAreaId": "1", "mapResourceId": "00000000-0000-4000-8000-000000000003", "mapResourceName": "example-area", "mapResourceType": "region"}]}],
"siteId": "",
"siteName": "",
"taskAPIVersion": "3.0"
},
"planFusionTaskMain": {
"createdAt": 1704067200,
"fusionName": "Example schedule task",
"fusionTaskId": "00000000-0000-4000-8000-000000000001",
"fusionTaskType": 1,
"loopCount": 1,
"persistence": true,
"siteInfo": {"siteId": ""},
"taskAPIVersion": "3.0",
"taskAdvanceConfig": {"activeCleanSwitch": false, "activeCleanMode": 0, "autoPlanning": true, "coverageMode": "standard",
"taskRunningMode": "normal", "walkingPattern": "0", "subTaskMode": false, "tempTaskPriority": false},
"tasks": [{"id": "00000000-0000-4000-8000-000000000004", "mapId": "00000000-0000-4000-8000-000000000002",
"mapResourceId": "00000000-0000-4000-8000-000000000003", "type": "region"}],
"updatedAt": 1704067200,
"updatedUser": "demo-user",
"user": "demo-user",
"workMode": {"id": "00000000-0000-4000-8000-000000000005", "name": "sweep", "strength": "middle", "subType": "sweep", "type": 0}
}
Keep existing endpoint-specific scalar request fields and keep create/update response data as . Only CodeBlock titles and surrounding prose are localized.
- Step 3: Replace query response examples
Use the same detailed plan object from Step 2 in these outer shapes:
Get Schedule Plan: response.data = detailed plan object
List Schedule Plans: response.data = [detailed plan object]
List Schedule Pre Tasks By Day: response.data.preTasks = [detailed plan object]
Preserve existing plan scalar fields, use the supplied UUID/map/resource values, and show both nested objects in each query example. Do not add productId, planTasks, switchStatus, or isFusionTask. Keep Chinese and English JSON data identical apart from already-localized prose outside the response block.
- Step 4: Run example assertions, build, inspect, and commit
Run the Markdown assertion; expect exit 0. Then run git diff --check, yarn build, inspect git diff for all ten Markdown files, stage only those files, run git diff --cached --check, and commit:
git commit -m "update: add Combined Task documentation examples"
Expected: build exits 0 with only the recorded broken-anchor warnings; no malformed JSON/MDX errors occur.
Task 4: Final verification, review, and push
Files:
-
Test: all inline schema/parity/example assertions from Tasks 1-3
-
Inspect: origin/master...HEAD
-
Step 1: Re-run all assertions from a clean working tree
Run every assertion from Tasks 1-3. Expected: all exit 0.
- Step 2: Run final verification
git diff --check origin/master...HEAD
yarn build
git status --short --branch
git log --oneline --decorate origin/master..HEAD
Expected: no whitespace errors; build exit 0 with only known broken-anchor warnings; no untracked/generated files; the log contains the design commit and three focused implementation commits.
- Step 3: Request code review before pushing
Use final range origin/master...HEAD, the approved design document, and schema/example assertions as review context. Fix Critical or Important findings, rerun affected assertions and yarn build, and commit fixes.
- Step 4: Push to GitLab
git push -u origin feature/a6-plan-explain
Expected: GitLab accepts the new branch without force-push; report the branch and final commits. Do not merge master unless separately requested.