@prmichaelsen/acp-visualizer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ACP YAML Schema Validator
|
|
3
|
+
# Pure bash YAML validation against schema definitions
|
|
4
|
+
# Zero external dependencies
|
|
5
|
+
|
|
6
|
+
# Source YAML parser (using new generic AST-based parser)
|
|
7
|
+
SCRIPT_DIR="$(dirname "$0")"
|
|
8
|
+
. "${SCRIPT_DIR}/acp.yaml-parser.sh"
|
|
9
|
+
. "${SCRIPT_DIR}/acp.common.sh"
|
|
10
|
+
|
|
11
|
+
# Initialize colors
|
|
12
|
+
init_colors
|
|
13
|
+
|
|
14
|
+
# Validation error tracking
|
|
15
|
+
VALIDATION_ERRORS=0
|
|
16
|
+
VALIDATION_WARNINGS=0
|
|
17
|
+
|
|
18
|
+
# Add validation error
|
|
19
|
+
# Usage: add_error "Error message"
|
|
20
|
+
add_error() {
|
|
21
|
+
echo "${RED}❌ $1${NC}" >&2
|
|
22
|
+
VALIDATION_ERRORS=$((VALIDATION_ERRORS + 1))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Add validation warning
|
|
26
|
+
# Usage: add_warning "Warning message"
|
|
27
|
+
add_warning() {
|
|
28
|
+
echo "${YELLOW}⚠️ $1${NC}" >&2
|
|
29
|
+
VALIDATION_WARNINGS=$((VALIDATION_WARNINGS + 1))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Validate field exists
|
|
33
|
+
# Usage: validate_field_exists "file.yaml" "field.path"
|
|
34
|
+
validate_field_exists() {
|
|
35
|
+
local yaml_file="$1"
|
|
36
|
+
local field_path="$2"
|
|
37
|
+
|
|
38
|
+
if ! yaml_has_key "$yaml_file" "$field_path"; then
|
|
39
|
+
return 1
|
|
40
|
+
fi
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Validate string pattern (regex)
|
|
45
|
+
# Usage: validate_pattern "value" "pattern" "field_name"
|
|
46
|
+
validate_pattern() {
|
|
47
|
+
local value="$1"
|
|
48
|
+
local pattern="$2"
|
|
49
|
+
local field_name="$3"
|
|
50
|
+
|
|
51
|
+
if ! echo "$value" | grep -qE "$pattern"; then
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Validate string length
|
|
58
|
+
# Usage: validate_length "value" min max "field_name"
|
|
59
|
+
validate_length() {
|
|
60
|
+
local value="$1"
|
|
61
|
+
local min="$2"
|
|
62
|
+
local max="$3"
|
|
63
|
+
local field_name="$4"
|
|
64
|
+
|
|
65
|
+
local length=${#value}
|
|
66
|
+
|
|
67
|
+
if [ -n "$min" ] && [ "$length" -lt "$min" ]; then
|
|
68
|
+
add_error "Field '$field_name': Too short (minimum $min characters, got $length)"
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ -n "$max" ] && [ "$length" -gt "$max" ]; then
|
|
73
|
+
add_error "Field '$field_name': Too long (maximum $max characters, got $length)"
|
|
74
|
+
return 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
return 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Validate package.yaml file
|
|
81
|
+
# Usage: validate_package_yaml "package.yaml"
|
|
82
|
+
# Returns: 0 if valid, 1 if invalid
|
|
83
|
+
validate_package_yaml() {
|
|
84
|
+
local yaml_file="$1"
|
|
85
|
+
|
|
86
|
+
if [ ! -f "$yaml_file" ]; then
|
|
87
|
+
add_error "File not found: $yaml_file"
|
|
88
|
+
return 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo "${BLUE}Validating $yaml_file...${NC}"
|
|
92
|
+
echo ""
|
|
93
|
+
|
|
94
|
+
# Check YAML syntax (try to parse)
|
|
95
|
+
if ! yaml_get "$yaml_file" "name" >/dev/null 2>&1; then
|
|
96
|
+
add_error "Invalid YAML syntax in $yaml_file"
|
|
97
|
+
return 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Validate required fields
|
|
101
|
+
local required_fields="name version description author license repository"
|
|
102
|
+
for field in $required_fields; do
|
|
103
|
+
if ! validate_field_exists "$yaml_file" "$field"; then
|
|
104
|
+
add_error "Required field missing: '$field'"
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
# Validate name field
|
|
109
|
+
if validate_field_exists "$yaml_file" "name"; then
|
|
110
|
+
local name=$(yaml_get "$yaml_file" "name")
|
|
111
|
+
if ! validate_pattern "$name" "^[a-z0-9-]+$" "name"; then
|
|
112
|
+
add_error "Field 'name': Must be lowercase letters, numbers, and hyphens only (got: '$name')"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Check reserved names
|
|
116
|
+
case "$name" in
|
|
117
|
+
acp|local|core|system|global)
|
|
118
|
+
add_error "Field 'name': '$name' is a reserved package name"
|
|
119
|
+
;;
|
|
120
|
+
esac
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Validate version field
|
|
124
|
+
if validate_field_exists "$yaml_file" "version"; then
|
|
125
|
+
local version=$(yaml_get "$yaml_file" "version")
|
|
126
|
+
if ! validate_pattern "$version" "^[0-9]+\\.[0-9]+\\.[0-9]+$" "version"; then
|
|
127
|
+
add_error "Field 'version': Must be semantic version format X.Y.Z (got: '$version')"
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Validate description field
|
|
132
|
+
if validate_field_exists "$yaml_file" "description"; then
|
|
133
|
+
local description=$(yaml_get "$yaml_file" "description")
|
|
134
|
+
validate_length "$description" 10 200 "description"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Validate author field
|
|
138
|
+
if validate_field_exists "$yaml_file" "author"; then
|
|
139
|
+
local author=$(yaml_get "$yaml_file" "author")
|
|
140
|
+
validate_length "$author" 2 "" "author"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Validate repository field
|
|
144
|
+
if validate_field_exists "$yaml_file" "repository"; then
|
|
145
|
+
local repository=$(yaml_get "$yaml_file" "repository")
|
|
146
|
+
if ! validate_pattern "$repository" "^https?://.*\\.git$" "repository"; then
|
|
147
|
+
add_error "Field 'repository': Must be a git URL ending with .git (got: '$repository')"
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Validate homepage field (optional)
|
|
152
|
+
if validate_field_exists "$yaml_file" "homepage"; then
|
|
153
|
+
local homepage=$(yaml_get "$yaml_file" "homepage")
|
|
154
|
+
if ! validate_pattern "$homepage" "^https?://.*" "homepage"; then
|
|
155
|
+
add_error "Field 'homepage': Must be a valid HTTP/HTTPS URL (got: '$homepage')"
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# Validate contents field (required) - use grep since yaml_has_key may not work for nested objects
|
|
160
|
+
if ! grep -q "^contents:" "$yaml_file"; then
|
|
161
|
+
add_error "Required field missing: 'contents'"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Validate requires.acp field (optional)
|
|
165
|
+
if validate_field_exists "$yaml_file" "requires.acp"; then
|
|
166
|
+
local acp_version=$(yaml_get "$yaml_file" "requires.acp")
|
|
167
|
+
if ! validate_pattern "$acp_version" "^>=?[0-9]+\\.[0-9]+\\.[0-9]+$" "requires.acp"; then
|
|
168
|
+
add_error "Field 'requires.acp': Must be version constraint like '>=2.0.0' (got: '$acp_version')"
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Report results
|
|
173
|
+
echo ""
|
|
174
|
+
if [ "$VALIDATION_ERRORS" -eq 0 ]; then
|
|
175
|
+
echo "${GREEN}✅ Validation passed${NC}"
|
|
176
|
+
if [ "$VALIDATION_WARNINGS" -gt 0 ]; then
|
|
177
|
+
echo "${YELLOW}⚠️ $VALIDATION_WARNINGS warning(s)${NC}"
|
|
178
|
+
fi
|
|
179
|
+
return 0
|
|
180
|
+
else
|
|
181
|
+
echo "${RED}❌ Validation failed${NC}"
|
|
182
|
+
echo "${RED} $VALIDATION_ERRORS error(s)${NC}"
|
|
183
|
+
if [ "$VALIDATION_WARNINGS" -gt 0 ]; then
|
|
184
|
+
echo "${YELLOW} $VALIDATION_WARNINGS warning(s)${NC}"
|
|
185
|
+
fi
|
|
186
|
+
return 1
|
|
187
|
+
fi
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Main function for standalone execution
|
|
191
|
+
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|
192
|
+
# Script is being executed directly
|
|
193
|
+
if [ $# -eq 0 ]; then
|
|
194
|
+
echo "Usage: $0 <yaml-file>"
|
|
195
|
+
echo ""
|
|
196
|
+
echo "Example:"
|
|
197
|
+
echo " $0 package.yaml"
|
|
198
|
+
exit 1
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
validate_package_yaml "$1"
|
|
202
|
+
exit $?
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Script is being sourced, functions are available
|
|
File without changes
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Task 1: Initialize TanStack Start Project
|
|
2
|
+
|
|
3
|
+
**Milestone**: [M1 - Project Scaffold & Data Pipeline](../../milestones/milestone-1-project-scaffold-data-pipeline.md)
|
|
4
|
+
**Design Reference**: [Dashboard Layout & Routing](../../design/local.dashboard-layout-routing.md)
|
|
5
|
+
**Estimated Time**: 2 hours
|
|
6
|
+
**Dependencies**: None
|
|
7
|
+
**Status**: Not Started
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Objective
|
|
12
|
+
|
|
13
|
+
Scaffold a TanStack Start project with Vite, React, TanStack Router, and Tailwind CSS. Configure design tokens for the admin dashboard theme. This establishes the foundational build toolchain and styling system that all subsequent tasks build upon.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Context
|
|
18
|
+
|
|
19
|
+
The ACP Progress Visualizer is a TanStack Start application that renders progress.yaml data as an interactive admin dashboard. This first task creates the project skeleton with all P0 dependencies installed and Tailwind configured with the project's design token palette (status colors, typography, dark theme). Without this foundation, no UI or data pipeline work can proceed.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
### 1. Scaffold the TanStack Start project
|
|
26
|
+
|
|
27
|
+
Run the TanStack Start scaffolding tool or manually initialize the project structure:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm create @tanstack/start@latest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If scaffolding manually, create a `package.json` with the project name and set `"type": "module"`.
|
|
34
|
+
|
|
35
|
+
### 2. Install dependencies
|
|
36
|
+
|
|
37
|
+
Install all P0 dependencies required for the visualizer:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @tanstack/react-router @tanstack/react-start @tanstack/react-table react react-dom
|
|
41
|
+
npm install tailwindcss postcss autoprefixer lucide-react js-yaml fuse.js
|
|
42
|
+
npm install -D @types/react @types/react-dom @types/js-yaml typescript vite
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Configure Tailwind with custom design tokens
|
|
46
|
+
|
|
47
|
+
Initialize Tailwind and configure the theme in `tailwind.config.ts`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx tailwindcss init -p --ts
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Add custom design tokens to the Tailwind config:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// tailwind.config.ts
|
|
57
|
+
import type { Config } from "tailwindcss";
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
content: ["./app/**/*.{ts,tsx}"],
|
|
61
|
+
darkMode: "class",
|
|
62
|
+
theme: {
|
|
63
|
+
extend: {
|
|
64
|
+
colors: {
|
|
65
|
+
status: {
|
|
66
|
+
completed: "#22c55e", // green-500
|
|
67
|
+
in_progress: "#3b82f6", // blue-500
|
|
68
|
+
not_started: "#6b7280", // gray-500
|
|
69
|
+
blocked: "#ef4444", // red-500
|
|
70
|
+
skipped: "#a855f7", // purple-500
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
fontFamily: {
|
|
74
|
+
sans: ["Inter", "system-ui", "sans-serif"],
|
|
75
|
+
mono: ["JetBrains Mono", "monospace"],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
plugins: [],
|
|
80
|
+
} satisfies Config;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Create the root layout
|
|
84
|
+
|
|
85
|
+
Create `app/routes/__root.tsx` with a minimal root layout that includes the dark theme class and global styles:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// app/routes/__root.tsx
|
|
89
|
+
import { createRootRoute, Outlet } from "@tanstack/react-router";
|
|
90
|
+
|
|
91
|
+
export const Route = createRootRoute({
|
|
92
|
+
component: RootLayout,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function RootLayout() {
|
|
96
|
+
return (
|
|
97
|
+
<div className="dark min-h-screen bg-gray-950 text-gray-100">
|
|
98
|
+
<Outlet />
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 5. Create the index route
|
|
105
|
+
|
|
106
|
+
Create `app/routes/index.tsx` with placeholder content to verify the app renders:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// app/routes/index.tsx
|
|
110
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
111
|
+
|
|
112
|
+
export const Route = createFileRoute("/")({
|
|
113
|
+
component: HomePage,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function HomePage() {
|
|
117
|
+
return (
|
|
118
|
+
<div className="p-8">
|
|
119
|
+
<h1 className="text-2xl font-bold font-sans">
|
|
120
|
+
ACP Progress Visualizer
|
|
121
|
+
</h1>
|
|
122
|
+
<p className="mt-2 text-gray-400 font-mono text-sm">
|
|
123
|
+
Dashboard loading...
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 6. Create TanStack Start configuration
|
|
131
|
+
|
|
132
|
+
Create `app.config.ts` for TanStack Start:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// app.config.ts
|
|
136
|
+
import { defineConfig } from "@tanstack/react-start/config";
|
|
137
|
+
import viteTsConfigPaths from "vite-tsconfig-paths";
|
|
138
|
+
|
|
139
|
+
export default defineConfig({
|
|
140
|
+
vite: {
|
|
141
|
+
plugins: [viteTsConfigPaths()],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 7. Verify dev server starts
|
|
147
|
+
|
|
148
|
+
Run the development server and confirm the app loads without errors:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm run dev
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Open the browser to the local dev URL and verify the placeholder content renders with correct fonts and dark theme styling.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Verification
|
|
159
|
+
|
|
160
|
+
- [ ] `package.json` exists with all P0 dependencies listed
|
|
161
|
+
- [ ] `tailwind.config.ts` exists and contains status color tokens (completed, in_progress, not_started, blocked, skipped)
|
|
162
|
+
- [ ] `tailwind.config.ts` specifies Inter for sans and JetBrains Mono for mono font families
|
|
163
|
+
- [ ] `app/routes/__root.tsx` exists with dark theme wrapper and `<Outlet />`
|
|
164
|
+
- [ ] `app/routes/index.tsx` exists with placeholder content
|
|
165
|
+
- [ ] `app.config.ts` exists with TanStack Start configuration
|
|
166
|
+
- [ ] `npm run dev` starts without errors
|
|
167
|
+
- [ ] Root route renders in the browser with dark background and light text
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Expected Output
|
|
172
|
+
|
|
173
|
+
**File Structure**:
|
|
174
|
+
```
|
|
175
|
+
project-root/
|
|
176
|
+
├── app/
|
|
177
|
+
│ ├── routes/
|
|
178
|
+
│ │ ├── __root.tsx
|
|
179
|
+
│ │ └── index.tsx
|
|
180
|
+
│ └── styles/
|
|
181
|
+
│ └── globals.css
|
|
182
|
+
├── app.config.ts
|
|
183
|
+
├── tailwind.config.ts
|
|
184
|
+
├── postcss.config.js
|
|
185
|
+
├── tsconfig.json
|
|
186
|
+
├── package.json
|
|
187
|
+
└── package-lock.json
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Key Files Created**:
|
|
191
|
+
- `package.json`: Project manifest with all P0 dependencies
|
|
192
|
+
- `tailwind.config.ts`: Tailwind configuration with status colors and typography tokens
|
|
193
|
+
- `app/routes/__root.tsx`: Root layout with dark theme wrapper
|
|
194
|
+
- `app/routes/index.tsx`: Index route with placeholder content
|
|
195
|
+
- `app.config.ts`: TanStack Start build configuration
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Notes
|
|
200
|
+
|
|
201
|
+
- Dark theme is the default and only theme for this project; no light mode toggle is planned
|
|
202
|
+
- The `lucide-react` icon library is installed now but used in later tasks for sidebar and status icons
|
|
203
|
+
- `fuse.js` and `@tanstack/react-table` are installed now to avoid dependency churn in later milestones
|
|
204
|
+
- If `npm create @tanstack/start@latest` output differs from expected, adjust file locations to match the scaffolding output
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
**Next Task**: [Task 2: Implement Data Model & YAML Parser](./task-2-implement-data-model-yaml-parser.md)
|
|
209
|
+
**Related Design Docs**: [Dashboard Layout & Routing](../../design/local.dashboard-layout-routing.md)
|
|
210
|
+
**Estimated Completion Date**: TBD
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Task 2: Implement Data Model & YAML Parser
|
|
2
|
+
|
|
3
|
+
**Milestone**: [M1 - Project Scaffold & Data Pipeline](../../milestones/milestone-1-project-scaffold-data-pipeline.md)
|
|
4
|
+
**Design Reference**: [Data Model & YAML Parsing](../../design/local.data-model-yaml-parsing.md)
|
|
5
|
+
**Estimated Time**: 3 hours
|
|
6
|
+
**Dependencies**: Task 1
|
|
7
|
+
**Status**: Not Started
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Objective
|
|
12
|
+
|
|
13
|
+
Create TypeScript interfaces for progress.yaml data and a lenient YAML parser that handles agent-maintained YAML with drift tolerance. The parser must gracefully handle missing fields, unknown keys, status aliases, and type coercion so the dashboard never crashes on imperfect YAML.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Context
|
|
18
|
+
|
|
19
|
+
The progress.yaml file is maintained by AI agents and tends to drift from a strict schema over time. Fields get renamed, statuses use informal terms ("done" instead of "completed"), single strings appear where arrays are expected, and unknown fields accumulate. The parser must be maximally tolerant: extract what it can, preserve unknown fields in an `extra` bag, normalize statuses and field names, and return safe defaults for anything missing. This approach ensures the dashboard always renders something useful rather than crashing on schema violations.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
### 1. Create TypeScript interfaces
|
|
26
|
+
|
|
27
|
+
Create `app/lib/types.ts` with all data model interfaces:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// app/lib/types.ts
|
|
31
|
+
|
|
32
|
+
export type Status = "completed" | "in_progress" | "not_started" | "blocked" | "skipped";
|
|
33
|
+
|
|
34
|
+
export interface ExtraFields {
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ProjectMetadata {
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
version: string;
|
|
42
|
+
repository: string;
|
|
43
|
+
extra: ExtraFields;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkEntry {
|
|
47
|
+
date: string;
|
|
48
|
+
task: string;
|
|
49
|
+
hours: number;
|
|
50
|
+
notes: string;
|
|
51
|
+
extra: ExtraFields;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Task {
|
|
55
|
+
id: string;
|
|
56
|
+
title: string;
|
|
57
|
+
status: Status;
|
|
58
|
+
estimated_hours: number;
|
|
59
|
+
actual_hours: number;
|
|
60
|
+
dependencies: string[];
|
|
61
|
+
tags: string[];
|
|
62
|
+
notes: string;
|
|
63
|
+
extra: ExtraFields;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface Milestone {
|
|
67
|
+
id: string;
|
|
68
|
+
title: string;
|
|
69
|
+
status: Status;
|
|
70
|
+
target_date: string;
|
|
71
|
+
tasks: Task[];
|
|
72
|
+
extra: ExtraFields;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DocumentationStats {
|
|
76
|
+
design_docs: number;
|
|
77
|
+
task_docs: number;
|
|
78
|
+
patterns: number;
|
|
79
|
+
clarifications: number;
|
|
80
|
+
extra: ExtraFields;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ProgressSummary {
|
|
84
|
+
total_tasks: number;
|
|
85
|
+
completed_tasks: number;
|
|
86
|
+
completion_percentage: number;
|
|
87
|
+
total_hours_estimated: number;
|
|
88
|
+
total_hours_actual: number;
|
|
89
|
+
extra: ExtraFields;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ProgressData {
|
|
93
|
+
project: ProjectMetadata;
|
|
94
|
+
milestones: Milestone[];
|
|
95
|
+
work_entries: WorkEntry[];
|
|
96
|
+
documentation: DocumentationStats;
|
|
97
|
+
progress: ProgressSummary;
|
|
98
|
+
extra: ExtraFields;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Create the YAML loader module
|
|
103
|
+
|
|
104
|
+
Create `app/lib/yaml-loader.ts` with the main `parseProgressYaml` function:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// app/lib/yaml-loader.ts
|
|
108
|
+
import yaml from "js-yaml";
|
|
109
|
+
import type { ProgressData } from "./types";
|
|
110
|
+
|
|
111
|
+
export function parseProgressYaml(raw: string): ProgressData {
|
|
112
|
+
// Implementation in subsequent steps
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 3. Implement the `extractKnown` helper
|
|
117
|
+
|
|
118
|
+
This utility separates known fields from extras, which is the foundation of drift tolerance:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
function extractKnown<T extends Record<string, unknown>>(
|
|
122
|
+
obj: Record<string, unknown>,
|
|
123
|
+
knownKeys: string[]
|
|
124
|
+
): { known: Partial<T>; extra: Record<string, unknown> } {
|
|
125
|
+
const known: Record<string, unknown> = {};
|
|
126
|
+
const extra: Record<string, unknown> = {};
|
|
127
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
128
|
+
if (knownKeys.includes(key)) {
|
|
129
|
+
known[key] = value;
|
|
130
|
+
} else {
|
|
131
|
+
extra[key] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { known: known as Partial<T>, extra };
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4. Implement `normalizeStatus`
|
|
139
|
+
|
|
140
|
+
Map informal status strings to canonical Status values using fuzzy matching:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
function normalizeStatus(raw: unknown): Status {
|
|
144
|
+
if (typeof raw !== "string") return "not_started";
|
|
145
|
+
const s = raw.toLowerCase().trim().replace(/[\s-]/g, "_");
|
|
146
|
+
const aliases: Record<string, Status> = {
|
|
147
|
+
done: "completed",
|
|
148
|
+
complete: "completed",
|
|
149
|
+
completed: "completed",
|
|
150
|
+
finished: "completed",
|
|
151
|
+
active: "in_progress",
|
|
152
|
+
wip: "in_progress",
|
|
153
|
+
in_progress: "in_progress",
|
|
154
|
+
working: "in_progress",
|
|
155
|
+
started: "in_progress",
|
|
156
|
+
pending: "not_started",
|
|
157
|
+
not_started: "not_started",
|
|
158
|
+
todo: "not_started",
|
|
159
|
+
queued: "not_started",
|
|
160
|
+
blocked: "blocked",
|
|
161
|
+
stuck: "blocked",
|
|
162
|
+
waiting: "blocked",
|
|
163
|
+
skipped: "skipped",
|
|
164
|
+
dropped: "skipped",
|
|
165
|
+
deferred: "skipped",
|
|
166
|
+
};
|
|
167
|
+
return aliases[s] ?? "not_started";
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 5. Implement key alias maps
|
|
172
|
+
|
|
173
|
+
Create alias maps so renamed fields still parse correctly:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const TASK_KEY_ALIASES: Record<string, string> = {
|
|
177
|
+
est_hours: "estimated_hours",
|
|
178
|
+
estimated: "estimated_hours",
|
|
179
|
+
actual: "actual_hours",
|
|
180
|
+
act_hours: "actual_hours",
|
|
181
|
+
deps: "dependencies",
|
|
182
|
+
depends_on: "dependencies",
|
|
183
|
+
name: "title",
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const MILESTONE_KEY_ALIASES: Record<string, string> = {
|
|
187
|
+
name: "title",
|
|
188
|
+
target: "target_date",
|
|
189
|
+
due_date: "target_date",
|
|
190
|
+
deadline: "target_date",
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 6. Implement `normalizeStringArray`
|
|
195
|
+
|
|
196
|
+
Handle cases where a single string appears instead of an array:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
function normalizeStringArray(raw: unknown): string[] {
|
|
200
|
+
if (Array.isArray(raw)) return raw.map(String);
|
|
201
|
+
if (typeof raw === "string") return [raw];
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 7. Implement entity normalizers
|
|
207
|
+
|
|
208
|
+
Create normalizer functions for each entity type:
|
|
209
|
+
|
|
210
|
+
- `normalizeProject(raw: unknown): ProjectMetadata` — extracts name, description, version, repository with string defaults
|
|
211
|
+
- `normalizeMilestones(raw: unknown): Milestone[]` — iterates array, applies key aliases, normalizes status and tasks
|
|
212
|
+
- `normalizeTasks(raw: unknown): Task[]` — iterates array, applies key aliases, normalizes status, coerces string arrays for deps/tags
|
|
213
|
+
- `normalizeWorkEntries(raw: unknown): WorkEntry[]` — iterates array, coerces hours to number, defaults date/task/notes
|
|
214
|
+
- `normalizeDocStats(raw: unknown): DocumentationStats` — extracts numeric counts with 0 defaults
|
|
215
|
+
- `normalizeProgress(raw: unknown): ProgressSummary` — extracts numeric summary fields with 0 defaults
|
|
216
|
+
|
|
217
|
+
Each normalizer should use `extractKnown` to separate known fields from extras.
|
|
218
|
+
|
|
219
|
+
### 8. Implement top-level parser with fallback
|
|
220
|
+
|
|
221
|
+
Wire up `parseProgressYaml`:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
export function parseProgressYaml(raw: string): ProgressData {
|
|
225
|
+
try {
|
|
226
|
+
const doc = yaml.load(raw) as Record<string, unknown> | null;
|
|
227
|
+
if (!doc || typeof doc !== "object") {
|
|
228
|
+
return emptyProgressData();
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
project: normalizeProject(doc.project),
|
|
232
|
+
milestones: normalizeMilestones(doc.milestones),
|
|
233
|
+
work_entries: normalizeWorkEntries(doc.work_entries ?? doc.work_log),
|
|
234
|
+
documentation: normalizeDocStats(doc.documentation ?? doc.docs),
|
|
235
|
+
progress: normalizeProgress(doc.progress ?? doc.summary),
|
|
236
|
+
extra: extractTopLevelExtra(doc),
|
|
237
|
+
};
|
|
238
|
+
} catch {
|
|
239
|
+
return emptyProgressData();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 9. Write unit tests
|
|
245
|
+
|
|
246
|
+
Create `app/lib/__tests__/yaml-loader.test.ts` with tests covering:
|
|
247
|
+
|
|
248
|
+
- Complete valid YAML parses correctly
|
|
249
|
+
- Missing sections default gracefully (empty arrays, zero counts)
|
|
250
|
+
- Unknown fields preserved in `extra`
|
|
251
|
+
- Status aliases resolve correctly (`"done"` becomes `"completed"`, `"wip"` becomes `"in_progress"`)
|
|
252
|
+
- Single string coerced to array for dependencies and tags
|
|
253
|
+
- Key aliases work (`est_hours` becomes `estimated_hours`)
|
|
254
|
+
- Completely invalid YAML returns empty ProgressData (no crash)
|
|
255
|
+
- Empty string input returns empty ProgressData
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Verification
|
|
260
|
+
|
|
261
|
+
- [ ] `app/lib/types.ts` exports all interfaces: Status, ExtraFields, ProgressData, ProjectMetadata, Milestone, Task, WorkEntry, DocumentationStats, ProgressSummary
|
|
262
|
+
- [ ] `app/lib/yaml-loader.ts` exports `parseProgressYaml` function
|
|
263
|
+
- [ ] Parser handles complete YAML and returns fully populated ProgressData
|
|
264
|
+
- [ ] Parser handles incomplete YAML with missing sections (defaults to empty arrays/zero counts)
|
|
265
|
+
- [ ] Unknown fields are preserved in `extra` objects at every level
|
|
266
|
+
- [ ] Status aliases resolve correctly (done, wip, active, pending, stuck, etc.)
|
|
267
|
+
- [ ] Single string values coerced to arrays for dependencies and tags
|
|
268
|
+
- [ ] Key aliases resolve correctly (est_hours, deps, name, etc.)
|
|
269
|
+
- [ ] Completely invalid YAML returns empty ProgressData without throwing
|
|
270
|
+
- [ ] All unit tests pass
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Expected Output
|
|
275
|
+
|
|
276
|
+
**Key Files Created**:
|
|
277
|
+
- `app/lib/types.ts`: TypeScript interfaces for the entire progress.yaml data model
|
|
278
|
+
- `app/lib/yaml-loader.ts`: Lenient YAML parser with drift tolerance
|
|
279
|
+
- `app/lib/__tests__/yaml-loader.test.ts`: Unit tests for the parser
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Notes
|
|
284
|
+
|
|
285
|
+
- The parser is intentionally lenient; it should never throw. Any error results in a fallback empty ProgressData
|
|
286
|
+
- The `extra` field pattern is critical: it allows the dashboard to display fields the schema does not yet know about, which is essential for agent-maintained YAML that evolves over time
|
|
287
|
+
- Status normalization covers common agent shorthand; new aliases can be added as they are discovered
|
|
288
|
+
- Key alias maps should be extended if agents consistently use non-standard field names
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
**Next Task**: [Task 3: Build Server API & Data Loading](./task-3-build-server-api-data-loading.md)
|
|
293
|
+
**Related Design Docs**: [Data Model & YAML Parsing](../../design/local.data-model-yaml-parsing.md)
|
|
294
|
+
**Estimated Completion Date**: TBD
|