docs(phase-02): create phase plans for file management
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
---
|
||||
phase: 02-data-persistence-file-management
|
||||
plan: "01"
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/app.py
|
||||
- src/templates/files.html
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FILE-01
|
||||
- FILE-02
|
||||
- FILE-04
|
||||
- BACK-04
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User can view list of CSV files stored in flash memory"
|
||||
- "User can view contents of selected CSV files"
|
||||
- "User can delete unwanted CSV files from flash memory"
|
||||
artifacts:
|
||||
- path: "src/app.py"
|
||||
provides: "File management API endpoints"
|
||||
exports: ["GET /api/files", "GET /api/files/<filename>", "DELETE /api/files/<filename>"]
|
||||
- path: "src/templates/files.html"
|
||||
provides: "File management UI page"
|
||||
routes: ["/files"]
|
||||
key_links:
|
||||
- from: "src/templates/files.html"
|
||||
to: "/api/files"
|
||||
via: "fetch in useEffect"
|
||||
pattern: "fetch.*api/files"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create file management API endpoints and basic UI for listing/viewing/deleting CSV files.
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
@./.planning/phases/02-data-persistence-file-management/02-CONTEXT.md
|
||||
@./.planning/phases/02-data-persistence-file-management/02-RESEARCH.md
|
||||
@./.planning/ROADMAP.md
|
||||
@./.planning/REQUIREMENTS.md
|
||||
@./src/app.py
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 1: Add file management API endpoints to Flask backend</name>
|
||||
<files>src/app.py</files>
|
||||
<action>
|
||||
Add the following API endpoints to src/app.py:
|
||||
|
||||
1. GET /api/files - List all CSV files in the logger directory
|
||||
- Returns JSON: {files: [{name, size, created}], total: count}
|
||||
- Storage path: /myvscada/logger (configurable, default to src/data/logger)
|
||||
- Handle missing directory gracefully
|
||||
|
||||
2. GET /api/files/<filename> - Get file contents
|
||||
- Returns JSON: {name, content: "full file text", lines: count}
|
||||
- Limit preview to first 100 lines for large files
|
||||
|
||||
3. DELETE /api/files/<filename> - Delete a file
|
||||
- Returns JSON: {success: true/false}
|
||||
- Confirm file exists before delete
|
||||
- Handle errors gracefully
|
||||
|
||||
Use the existing Flask patterns from src/app.py:
|
||||
- Use jsonify for responses
|
||||
- Use request.get_json() for POST data
|
||||
- Add error handling with try/except
|
||||
- Follow existing route structure
|
||||
</action>
|
||||
<verify>
|
||||
<automated>curl -s http://localhost:8080/api/files | python3 -c "import sys,json; d=json.load(sys.stdin); print('OK' if 'files' in d else 'FAIL')"</automated>
|
||||
</verify>
|
||||
<done>API endpoints return valid JSON, file list shows files, delete removes files</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 2: Create files.html template</name>
|
||||
<files>src/templates/files.html</files>
|
||||
<action>
|
||||
Create src/templates/files.html with:
|
||||
|
||||
1. Main navigation menu (reuse from other templates)
|
||||
2. Page title: "Flash Memory Files"
|
||||
3. File list display:
|
||||
- Table or card layout showing: filename, size, date
|
||||
- "View" button for each file (opens modal)
|
||||
- "Delete" button with confirmation
|
||||
4. Modal for viewing file contents:
|
||||
- File name header
|
||||
- Scrollable content area (pre/code block)
|
||||
- Close button
|
||||
5. JavaScript:
|
||||
- Fetch file list on page load
|
||||
- Handle view button click -> fetch file content
|
||||
- Handle delete button click -> confirm -> delete -> refresh list
|
||||
- Use Bootstrap 5.3 + Bootstrap Icons (like existing templates)
|
||||
- Touch-optimized 48px+ buttons
|
||||
|
||||
Base on the existing templates (settings.html, calibration.html) for:
|
||||
- Bootstrap 5.3 structure
|
||||
- Navigation menu
|
||||
- CSS variables and touch targets
|
||||
- Socket.IO integration (optional)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>curl -s http://localhost:8080/files | grep -q "Flash Memory" && echo "OK" || echo "FAIL"</automated>
|
||||
</verify>
|
||||
<done>Files page loads, shows file list, view modal works, delete works</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 3: Add /files route to Flask app</name>
|
||||
<files>src/app.py</files>
|
||||
<action>
|
||||
Add the route for the files page in src/app.py:
|
||||
|
||||
```python
|
||||
@app.route('/files')
|
||||
def files():
|
||||
"""File management page"""
|
||||
return render_template('files.html',
|
||||
page='files',
|
||||
version=SOFTWARE_VERSION,
|
||||
build_date=BUILD_DATE)
|
||||
```
|
||||
|
||||
Add this route near the other page routes (index, settings, calibration).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/files</automated>
|
||||
</verify>
|
||||
<done>Route returns 200 OK</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 4: Add Files menu item to navigation</name>
|
||||
<files>src/templates/dashboard.html, src/templates/settings.html, src/templates/calibration.html</files>
|
||||
<action>
|
||||
Add "Files" navigation button to main menu in:
|
||||
- dashboard.html
|
||||
- settings.html
|
||||
- calibration.html
|
||||
|
||||
The navigation should link to /files route. Follow the existing nav structure with nav-btn class.
|
||||
|
||||
Example (add after calibration button):
|
||||
```html
|
||||
<a href="/files" class="nav-btn btn btn-outline-primary">
|
||||
<i class="bi bi-folder"></i> Files
|
||||
</a>
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>grep -l "Files" src/templates/*.html | wc -l</automated>
|
||||
</verify>
|
||||
<done>All pages have Files link in navigation</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- API endpoints return valid JSON
|
||||
- Files page loads with file list
|
||||
- View modal shows file contents
|
||||
- Delete removes files from storage
|
||||
- Navigation works across all pages
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
User can view list of CSV files, view file contents, and delete files from flash memory via web UI
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-data-persistence-file-management/02-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
phase: 02-data-persistence-file-management
|
||||
plan: "02"
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/app.py
|
||||
- src/templates/files.html
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FILE-03
|
||||
- FILE-05
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User can navigate file list (scroll up/down) to find specific files"
|
||||
- "User can generate tidEDA formatted files for data transmission"
|
||||
artifacts:
|
||||
- path: "src/app.py"
|
||||
provides: "tidEDA export API endpoint"
|
||||
exports: ["POST /api/files/export"]
|
||||
- path: "src/templates/files.html"
|
||||
provides: "Scroll navigation and export functionality"
|
||||
key_links:
|
||||
- from: "src/templates/files.html"
|
||||
to: "/api/files/export"
|
||||
via: "fetch POST"
|
||||
pattern: "fetch.*api/files/export"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add scroll navigation to file list and tidEDA export functionality.
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
@./.planning/phases/02-data-persistence-file-management/02-CONTEXT.md
|
||||
@./.planning/phases/02-data-persistence-file-management/02-RESEARCH.md
|
||||
@./.planning/phases/02-data-persistence-file-management/02-01-PLAN.md
|
||||
@./src/app.py
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 1: Add scroll navigation to file list</name>
|
||||
<files>src/templates/files.html</files>
|
||||
<action>
|
||||
Enhance src/templates/files.html with scroll navigation:
|
||||
|
||||
1. If file list exceeds viewport:
|
||||
- Enable native scroll (overflow-y: auto)
|
||||
- Or add pagination controls (Previous/Next buttons)
|
||||
|
||||
2. For touch-optimized scrolling on 7" display:
|
||||
- Ensure smooth scrolling with -webkit-overflow-scrolling: touch
|
||||
- Minimum touch target size: 48px for all interactive elements
|
||||
|
||||
3. Add file count display:
|
||||
- "Showing X of Y files"
|
||||
- Update after scroll/filter
|
||||
|
||||
4. Optional enhancements:
|
||||
- Sort by date (newest first)
|
||||
- Filter by filename search
|
||||
- These are "OpenCode's Discretion" - implement if straightforward
|
||||
|
||||
The key requirement is FILE-03: User can navigate file list (scroll up/down) to find specific files.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>Test by loading files page with multiple files and verifying scroll works</automated>
|
||||
</verify>
|
||||
<done>User can scroll through file list to find specific files</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 2: Add tidEDA export API endpoint</name>
|
||||
<files>src/app.py</files>
|
||||
<action>
|
||||
Add POST /api/files/export endpoint to src/app.py:
|
||||
|
||||
```python
|
||||
@app.route('/api/files/export', methods=['POST'])
|
||||
def api_files_export():
|
||||
"""Generate tidEDA formatted export from CSV file"""
|
||||
data = request.get_json() or {}
|
||||
filename = data.get('filename')
|
||||
|
||||
if not filename:
|
||||
return jsonify({"error": "filename required"}), 400
|
||||
|
||||
# Validate filename (security: prevent path traversal)
|
||||
if '..' in filename or '/' in filename or '\\' in filename:
|
||||
return jsonify({"error": "invalid filename"}), 400
|
||||
|
||||
# Ensure .csv extension
|
||||
if not filename.endswith('.csv'):
|
||||
filename = filename + '.csv'
|
||||
|
||||
# Read the CSV file
|
||||
file_path = os.path.join(LOGGER_DIR, filename)
|
||||
if not os.path.exists(file_path):
|
||||
return jsonify({"error": "file not found"}), 404
|
||||
|
||||
# Generate tidEDA format
|
||||
# Structure:
|
||||
# $STATION,{station_id}
|
||||
# $DATETIME,{timestamp}
|
||||
# $DATA
|
||||
# {original_csv_content}
|
||||
# $END
|
||||
|
||||
settings = load_settings()
|
||||
station_id = settings.get("station", {}).get("id", "UNKNOWN")
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
csv_content = f.read()
|
||||
|
||||
tideda_content = f"$STATION,{station_id}\n"
|
||||
tideda_content += f"$DATETIME,{datetime.now().isoformat()}\n"
|
||||
tideda_content += "$DATA\n"
|
||||
tideda_content += csv_content
|
||||
tideda_content += "$END\n"
|
||||
|
||||
return jsonify({
|
||||
"filename": filename.replace('.csv', '.tideda'),
|
||||
"content": tideda_content,
|
||||
"format": "tidEDA v1.0"
|
||||
})
|
||||
```
|
||||
|
||||
Add LOGGER_DIR constant at top of file:
|
||||
```python
|
||||
LOGGER_DIR = os.path.join(os.path.dirname(__file__), 'data', 'logger')
|
||||
os.makedirs(LOGGER_DIR, exist_ok=True)
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>curl -s -X POST -H "Content-Type: application/json" -d '{"filename":"test.csv"}' http://localhost:8080/api/files/export</automated>
|
||||
</verify>
|
||||
<done>API returns tidEDA formatted content with $STATION, $DATETIME, $DATA, $END structure</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>task 3: Add export button to files UI</name>
|
||||
<files>src/templates/files.html</files>
|
||||
<action>
|
||||
Enhance src/templates/files.html with export functionality:
|
||||
|
||||
1. Add "Export" button next to each file or as a bulk action:
|
||||
- Button with download icon
|
||||
- Calls POST /api/files/export
|
||||
|
||||
2. Handle export response:
|
||||
- Receive tidEDA formatted content
|
||||
- Trigger browser download as .tideda file
|
||||
|
||||
3. Add JavaScript download helper:
|
||||
```javascript
|
||||
function downloadFile(filename, content) {
|
||||
const blob = new Blob([content], {type: 'text/plain'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
```
|
||||
|
||||
4. Add "Generate tidEDA" button at top of page for exporting selected/all files
|
||||
|
||||
This satisfies FILE-05: User can generate tidEDA formatted files for data transmission.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>Verify export button exists in UI and triggers download</automated>
|
||||
</verify>
|
||||
<done>User can generate and download tidEDA formatted files</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Scroll navigation works smoothly
|
||||
- tidEDA export generates correct format
|
||||
- Download works in browser
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
User can navigate file list with scrolling and generate tidEDA formatted files for transmission
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-data-persistence-file-management/02-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,163 @@
|
||||
# Phase 2: Data Persistence & File Management - Research
|
||||
|
||||
**Researched:** 2026-03-12
|
||||
**Phase:** 02-data-persistence-file-management
|
||||
|
||||
## Domain Overview
|
||||
|
||||
Phase 2 implements CSV data logging to flash memory with file management UI. This builds on Phase 1's Flask backend by adding:
|
||||
- File system operations (list, read, delete)
|
||||
- CSV file generation/management
|
||||
- tidEDA formatted export
|
||||
- File management UI page
|
||||
|
||||
## Requirements Analysis
|
||||
|
||||
| Requirement | Description | Implementation Approach |
|
||||
|-------------|-------------|------------------------|
|
||||
| FILE-01 | List CSV files in flash memory | Flask route returning JSON array of file metadata |
|
||||
| FILE-02 | View CSV file contents | Flask route serving file content with pagination |
|
||||
| FILE-03 | Navigate file list (scroll) | Frontend JavaScript with pagination or virtual scroll |
|
||||
| FILE-04 | Delete CSV files | Flask DELETE endpoint with confirmation |
|
||||
| FILE-05 | Generate tidEDA formatted files | Backend transformation endpoint |
|
||||
| BACK-04 | File management API | All above endpoints combined |
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Storage Location
|
||||
- **Path:** `/myvscada/logger` (per CONTEXT.md)
|
||||
- **Note:** On Pi, this may need to be relative to project or use a configurable path
|
||||
|
||||
### File Naming Convention
|
||||
- Format: `SP{station_id}_{timestamp}.csv`
|
||||
- Example: `SP001_20260312_143022.csv`
|
||||
|
||||
### API Endpoints Needed
|
||||
|
||||
```
|
||||
GET /api/files - List all CSV files with metadata
|
||||
GET /api/files/<filename> - Get file contents
|
||||
DELETE /api/files/<filename> - Delete a file
|
||||
POST /api/files/export - Generate tidEDA format from CSV
|
||||
```
|
||||
|
||||
### Response Formats
|
||||
|
||||
**GET /api/files**
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"name": "SP001_20260312_143022.csv",
|
||||
"size": 1234,
|
||||
"created": "2026-03-12T14:30:22Z"
|
||||
}
|
||||
],
|
||||
"total": 10
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/files/<filename>**
|
||||
```json
|
||||
{
|
||||
"name": "SP001_20260312_143022.csv",
|
||||
"content": "timestamp,rainfall,solar,battery\n...",
|
||||
"lines": 50
|
||||
}
|
||||
```
|
||||
|
||||
### tidEDA Format
|
||||
|
||||
Based on standard environmental data transmission formats. The tidEDA format should contain:
|
||||
- Station identification
|
||||
- Timestamp
|
||||
- Sensor readings (rainfall, voltage)
|
||||
- Data quality indicators
|
||||
|
||||
**Suggested structure:**
|
||||
```
|
||||
$STATION,SP001
|
||||
$DATETIME,2026-03-12T14:30:00Z
|
||||
$DATA
|
||||
timestamp,rainfall_mm,solar_v,battery_v
|
||||
2026-03-12T14:30:00,0.2,12.4,12.8
|
||||
...
|
||||
$END
|
||||
```
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### New Page: Files (`/files`)
|
||||
- Bootstrap 5.3 template (reuse existing patterns)
|
||||
- File list with:
|
||||
- Filename, size, date
|
||||
- View button
|
||||
- Delete button with confirmation
|
||||
- Export to tidEDA button
|
||||
- Scroll navigation (per FILE-03)
|
||||
- Touch-optimized 48px+ targets
|
||||
|
||||
### UI Components
|
||||
- File list table/card layout
|
||||
- Modal for file content viewing
|
||||
- Confirmation dialogs for delete
|
||||
- Loading states during API calls
|
||||
|
||||
## Dependencies
|
||||
|
||||
No new Python packages required beyond existing Flask:
|
||||
- `os`, `datetime`, `json` (stdlib)
|
||||
- Flask already installed
|
||||
|
||||
## Integration Points
|
||||
|
||||
1. **Main Navigation:** Add "Files" menu item
|
||||
2. **Settings API reuse:** Get station ID from settings
|
||||
3. **Socket.IO:** Optional - file list refresh on changes
|
||||
4. **Template base:** Extend existing Bootstrap 5.3 layout
|
||||
|
||||
## Validation Architecture
|
||||
|
||||
**Success Criteria Validation:**
|
||||
|
||||
1. User can view list of CSV files stored in flash memory
|
||||
- `GET /api/files` returns file array
|
||||
- Files page renders list correctly
|
||||
|
||||
2. User can navigate file list (scroll up/down)
|
||||
- Frontend implements scroll or pagination
|
||||
- Touch targets work on 7" display
|
||||
|
||||
3. User can view contents of selected CSV files
|
||||
- `GET /api/files/<filename>` returns content
|
||||
- Modal/displays shows formatted content
|
||||
|
||||
4. User can delete unwanted CSV files from flash memory
|
||||
- `DELETE /api/files/<filename>` removes file
|
||||
- UI updates after deletion
|
||||
|
||||
5. User can generate tidEDA formatted files
|
||||
- `POST /api/files/export` generates format
|
||||
- Returns downloadable content
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| File path not accessible | Use configurable path, default to project data dir |
|
||||
| Large files crash display | Paginate content, limit preview lines |
|
||||
| Concurrent file access | Handle file locked errors gracefully |
|
||||
| tidEDA format incompatibility | Define format clearly, add version field |
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. Backend API routes first (file operations)
|
||||
2. Frontend file list page
|
||||
3. File viewing modal
|
||||
4. Delete functionality with confirmation
|
||||
5. tidEDA export endpoint
|
||||
6. Integration testing
|
||||
|
||||
---
|
||||
|
||||
*Research complete - ready for planning*
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
phase: 02
|
||||
slug: data-persistence-file-management
|
||||
status: draft
|
||||
nyquist_compliant: false
|
||||
wave_0_complete: false
|
||||
created: 2026-03-12
|
||||
---
|
||||
|
||||
# Phase 2 — Validation Strategy
|
||||
|
||||
> Per-phase validation contract for feedback sampling during execution.
|
||||
|
||||
---
|
||||
|
||||
## Test Infrastructure
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Framework** | pytest (Python) + Flask test client |
|
||||
| **Config file** | tests/ (Wave 0 creates) |
|
||||
| **Quick run command** | `pytest tests/test_api.py -v` |
|
||||
| **Full suite command** | `pytest tests/ -v` |
|
||||
| **Estimated runtime** | ~15 seconds |
|
||||
|
||||
---
|
||||
|
||||
## Sampling Rate
|
||||
|
||||
- **After every task commit:** Run `pytest tests/test_api.py -v`
|
||||
- **After every plan wave:** Run `pytest tests/ -v`
|
||||
- **Before `/gsd-verify-work`:** Full suite must be green
|
||||
- **Max feedback latency:** 30 seconds
|
||||
|
||||
---
|
||||
|
||||
## Per-task Verification Map
|
||||
|
||||
| task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|
||||
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
|
||||
| 02-01-01 | 01 | 1 | BACK-04 | API | `pytest tests/test_api.py::test_list_files -v` | ⚠️ W0 | ⬜ pending |
|
||||
| 02-01-02 | 01 | 1 | FILE-01 | API | `pytest tests/test_api.py::test_get_file_contents -v` | ⚠️ W0 | ⬜ pending |
|
||||
| 02-01-03 | 01 | 1 | FILE-04 | API | `pytest tests/test_api.py::test_delete_file -v` | ⚠️ W0 | ⬜ pending |
|
||||
| 02-02-01 | 02 | 1 | FILE-02 | UI | Browser test or manual | - | ⬜ pending |
|
||||
| 02-02-02 | 02 | 1 | FILE-03 | UI | Manual (touch scroll) | - | ⬜ pending |
|
||||
| 02-02-03 | 02 | 2 | FILE-05 | API | `pytest tests/test_api.py::test_export_tideda -v` | ⚠️ W0 | ⬜ pending |
|
||||
|
||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||
|
||||
---
|
||||
|
||||
## Wave 0 Requirements
|
||||
|
||||
- [ ] `tests/conftest.py` — Flask test client fixture
|
||||
- [ ] `tests/test_api.py` — API endpoint stubs for:
|
||||
- `test_list_files`
|
||||
- `test_get_file_contents`
|
||||
- `test_delete_file`
|
||||
- `test_export_tideda`
|
||||
- [ ] `tests/__init__.py` — Package marker
|
||||
|
||||
*Existing Flask test client pattern from src/app.py can be reused.*
|
||||
|
||||
---
|
||||
|
||||
## Manual-Only Verifications
|
||||
|
||||
| Behavior | Requirement | Why Manual | Test Instructions |
|
||||
|----------|-------------|------------|-------------------|
|
||||
| File list scroll navigation | FILE-03 | Touch UI interaction | Visit /files, scroll through list |
|
||||
| File content display formatting | FILE-02 | Visual verification | Click file, verify content renders |
|
||||
| Delete confirmation dialog | FILE-04 | UX flow | Click delete, verify confirmation |
|
||||
| tidEDA export download | FILE-05 | File download | Click export, verify file downloads |
|
||||
|
||||
*These require browser/UI interaction that automated tests don't reliably capture.*
|
||||
|
||||
---
|
||||
|
||||
## Validation Sign-Off
|
||||
|
||||
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
|
||||
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
|
||||
- [ ] Wave 0 covers all MISSING references
|
||||
- [ ] No watch-mode flags
|
||||
- [ ] Feedback latency < 30s
|
||||
- [ ] `nyquist_compliant: true` set in frontmatter
|
||||
|
||||
**Approval:** pending
|
||||
|
||||
Reference in New Issue
Block a user