]*href'
+ 'onClick='
+ 'onChange='
+ 'onSubmit='
+ )
+
+ for pattern in "${patterns[@]}"; do
+ # Find lines matching the pattern
+ while IFS= read -r line_num; do
+ # Get the actual line content
+ line=$(sed -n "${line_num}p" "$file")
+
+ # Check if line has data-testid
+ if ! echo "$line" | grep -q 'data-testid'; then
+ echo -e "${RED}✗${NC} Line $line_num: Missing data-testid"
+ echo " $line"
+ ((issues++))
+ fi
+ done < <(grep -n "$pattern" "$file" | cut -d: -f1 || true)
+ done
+
+ return $issues
+}
+
+# Main script
+main() {
+ local total_issues=0
+ local files_to_check=()
+
+ # If files provided as arguments, use those
+ if [ $# -gt 0 ]; then
+ files_to_check=("$@")
+ else
+ # Otherwise, check git-modified .tsx files
+ echo "No files specified, checking git-modified .tsx files..."
+ mapfile -t files_to_check < <(git diff --name-only --diff-filter=ACM | grep '\.tsx$' || true)
+
+ # Also check staged files
+ mapfile -t -O "${#files_to_check[@]}" files_to_check < <(git diff --cached --name-only --diff-filter=ACM | grep '\.tsx$' || true)
+ fi
+
+ # Remove duplicates
+ files_to_check=($(echo "${files_to_check[@]}" | tr ' ' '\n' | sort -u))
+
+ if [ ${#files_to_check[@]} -eq 0 ]; then
+ echo -e "${YELLOW}No .tsx files to check${NC}"
+ exit 0
+ fi
+
+ echo "Checking ${#files_to_check[@]} files for data-testid attributes..."
+ echo
+
+ for file in "${files_to_check[@]}"; do
+ # Skip if file doesn't exist or isn't in frontend directory
+ if [ ! -f "$file" ] || [[ ! "$file" =~ frontend/src/ ]]; then
+ continue
+ fi
+
+ if check_file "$file"; then
+ total_issues=$((total_issues + $?))
+ fi
+ done
+
+ echo
+ if [ $total_issues -eq 0 ]; then
+ echo -e "${GREEN}✓ All checked files have proper data-testid attributes!${NC}"
+ exit 0
+ else
+ echo -e "${RED}✗ Found $total_issues interactive elements missing data-testid${NC}"
+ echo
+ echo "Please add data-testid attributes to all interactive elements:"
+ echo " "
+ echo " "
+ echo
+ echo "See CLAUDE.md for naming conventions."
+ exit 1
+ fi
+}
+
+main "$@"
diff --git a/specs/README.md b/specs/README.md
new file mode 100644
index 00000000..4a7aa205
--- /dev/null
+++ b/specs/README.md
@@ -0,0 +1,101 @@
+# Specifications Directory
+
+This directory contains feature specifications and test case documents for the UShadow project.
+
+## Directory Structure
+
+```
+specs/
+├── features/ # Feature specifications and test cases
+│ ├── {feature}.md # Feature specification
+│ └── {feature}.testcases.md # Test case specifications
+└── templates/ # Document templates
+ ├── spec-template.md # Feature spec template
+ └── testcase-template.md # Test case template
+```
+
+## Workflow
+
+### 1. Create Specification
+
+Use the `/spec` command to create a feature specification from conversation:
+
+```
+/spec user-authentication
+```
+
+This creates `specs/features/user-authentication.md` with structured requirements.
+
+### 2. Generate Test Cases
+
+Use the `/qa-test-cases` command to generate test scenarios:
+
+```
+/qa-test-cases user-authentication
+```
+
+This creates `specs/features/user-authentication.testcases.md` with comprehensive test coverage.
+
+### 3. Automate Tests
+
+Use the `/automate-tests` command to generate executable test code:
+
+```
+/automate-tests user-authentication
+```
+
+This generates:
+- pytest tests in `ushadow/backend/tests/`
+- Robot Framework tests in `robot_tests/api/`
+- Playwright E2E tests in `frontend/e2e/`
+
+## Test Automation Agents
+
+The test automation workflow uses three specialized agents:
+
+### spec-agent
+Extracts requirements from feature discussions and creates structured specification documents.
+
+**Invoked by**: `/spec` command
+
+**Output**: `specs/features/{feature}.md`
+
+### qa-agent
+Generates comprehensive test case specifications from feature specs, including happy paths, edge cases, and negative tests.
+
+**Invoked by**: `/qa-test-cases` command
+
+**Output**: `specs/features/{feature}.testcases.md`
+
+### automation-agent
+Generates executable test code in appropriate frameworks (pytest, Robot Framework, Playwright) with correct test level and secret categorization.
+
+**Invoked by**: `/automate-tests` command
+
+**Output**: Test files in appropriate directories
+
+## Test Level Decision
+
+The automation-agent automatically determines the appropriate test level:
+
+| What's Being Tested | Framework | Location |
+|---------------------|-----------|----------|
+| Individual function/class logic | pytest (unit) | `ushadow/backend/tests/` |
+| API endpoint behavior | Robot Framework | `robot_tests/api/` |
+| Service integration | pytest (integration) | `ushadow/backend/tests/integration/` |
+| Frontend component logic | Playwright CT | `frontend/tests/` |
+| Full user workflow | Playwright E2E | `frontend/e2e/` |
+
+## Secret Categorization
+
+All pytest tests are marked with secret requirements:
+
+- `@pytest.mark.no_secrets` - Can run in PR CI without secrets
+- `@pytest.mark.requires_secrets` - Only runs when manually triggered with secrets configured
+
+This allows PRs to run fast feedback tests while protecting API keys.
+
+## References
+
+- [Testing Strategy](../docs/TESTING_STRATEGY.md)
+- [Frontend Testing Guide](../CLAUDE.md#frontend-testing-data-testid-and-playwright-pom)
diff --git a/specs/features/memory-feedback.md b/specs/features/memory-feedback.md
new file mode 100644
index 00000000..10c25857
--- /dev/null
+++ b/specs/features/memory-feedback.md
@@ -0,0 +1,405 @@
+# Example: Memory Feedback Feature
+
+This document demonstrates the test automation workflow using a real feature.
+
+## Feature Request
+
+**User Request**: "We want to be able to easily and quickly indicate if a memory is true, false or almost (where it can be corrected). The updated info will make it's way to the memory server where the facts can get updated."
+
+## Step 1: Run `/spec` Command
+
+The spec-agent would analyze this request and create a structured specification.
+
+Let me simulate what the spec-agent would produce:
+
+---
+
+# Feature Specification: Memory Feedback System
+
+**Created**: 2026-01-17
+**Status**: 📝 Draft
+**Priority**: High
+**Target Release**: v0.2.0
+
+---
+
+## Overview
+
+Users need a way to provide quick feedback on memories (facts) stored in the system, indicating whether they are correct, incorrect, or need correction. This feedback will be propagated to the memory server to improve fact accuracy over time.
+
+---
+
+## User Stories
+
+### Primary User Story
+
+**As a** user reviewing AI-generated memories
+**I want** to quickly mark memories as true, false, or needing correction
+**So that** the system learns and improves its fact accuracy over time
+
+### Additional User Stories
+
+1. **As a** user, **I want** to provide corrected information when a memory is almost correct, **so that** the system has the accurate version
+2. **As a** developer, **I want** feedback to propagate to the memory server automatically, **so that** the knowledge base stays up-to-date
+
+---
+
+## Functional Requirements
+
+### FR-001: Memory Feedback UI
+
+**Priority**: Must Have
+**Description**: Provide UI controls to mark a memory as true, false, or "almost" (needs correction)
+
+**Acceptance Criteria**:
+- [ ] Each memory display has three action buttons: True, False, Almost
+- [ ] Clicking True marks memory as verified
+- [ ] Clicking False marks memory as incorrect
+- [ ] Clicking Almost opens correction interface
+- [ ] Actions have visual feedback (confirmation, loading state)
+- [ ] Actions are accessible (keyboard shortcuts optional)
+
+**Dependencies**:
+- Memory display component exists
+
+### FR-002: Correction Input
+
+**Priority**: Must Have
+**Description**: When user selects "Almost", provide interface to input corrected information
+
+**Acceptance Criteria**:
+- [ ] Modal/inline editor appears when "Almost" is clicked
+- [ ] Shows original memory text
+- [ ] Provides text field for corrected version
+- [ ] Has submit and cancel buttons
+- [ ] Validates that correction is not empty
+- [ ] Shows feedback on successful submission
+
+**Dependencies**:
+- FR-001
+
+### FR-003: Feedback API Endpoint
+
+**Priority**: Must Have
+**Description**: Backend API to receive and process memory feedback
+
+**Acceptance Criteria**:
+- [ ] POST /api/memories/{memory_id}/feedback endpoint exists
+- [ ] Accepts feedback type: "true", "false", "correction"
+- [ ] Accepts optional corrected_text for "correction" type
+- [ ] Validates memory_id exists
+- [ ] Requires authentication
+- [ ] Returns 200 on success with updated memory status
+- [ ] Returns 400 for invalid input
+- [ ] Returns 404 if memory doesn't exist
+
+**Dependencies**:
+- Memory storage/database
+
+### FR-004: Memory Server Integration
+
+**Priority**: Must Have
+**Description**: Propagate feedback to memory server for fact updates
+
+**Acceptance Criteria**:
+- [ ] API endpoint sends feedback to memory server via HTTP/gRPC
+- [ ] Handles memory server connection failures gracefully
+- [ ] Retries failed updates with exponential backoff
+- [ ] Logs all feedback attempts
+- [ ] Updates local memory status regardless of memory server status
+- [ ] Queues feedback if memory server is unavailable
+
+**Dependencies**:
+- Memory server API available
+- FR-003
+
+### FR-005: Feedback History
+
+**Priority**: Should Have
+**Description**: Track feedback history for each memory
+
+**Acceptance Criteria**:
+- [ ] Store timestamp of each feedback action
+- [ ] Store user who provided feedback
+- [ ] Store feedback type and correction text
+- [ ] Allow retrieval of feedback history per memory
+- [ ] Show feedback count in memory display (e.g., "Verified by 3 users")
+
+**Dependencies**:
+- Database schema for feedback history
+
+---
+
+## Non-Functional Requirements
+
+### NFR-001: Response Time
+
+**Category**: Performance
+**Description**: Feedback submission should feel instant
+
+**Acceptance Criteria**:
+- [ ] API responds within 200ms for 95% of requests
+- [ ] UI shows loading state if response takes >500ms
+- [ ] Background sync to memory server doesn't block user
+
+### NFR-002: Reliability
+
+**Category**: Reliability
+**Description**: Feedback should never be lost, even if memory server is down
+
+**Acceptance Criteria**:
+- [ ] Feedback is persisted locally before sending to memory server
+- [ ] Failed transmissions are retried automatically
+- [ ] Admin dashboard shows failed sync queue
+
+### NFR-003: Security
+
+**Category**: Security
+**Description**: Only authenticated users can provide feedback
+
+**Acceptance Criteria**:
+- [ ] All feedback endpoints require valid JWT token
+- [ ] Rate limiting: max 100 feedback actions per user per hour
+- [ ] Input sanitization on corrected text
+
+---
+
+## User Interface / API Design
+
+### Endpoints
+
+```http
+POST /api/memories/{memory_id}/feedback
+Authorization: Bearer {jwt_token}
+
+Request:
+{
+ "feedback_type": "true" | "false" | "correction",
+ "corrected_text": "string (required if feedback_type=correction)"
+}
+
+Response (200 OK):
+{
+ "memory_id": "string",
+ "status": "verified" | "disputed" | "corrected",
+ "updated_at": "timestamp",
+ "synced_to_server": boolean
+}
+
+Error Responses:
+- 400 Bad Request: Invalid feedback_type or missing corrected_text
+- 401 Unauthorized: Missing or invalid token
+- 404 Not Found: Memory doesn't exist
+- 429 Too Many Requests: Rate limit exceeded
+```
+
+```http
+GET /api/memories/{memory_id}/feedback-history
+Authorization: Bearer {jwt_token}
+
+Response (200 OK):
+{
+ "memory_id": "string",
+ "feedback_count": {
+ "true": 5,
+ "false": 1,
+ "correction": 2
+ },
+ "history": [
+ {
+ "user_id": "string",
+ "feedback_type": "true",
+ "timestamp": "timestamp"
+ }
+ ]
+}
+```
+
+### UI Components
+
+**MemoryFeedbackButtons Component**:
+- Three icon buttons: ✓ (True), ✗ (False), ✏ (Almost)
+- Hover states showing tooltips
+- Active state when feedback given
+- data-testid: `memory-feedback-true`, `memory-feedback-false`, `memory-feedback-almost`
+
+**CorrectionModal Component**:
+- Modal dialog with original text display
+- Textarea for corrected version
+- Submit and Cancel buttons
+- data-testid: `correction-modal`, `correction-text-field`, `correction-submit`
+
+**User Flow**:
+1. User views memory in UI
+2. User clicks True/False/Almost button
+3. If Almost:
+ a. Correction modal appears
+ b. User enters corrected text
+ c. User clicks Submit
+4. System shows success confirmation
+5. Background: API sends feedback to backend
+6. Background: Backend syncs to memory server
+
+---
+
+## Data Model
+
+### New Entity: MemoryFeedback
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| id | ObjectId | Yes | Unique identifier |
+| memory_id | ObjectId | Yes | Reference to memory |
+| user_id | ObjectId | Yes | User who gave feedback |
+| feedback_type | enum | Yes | "true", "false", "correction" |
+| corrected_text | string | No | Only for correction type |
+| created_at | timestamp | Yes | When feedback was given |
+| synced_to_server | boolean | Yes | Whether sent to memory server |
+| sync_attempts | number | Yes | Number of sync retries |
+| last_sync_error | string | No | Error message if sync failed |
+
+**Indexes**:
+- memory_id: Query feedback for specific memory
+- user_id + created_at: User activity tracking
+- synced_to_server: Find pending syncs
+
+### Modified Entity: Memory
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| status | enum | Yes | "unverified", "verified", "disputed", "corrected" |
+| verification_count | number | Yes | Number of "true" feedbacks |
+| dispute_count | number | Yes | Number of "false" feedbacks |
+| last_updated | timestamp | Yes | Last feedback timestamp |
+
+---
+
+## Business Logic
+
+### Validation Rules
+
+1. **feedback_type**: Must be one of: "true", "false", "correction"
+2. **corrected_text**: Required when feedback_type="correction", max 1000 characters
+3. **memory_id**: Must exist in database
+4. **Rate limiting**: Max 100 feedback actions per user per hour
+
+### Status Calculation
+
+```python
+def calculate_memory_status(memory):
+ if memory.verification_count >= 3:
+ return "verified"
+ elif memory.dispute_count >= 2:
+ return "disputed"
+ elif memory.has_corrections:
+ return "corrected"
+ else:
+ return "unverified"
+```
+
+---
+
+## Integration Points
+
+### External Services
+
+| Service | Purpose | Authentication | Requires Secrets? |
+|---------|---------|----------------|-------------------|
+| Memory Server | Fact storage and updates | API Key | Yes |
+
+### Internal Services
+
+| Service | Purpose |
+|---------|---------|
+| Auth Service | User authentication |
+| Database (MongoDB) | Store feedback and memories |
+
+---
+
+## Security Considerations
+
+### Authentication/Authorization
+
+- [x] Who can access: Authenticated users only
+- [x] What permissions: Any authenticated user can provide feedback
+- [x] How verified: JWT token validation
+
+### Data Protection
+
+- [x] Sensitive data: User feedback, corrected memories
+- [x] Protection: Standard HTTPS, JWT authentication
+- [x] Compliance: User feedback is considered user-generated content
+
+### Input Validation
+
+- [x] Corrected text sanitized to prevent XSS
+- [x] Memory ID validated to prevent injection
+- [x] Rate limiting prevents abuse
+
+---
+
+## Error Handling
+
+### Expected Error Scenarios
+
+| Scenario | Error Code | Message | User Action |
+|----------|------------|---------|-------------|
+| Invalid memory_id | 404 | "Memory not found" | Check memory exists |
+| Missing corrected_text | 400 | "Correction text required" | Provide text |
+| Rate limit exceeded | 429 | "Too many requests, try again later" | Wait before retrying |
+| Memory server down | 200* | "Feedback saved, will sync when server available" | None (transparent) |
+
+*Note: User-facing success even if memory server sync fails (queued for retry)
+
+---
+
+## Testing Considerations
+
+### Test Data Requirements
+
+- Sample memories with different statuses
+- Multiple users for concurrent feedback testing
+- Mock memory server API responses
+
+### Test Environment
+
+- Backend API server running
+- MongoDB instance
+- Mock memory server (for integration tests)
+- Frontend UI (for E2E tests)
+
+---
+
+## Open Questions
+
+1. Should users be able to see who verified/disputed a memory?
+2. What happens if multiple users provide conflicting corrections?
+3. Should there be a review/approval process for corrections before syncing?
+4. How should we handle feedback on already-deleted memories?
+
+---
+
+## Out of Scope
+
+- Memory server implementation (assumes it exists)
+- Admin interface for reviewing disputed memories
+- Batch feedback operations
+- Undo feedback functionality
+
+---
+
+## References
+
+- Memory Server API Documentation: (to be linked)
+- Authentication System: `docs/AUTH.md`
+
+---
+
+## Approval
+
+- [ ] Product Owner
+- [ ] Tech Lead
+- [ ] QA Lead
+
+**Approved By**: _______________
+**Date**: _______________
diff --git a/specs/features/memory-feedback.testcases.md b/specs/features/memory-feedback.testcases.md
new file mode 100644
index 00000000..498ff8d9
--- /dev/null
+++ b/specs/features/memory-feedback.testcases.md
@@ -0,0 +1,578 @@
+# Test Cases: Memory Feedback System
+
+**Source Specification**: `specs/features/memory-feedback.md`
+**Generated**: 2026-01-17
+**Status**: ⏳ Pending Review
+
+---
+
+## Test Summary
+
+| Metric | Count |
+|--------|-------|
+| Total Test Cases | 19 |
+| Critical Priority | 6 |
+| High Priority | 9 |
+| Medium Priority | 4 |
+| Unit Tests | 6 |
+| Integration Tests | 5 |
+| API Tests | 5 |
+| E2E Tests | 3 |
+| Requires Secrets | 4 |
+| No Secrets Required | 15 |
+
+---
+
+## Unit Tests (6 tests - all no_secrets)
+
+### TC-MF-001: Validate Feedback Type
+
+**Type**: Unit
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Verify that feedback_type validation only accepts valid values
+
+**Preconditions**
+- Validation function exists
+
+**Test Steps**
+1. Call validation with feedback_type="true"
+2. Call validation with feedback_type="false"
+3. Call validation with feedback_type="correction"
+4. Call validation with feedback_type="invalid"
+5. Call validation with feedback_type=""
+6. Call validation with feedback_type=null
+
+**Expected Results**
+- Steps 1-3: Validation passes
+- Steps 4-6: Validation fails with appropriate error message
+
+**Test Data**
+```json
+{
+ "valid_types": ["true", "false", "correction"],
+ "invalid_types": ["invalid", "", null, "TRUE", "False", 123]
+}
+```
+
+---
+
+### TC-MF-002: Validate Corrected Text Length
+
+**Type**: Unit
+**Priority**: High
+**Requires Secrets**: No
+
+**Description**
+Verify corrected_text length validation (1-2000 characters)
+
+**Test Steps**
+1. Validate empty string ("")
+2. Validate 1 character string
+3. Validate 2000 character string
+4. Validate 2001 character string
+5. Validate null/undefined
+
+**Expected Results**
+- Empty, null, undefined: Fail (required for correction type)
+- 1 char: Pass
+- 2000 chars: Pass
+- 2001 chars: Fail (exceeds max length)
+
+---
+
+### TC-MF-003: Sanitize Corrected Text for XSS
+
+**Type**: Unit
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Ensure user input is sanitized to prevent XSS attacks
+
+**Test Steps**
+1. Input: ``
+2. Input: `
`
+3. Input: `javascript:alert('xss')`
+4. Input: Normal text with HTML entities: `Test & "quotes"
`
+
+**Expected Results**
+- All malicious scripts are escaped or stripped
+- Safe HTML entities are properly encoded
+- Plain text is preserved
+
+---
+
+### TC-MF-004: Calculate Memory Status - Verified
+
+**Type**: Unit
+**Priority**: High
+**Requires Secrets**: No
+
+**Description**
+Verify status calculation when memory has >= 3 true feedbacks
+
+**Test Data**
+```json
+{
+ "feedback_summary": {"true": 3, "false": 0, "correction": 0}
+}
+```
+
+**Expected Results**
+- Status = "verified"
+
+---
+
+### TC-MF-005: Calculate Memory Status - Disputed
+
+**Type**: Unit
+**Priority**: High
+**Requires Secrets**: No
+
+**Description**
+Verify status calculation when memory has >= 2 false feedbacks
+
+**Test Data**
+```json
+{
+ "feedback_summary": {"true": 1, "false": 2, "correction": 0}
+}
+```
+
+**Expected Results**
+- Status = "disputed"
+
+---
+
+### TC-MF-006: Calculate Memory Status - Corrected
+
+**Type**: Unit
+**Priority**: Medium
+**Requires Secrets**: No
+
+**Description**
+Verify status calculation when memory has corrections
+
+**Test Data**
+```json
+{
+ "feedback_summary": {"true": 1, "false": 0, "correction": 1}
+}
+```
+
+**Expected Results**
+- Status = "corrected"
+
+---
+
+## Integration Tests (5 tests - 3 require secrets)
+
+### TC-MF-007: Store Feedback in Database
+
+**Type**: Integration
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Verify feedback is correctly stored in memory_feedback collection
+
+**Preconditions**
+- MongoDB test database running
+- Test user authenticated
+- Test memory exists
+
+**Test Steps**
+1. Submit feedback with type="true"
+2. Query memory_feedback collection
+3. Verify document created with correct fields
+
+**Expected Results**
+- Document exists with:
+ - memory_id (correct ObjectId)
+ - user_id (correct ObjectId)
+ - feedback_type = "true"
+ - created_at (timestamp)
+ - synced_to_server = false
+ - sync_attempts = 0
+
+---
+
+### TC-MF-008: Update Memory Feedback Summary
+
+**Type**: Integration
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Verify memory document feedback_summary is updated when feedback is submitted
+
+**Preconditions**
+- Test memory exists with feedback_summary = {true: 0, false: 0, correction: 0}
+
+**Test Steps**
+1. Submit "true" feedback
+2. Query memory document
+3. Submit another "true" feedback
+4. Query memory document again
+
+**Expected Results**
+- After step 2: feedback_summary.true = 1
+- After step 4: feedback_summary.true = 2
+
+---
+
+### TC-MF-009: Memory Server Sync Success
+
+**Type**: Integration
+**Priority**: High
+**Requires Secrets**: **Yes** (MEMORY_SERVER_API_KEY)
+
+**Description**
+Verify successful sync to memory server
+
+**Preconditions**
+- Memory server API accessible
+- Valid MEMORY_SERVER_API_KEY configured
+
+**Test Steps**
+1. Submit feedback
+2. Background job processes sync queue
+3. Verify HTTP request sent to memory server
+4. Verify feedback document updated
+
+**Expected Results**
+- HTTP POST sent to memory server with feedback data
+- Response 200 OK received
+- feedback.synced_to_server = true
+- feedback.sync_attempts = 1
+
+---
+
+### TC-MF-010: Memory Server Sync Failure with Retry
+
+**Type**: Integration
+**Priority**: High
+**Requires Secrets**: **Yes** (MEMORY_SERVER_API_KEY)
+
+**Description**
+Verify retry mechanism when memory server is unavailable
+
+**Preconditions**
+- Memory server mock returns 503 error
+
+**Test Steps**
+1. Submit feedback
+2. First sync attempt (mock returns 503)
+3. Verify retry scheduled with exponential backoff
+4. Second sync attempt succeeds (mock returns 200)
+
+**Expected Results**
+- After step 2:
+ - synced_to_server = false
+ - sync_attempts = 1
+ - last_sync_error contains error message
+- After step 4:
+ - synced_to_server = true
+ - sync_attempts = 2
+
+---
+
+### TC-MF-011: Sync Queue Persistence
+
+**Type**: Integration
+**Priority**: Medium
+**Requires Secrets**: **Yes** (requires mock memory server)
+
+**Description**
+Verify sync queue survives service restart
+
+**Test Steps**
+1. Submit 5 feedbacks
+2. Mock memory server as unavailable
+3. Verify 5 items in sync queue
+4. Restart backend service
+5. Verify 5 items still in queue
+6. Bring memory server back online
+7. Verify all 5 synced
+
+**Expected Results**
+- Sync queue persisted across restart
+- All feedbacks eventually synced when server available
+
+---
+
+## API Tests (5 tests - 1 requires secrets)
+
+### TC-MF-012: Submit True Feedback via API
+
+**Type**: API
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Test POST /api/memories/{id}/feedback with feedback_type="true"
+
+**Preconditions**
+- User authenticated (valid JWT)
+- Memory exists in database
+
+**Test Steps**
+1. POST /api/memories/{memory_id}/feedback
+ ```json
+ {
+ "feedback_type": "true"
+ }
+ ```
+
+**Expected Results**
+- Status: 200 OK
+- Response body:
+ ```json
+ {
+ "memory_id": "{id}",
+ "status": "unverified",
+ "feedback_count": {"true": 1, "false": 0, "correction": 0},
+ "updated_at": "{timestamp}",
+ "synced_to_server": false
+ }
+ ```
+
+---
+
+### TC-MF-013: Submit Correction via API
+
+**Type**: API
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Test POST /api/memories/{id}/feedback with correction
+
+**Test Steps**
+1. POST /api/memories/{memory_id}/feedback
+ ```json
+ {
+ "feedback_type": "correction",
+ "corrected_text": "The meeting was on Tuesday, not Monday"
+ }
+ ```
+
+**Expected Results**
+- Status: 200 OK
+- Response contains memory_id, status="corrected"
+- Feedback stored with corrected_text
+
+---
+
+### TC-MF-014: API Validation - Missing Corrected Text
+
+**Type**: API
+**Priority**: High
+**Requires Secrets**: No
+
+**Description**
+Verify API returns 400 when corrected_text is missing for correction type
+
+**Test Steps**
+1. POST /api/memories/{memory_id}/feedback
+ ```json
+ {
+ "feedback_type": "correction"
+ }
+ ```
+
+**Expected Results**
+- Status: 400 Bad Request
+- Error message: "corrected_text is required when feedback_type is 'correction'"
+
+---
+
+### TC-MF-015: API Validation - Invalid Memory ID
+
+**Type**: API
+**Priority**: High
+**Requires Secrets**: No
+
+**Description**
+Verify API returns 404 for non-existent memory
+
+**Test Steps**
+1. POST /api/memories/000000000000000000000000/feedback
+ ```json
+ {
+ "feedback_type": "true"
+ }
+ ```
+
+**Expected Results**
+- Status: 404 Not Found
+- Error message: "Memory not found"
+
+---
+
+### TC-MF-016: API Rate Limiting
+
+**Type**: API
+**Priority**: Medium
+**Requires Secrets**: No
+
+**Description**
+Verify rate limiting (max 100 feedbacks per user per hour)
+
+**Test Steps**
+1. Submit 100 feedback requests rapidly
+2. Submit 101st request
+
+**Expected Results**
+- Requests 1-100: Success (200 OK)
+- Request 101: 429 Too Many Requests
+- Error includes retry-after information
+
+---
+
+## E2E Tests (3 tests - all no_secrets)
+
+### TC-MF-017: User Marks Memory as True
+
+**Type**: E2E
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Test complete workflow for marking memory as true via UI
+
+**Preconditions**
+- User logged in
+- Memory visible in UI
+
+**Test Steps**
+1. Navigate to page with memory display
+2. Hover over memory to reveal feedback buttons
+3. Click "True" button (data-testid="memory-feedback-true")
+4. Verify success animation/message appears
+5. Verify button state changes to "active/selected"
+
+**Expected Results**
+- Button click triggers API call
+- Success feedback shown to user
+- Button visual state updates
+- Memory status may update if threshold reached
+
+---
+
+### TC-MF-018: User Provides Correction
+
+**Type**: E2E
+**Priority**: Critical
+**Requires Secrets**: No
+
+**Description**
+Test complete correction workflow via UI
+
+**Preconditions**
+- User logged in
+- Memory visible
+
+**Test Steps**
+1. Click "Almost" button (data-testid="memory-feedback-almost")
+2. Verify correction modal opens (data-testid="correction-modal")
+3. Verify original text displayed
+4. Enter corrected text in field (data-testid="correction-text-field")
+5. Click Submit (data-testid="correction-submit")
+6. Verify success message
+7. Verify modal closes
+
+**Expected Results**
+- Modal opens with original text visible
+- User can type correction
+- Submit sends API request
+- Success feedback shown
+- Modal auto-closes on success
+
+---
+
+### TC-MF-019: Cancel Correction Modal
+
+**Type**: E2E
+**Priority**: Medium
+**Requires Secrets**: No
+
+**Description**
+Verify user can cancel correction without submitting
+
+**Test Steps**
+1. Click "Almost" button
+2. Modal opens
+3. Type some text in correction field
+4. Click Cancel (data-testid="correction-cancel")
+
+**Expected Results**
+- Modal closes
+- No API request sent
+- Memory state unchanged
+- User can retry if desired
+
+---
+
+## Test Coverage Matrix
+
+| Requirement | Test Cases | Coverage |
+|-------------|-----------|----------|
+| FR-001: Feedback UI Controls | TC-MF-017, TC-MF-018, TC-MF-019 | ✅ Happy Path, ⚠️ Edge Cases (cancel) |
+| FR-002: Correction Input | TC-MF-018, TC-MF-019 | ✅ Happy Path, ⚠️ Edge Cases |
+| FR-003: Feedback API | TC-MF-012, TC-MF-013, TC-MF-014, TC-MF-015, TC-MF-016 | ✅ Happy Path, ⚠️ Edge Cases, ❌ Negative |
+| FR-004: Memory Server Sync | TC-MF-009, TC-MF-010, TC-MF-011 | ✅ Happy Path, ❌ Failure scenarios |
+| FR-005: Feedback History | (Not yet covered - future enhancement) | ⚠️ Partial |
+| NFR-001: Performance | (Implicit in API response time requirements) | ⚠️ Manual verification |
+| NFR-002: Reliability | TC-MF-010, TC-MF-011 | ✅ Retry logic, queue persistence |
+| NFR-003: Security | TC-MF-003, TC-MF-016 | ✅ XSS prevention, ❌ Rate limiting |
+
+---
+
+## Secret Requirements Summary
+
+**Tests Requiring Secrets (4 tests)**:
+- TC-MF-009: Memory Server Sync Success
+- TC-MF-010: Memory Server Sync Failure with Retry
+- TC-MF-011: Sync Queue Persistence
+
+These tests require `MEMORY_SERVER_API_KEY` environment variable.
+
+**Tests Without Secrets (15 tests)**:
+- All unit tests (6)
+- Most integration tests (2 of 5)
+- All API tests (5)
+- All E2E tests (3)
+
+**CI/CD Strategy**:
+- 79% of tests (15/19) can run on every PR without secrets
+- Only 21% (4/19) require manual trigger with secrets
+
+---
+
+## Review Checklist
+
+Before approving for automation:
+
+- [x] All functional requirements have test cases
+- [x] Happy path scenarios covered
+- [x] Edge cases identified (empty input, validation, cancellation)
+- [x] Negative tests included (invalid data, missing fields, non-existent resources)
+- [x] Test data is realistic and sufficient
+- [x] Dependencies are documented
+- [x] Security considerations addressed (XSS, rate limiting)
+- [x] Secret requirements clearly marked
+
+---
+
+## Approval
+
+- [ ] QA Lead Approval
+- [ ] Product Owner Approval
+- [ ] Ready for Automation
+
+**Approved By**: _______________
+**Date**: _______________
diff --git a/ushadow/backend/pyproject.toml b/ushadow/backend/pyproject.toml
index 3c6f7be4..977a132d 100644
--- a/ushadow/backend/pyproject.toml
+++ b/ushadow/backend/pyproject.toml
@@ -90,3 +90,17 @@ ignore = ["E501"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
+
+# Custom markers for test categorization
+markers = [
+ "unit: Unit tests that don't require external dependencies",
+ "integration: Integration tests that require services (DB, Redis, etc.)",
+ "e2e: End-to-end tests that test full workflows",
+ "requires_secrets: Tests that require API keys or secrets (skipped in CI)",
+ "no_secrets: Tests that run without any secrets (safe for PR checks)",
+ "requires_backend: Tests that need backend services running",
+ "requires_frontend: Tests that need frontend running",
+]
+
+# Default: run only tests without secrets in CI
+addopts = "-v --strict-markers"
diff --git a/ushadow/backend/tests/integration/test_memory_server_sync.py b/ushadow/backend/tests/integration/test_memory_server_sync.py
new file mode 100644
index 00000000..2c1acc46
--- /dev/null
+++ b/ushadow/backend/tests/integration/test_memory_server_sync.py
@@ -0,0 +1,193 @@
+"""
+Integration tests for memory server synchronization.
+
+Generated by automation-agent from test cases in:
+specs/features/memory-feedback.testcases.md
+
+Test Cases Covered:
+- TC-MF-009: Memory Server Sync Success (REQUIRES SECRETS)
+- TC-MF-010: Memory Server Sync Failure with Retry (REQUIRES SECRETS)
+- TC-MF-011: Sync Queue Persistence (REQUIRES SECRETS)
+"""
+
+import os
+from unittest.mock import AsyncMock, patch
+
+import pytest
+
+
+# TC-MF-009: Memory Server Sync Success
+@pytest.mark.integration
+@pytest.mark.requires_secrets
+async def test_memory_server_sync_success():
+ """
+ Test Case: TC-MF-009
+
+ Steps:
+ 1. Submit feedback
+ 2. Background job processes sync queue
+ 3. Verify HTTP request sent to memory server
+ 4. Verify feedback document updated
+
+ Expected:
+ - HTTP POST sent to memory server
+ - Response 200 OK
+ - feedback.synced_to_server = true
+ """
+ from src.services.memory_feedback import MemoryFeedbackService
+ from src.services.memory_server_sync import MemoryServerSyncService
+
+ # Skip if no API key configured
+ api_key = os.getenv("MEMORY_SERVER_API_KEY")
+ if not api_key:
+ pytest.skip("MEMORY_SERVER_API_KEY not configured")
+
+ # Arrange
+ feedback_service = MemoryFeedbackService()
+ sync_service = MemoryServerSyncService()
+
+ # Create test feedback
+ feedback_id = await feedback_service.submit_feedback(
+ memory_id="507f1f77bcf86cd799439011",
+ user_id="507f1f77bcf86cd799439012",
+ feedback_type="true",
+ )
+
+ # Act
+ result = await sync_service.sync_feedback(feedback_id)
+
+ # Assert
+ assert result.success is True
+ assert result.response_status == 200
+
+ # Verify feedback updated
+ feedback = await feedback_service.get_feedback(feedback_id)
+ assert feedback.synced_to_server is True
+ assert feedback.sync_attempts == 1
+ assert feedback.last_sync_error is None
+
+
+# TC-MF-010: Memory Server Sync Failure with Retry
+@pytest.mark.integration
+@pytest.mark.requires_secrets
+async def test_memory_server_sync_retry_on_failure():
+ """
+ Test Case: TC-MF-010
+
+ Steps:
+ 1. Submit feedback
+ 2. First sync attempt fails (503)
+ 3. Verify retry scheduled
+ 4. Second attempt succeeds
+
+ Expected:
+ - After failure: synced_to_server=false, sync_attempts=1, error logged
+ - After success: synced_to_server=true, sync_attempts=2
+ """
+ from src.services.memory_feedback import MemoryFeedbackService
+ from src.services.memory_server_sync import MemoryServerSyncService
+
+ # Skip if no API key
+ api_key = os.getenv("MEMORY_SERVER_API_KEY")
+ if not api_key:
+ pytest.skip("MEMORY_SERVER_API_KEY not configured")
+
+ # Arrange
+ feedback_service = MemoryFeedbackService()
+ sync_service = MemoryServerSyncService()
+
+ feedback_id = await feedback_service.submit_feedback(
+ memory_id="507f1f77bcf86cd799439011",
+ user_id="507f1f77bcf86cd799439012",
+ feedback_type="false",
+ )
+
+ # Act - First attempt (mock 503 error)
+ with patch.object(sync_service, "_make_request") as mock_request:
+ mock_request.return_value = AsyncMock(status_code=503)
+ result_1 = await sync_service.sync_feedback(feedback_id)
+
+ # Assert - After first failure
+ assert result_1.success is False
+ feedback = await feedback_service.get_feedback(feedback_id)
+ assert feedback.synced_to_server is False
+ assert feedback.sync_attempts == 1
+ assert feedback.last_sync_error is not None
+
+ # Act - Second attempt (succeeds)
+ result_2 = await sync_service.sync_feedback(feedback_id)
+
+ # Assert - After success
+ assert result_2.success is True
+ feedback = await feedback_service.get_feedback(feedback_id)
+ assert feedback.synced_to_server is True
+ assert feedback.sync_attempts == 2
+
+
+# TC-MF-011: Sync Queue Persistence
+@pytest.mark.integration
+@pytest.mark.requires_secrets
+@pytest.mark.slow # This test involves service restart simulation
+async def test_sync_queue_persistence_across_restart():
+ """
+ Test Case: TC-MF-011
+
+ Steps:
+ 1. Submit 5 feedbacks
+ 2. Mock memory server as unavailable
+ 3. Verify 5 items in sync queue
+ 4. Simulate service restart
+ 5. Verify 5 items still in queue
+ 6. Bring server back online
+ 7. Verify all 5 synced
+
+ Expected: Sync queue persists across restart
+ """
+ from src.services.memory_feedback import MemoryFeedbackService
+ from src.services.memory_server_sync import MemoryServerSyncService
+
+ # Skip if no API key
+ api_key = os.getenv("MEMORY_SERVER_API_KEY")
+ if not api_key:
+ pytest.skip("MEMORY_SERVER_API_KEY not configured")
+
+ # Arrange
+ feedback_service = MemoryFeedbackService()
+ sync_service = MemoryServerSyncService()
+
+ # Submit 5 feedbacks
+ feedback_ids = []
+ for i in range(5):
+ fid = await feedback_service.submit_feedback(
+ memory_id="507f1f77bcf86cd799439011",
+ user_id="507f1f77bcf86cd799439012",
+ feedback_type="true",
+ )
+ feedback_ids.append(fid)
+
+ # Mock server unavailable
+ with patch.object(sync_service, "_make_request") as mock_request:
+ mock_request.return_value = AsyncMock(status_code=503)
+ for fid in feedback_ids:
+ await sync_service.sync_feedback(fid)
+
+ # Assert - 5 items in queue (not synced)
+ queue_size = await sync_service.get_queue_size()
+ assert queue_size == 5
+
+ # Simulate service restart (close and reopen connection)
+ await sync_service.close()
+ sync_service = MemoryServerSyncService() # Fresh instance
+
+ # Assert - Queue still has 5 items
+ queue_size = await sync_service.get_queue_size()
+ assert queue_size == 5
+
+ # Bring server back online (real requests now)
+ for fid in feedback_ids:
+ result = await sync_service.sync_feedback(fid)
+ assert result.success is True
+
+ # Assert - All synced
+ queue_size = await sync_service.get_queue_size()
+ assert queue_size == 0
diff --git a/ushadow/backend/tests/test_memory_feedback_validation.py b/ushadow/backend/tests/test_memory_feedback_validation.py
new file mode 100644
index 00000000..e2f7a283
--- /dev/null
+++ b/ushadow/backend/tests/test_memory_feedback_validation.py
@@ -0,0 +1,172 @@
+"""
+Test module for memory feedback validation logic.
+
+Generated by automation-agent from test cases in:
+specs/features/memory-feedback.testcases.md
+
+Test Cases Covered:
+- TC-MF-001: Validate Feedback Type
+- TC-MF-002: Validate Corrected Text Length
+- TC-MF-003: Sanitize Corrected Text for XSS
+- TC-MF-004: Calculate Memory Status - Verified
+- TC-MF-005: Calculate Memory Status - Disputed
+- TC-MF-006: Calculate Memory Status - Corrected
+"""
+
+import pytest
+
+
+# TC-MF-001: Validate Feedback Type
+@pytest.mark.unit
+@pytest.mark.no_secrets
+def test_validate_feedback_type_valid_values():
+ """
+ Test Case: TC-MF-001 (Valid feedback types)
+
+ Steps:
+ 1. Validate feedback_type="true"
+ 2. Validate feedback_type="false"
+ 3. Validate feedback_type="correction"
+
+ Expected: All validations pass
+ """
+ from src.validators.memory_feedback import validate_feedback_type
+
+ # Arrange & Act & Assert
+ assert validate_feedback_type("true") is True
+ assert validate_feedback_type("false") is True
+ assert validate_feedback_type("correction") is True
+
+
+@pytest.mark.unit
+@pytest.mark.no_secrets
+def test_validate_feedback_type_invalid_values():
+ """
+ Test Case: TC-MF-001 (Invalid feedback types)
+
+ Steps:
+ 1. Validate feedback_type="invalid"
+ 2. Validate feedback_type=""
+ 3. Validate feedback_type=None
+
+ Expected: All validations fail with appropriate error
+ """
+ from src.validators.memory_feedback import validate_feedback_type, ValidationError
+
+ # Arrange
+ invalid_types = ["invalid", "", None, "TRUE", "False", 123]
+
+ # Act & Assert
+ for invalid_type in invalid_types:
+ with pytest.raises(ValidationError, match="feedback_type must be"):
+ validate_feedback_type(invalid_type)
+
+
+# TC-MF-002: Validate Corrected Text Length
+@pytest.mark.unit
+@pytest.mark.no_secrets
+def test_validate_corrected_text_length():
+ """
+ Test Case: TC-MF-002
+
+ Steps:
+ 1. Validate empty string
+ 2. Validate 1 character
+ 3. Validate 2000 characters
+ 4. Validate 2001 characters
+
+ Expected: Only 1-2000 chars pass
+ """
+ from src.validators.memory_feedback import (
+ validate_corrected_text,
+ ValidationError,
+ )
+
+ # Empty/None should fail
+ with pytest.raises(ValidationError, match="corrected_text is required"):
+ validate_corrected_text("")
+
+ with pytest.raises(ValidationError, match="corrected_text is required"):
+ validate_corrected_text(None)
+
+ # 1 char should pass
+ assert validate_corrected_text("a") is True
+
+ # 2000 chars should pass
+ assert validate_corrected_text("x" * 2000) is True
+
+ # 2001 chars should fail
+ with pytest.raises(ValidationError, match="exceeds maximum length"):
+ validate_corrected_text("x" * 2001)
+
+
+# TC-MF-003: Sanitize Corrected Text for XSS
+@pytest.mark.unit
+@pytest.mark.no_secrets
+def test_sanitize_corrected_text_xss_prevention():
+ """
+ Test Case: TC-MF-003
+
+ Steps:
+ 1. Input malicious script tags
+ 2. Input image with onerror
+ 3. Input javascript: protocol
+ 4. Input normal text with HTML entities
+
+ Expected: All malicious code escaped/stripped, safe text preserved
+ """
+ from src.utils.sanitize import sanitize_html
+
+ # Malicious inputs
+ assert "")
+ assert "onerror" not in sanitize_html("
")
+ assert "javascript:" not in sanitize_html("javascript:alert('xss')")
+
+ # Safe HTML entities should be encoded
+ result = sanitize_html('Test & "quotes"
')
+ assert "<" in result or "" not in result # Tags escaped or stripped
+
+ # Plain text preserved
+ plain_text = "This is normal text without HTML"
+ assert sanitize_html(plain_text) == plain_text
+
+
+# TC-MF-004, TC-MF-005, TC-MF-006: Calculate Memory Status
+@pytest.mark.unit
+@pytest.mark.no_secrets
+@pytest.mark.parametrize(
+ "feedback_summary,expected_status",
+ [
+ # TC-MF-004: Verified (>= 3 true)
+ ({"true": 3, "false": 0, "correction": 0}, "verified"),
+ ({"true": 5, "false": 1, "correction": 0}, "verified"),
+ # TC-MF-005: Disputed (>= 2 false)
+ ({"true": 1, "false": 2, "correction": 0}, "disputed"),
+ ({"true": 0, "false": 3, "correction": 0}, "disputed"),
+ # TC-MF-006: Corrected (has corrections)
+ ({"true": 1, "false": 0, "correction": 1}, "corrected"),
+ ({"true": 2, "false": 1, "correction": 2}, "corrected"),
+ # Edge case: Unverified (no significant feedback)
+ ({"true": 1, "false": 0, "correction": 0}, "unverified"),
+ ({"true": 2, "false": 1, "correction": 0}, "unverified"),
+ ],
+)
+def test_calculate_memory_status(feedback_summary, expected_status):
+ """
+ Test Cases: TC-MF-004, TC-MF-005, TC-MF-006
+
+ Verifies memory status calculation based on feedback counts.
+
+ Rules:
+ - >= 3 true feedbacks → "verified"
+ - >= 2 false feedbacks → "disputed"
+ - > 0 corrections → "corrected"
+ - Otherwise → "unverified"
+ """
+ from src.models.memory import calculate_memory_status
+
+ # Act
+ status = calculate_memory_status(feedback_summary)
+
+ # Assert
+ assert status == expected_status
diff --git a/ushadow/frontend/e2e/memory-feedback.spec.ts b/ushadow/frontend/e2e/memory-feedback.spec.ts
new file mode 100644
index 00000000..0a2d7ffd
--- /dev/null
+++ b/ushadow/frontend/e2e/memory-feedback.spec.ts
@@ -0,0 +1,145 @@
+/**
+ * E2E Tests: Memory Feedback
+ *
+ * Generated from: specs/features/memory-feedback.testcases.md
+ *
+ * Test Cases Covered:
+ * - TC-MF-017: User Marks Memory as True
+ * - TC-MF-018: User Provides Correction
+ * - TC-MF-019: Cancel Correction Modal
+ */
+
+import { test, expect } from '@playwright/test'
+
+test.describe('Memory Feedback', () => {
+ test.beforeEach(async ({ page }) => {
+ // Setup: Login and navigate to memories page
+ await page.goto('/login')
+ await page.fill('[data-testid="email-field"]', 'test@example.com')
+ await page.fill('[data-testid="password-field"]', 'testpass123')
+ await page.click('[data-testid="login-button"]')
+ await page.waitForURL('/memories')
+ })
+
+ /**
+ * TC-MF-017: User Marks Memory as True
+ *
+ * Priority: Critical
+ * Requires Secrets: No
+ */
+ test('TC-MF-017: user can mark memory as true', async ({ page }) => {
+ // Given: Memory is visible
+ const memory = page.locator('[data-testid="memory-item"]').first()
+ await expect(memory).toBeVisible()
+
+ // When: User clicks True button
+ await memory.locator('[data-testid="memory-feedback-true"]').click()
+
+ // Then: Success feedback shown
+ await expect(page.locator('[data-testid="feedback-success-toast"]')).toBeVisible()
+ await expect(page.locator('[data-testid="feedback-success-toast"]')).toContainText(
+ 'Feedback submitted'
+ )
+
+ // And: Button state updates
+ const trueButton = memory.locator('[data-testid="memory-feedback-true"]')
+ await expect(trueButton).toHaveClass(/active|selected/)
+ })
+
+ /**
+ * TC-MF-018: User Provides Correction
+ *
+ * Priority: Critical
+ * Requires Secrets: No
+ */
+ test('TC-MF-018: user can provide correction', async ({ page }) => {
+ // Given: Memory is visible
+ const memory = page.locator('[data-testid="memory-item"]').first()
+ const originalText = await memory.locator('[data-testid="memory-text"]').textContent()
+
+ // When: User clicks Almost button
+ await memory.locator('[data-testid="memory-feedback-almost"]').click()
+
+ // Then: Correction modal opens
+ const modal = page.locator('[data-testid="correction-modal"]')
+ await expect(modal).toBeVisible()
+
+ // And: Original text is displayed
+ const displayedOriginal = await modal.locator('[data-testid="original-text"]').textContent()
+ expect(displayedOriginal).toContain(originalText)
+
+ // When: User enters correction
+ const correctionText = 'The meeting was on Tuesday, not Monday'
+ await modal.locator('[data-testid="correction-text-field"]').fill(correctionText)
+
+ // And: User clicks Submit
+ await modal.locator('[data-testid="correction-submit"]').click()
+
+ // Then: Success message shown
+ await expect(page.locator('[data-testid="feedback-success-toast"]')).toBeVisible()
+
+ // And: Modal closes
+ await expect(modal).not.toBeVisible()
+
+ // And: Memory status may update
+ // (Status update depends on backend calculation, just verify no error)
+ await expect(page.locator('[data-testid="error-toast"]')).not.toBeVisible()
+ })
+
+ /**
+ * TC-MF-019: Cancel Correction Modal
+ *
+ * Priority: Medium
+ * Requires Secrets: No
+ */
+ test('TC-MF-019: user can cancel correction modal', async ({ page }) => {
+ // Given: Memory is visible
+ const memory = page.locator('[data-testid="memory-item"]').first()
+
+ // When: User clicks Almost button
+ await memory.locator('[data-testid="memory-feedback-almost"]').click()
+
+ // Then: Modal opens
+ const modal = page.locator('[data-testid="correction-modal"]')
+ await expect(modal).toBeVisible()
+
+ // When: User types some text
+ await modal
+ .locator('[data-testid="correction-text-field"]')
+ .fill('Some partial correction')
+
+ // And: User clicks Cancel
+ await modal.locator('[data-testid="correction-cancel"]').click()
+
+ // Then: Modal closes
+ await expect(modal).not.toBeVisible()
+
+ // And: No API request was made (verify by checking no success toast)
+ await expect(page.locator('[data-testid="feedback-success-toast"]')).not.toBeVisible()
+
+ // And: Memory state unchanged (Almost button still clickable)
+ await expect(memory.locator('[data-testid="memory-feedback-almost"]')).toBeVisible()
+ })
+
+ /**
+ * Additional test: Verify data-testid attributes exist
+ */
+ test('all required test IDs are present', async ({ page }) => {
+ const memory = page.locator('[data-testid="memory-item"]').first()
+ await expect(memory).toBeVisible()
+
+ // Verify all feedback buttons have test IDs
+ await expect(memory.locator('[data-testid="memory-feedback-true"]')).toBeVisible()
+ await expect(memory.locator('[data-testid="memory-feedback-false"]')).toBeVisible()
+ await expect(memory.locator('[data-testid="memory-feedback-almost"]')).toBeVisible()
+
+ // Open modal to verify its test IDs
+ await memory.locator('[data-testid="memory-feedback-almost"]').click()
+ const modal = page.locator('[data-testid="correction-modal"]')
+
+ await expect(modal.locator('[data-testid="original-text"]')).toBeVisible()
+ await expect(modal.locator('[data-testid="correction-text-field"]')).toBeVisible()
+ await expect(modal.locator('[data-testid="correction-submit"]')).toBeVisible()
+ await expect(modal.locator('[data-testid="correction-cancel"]')).toBeVisible()
+ })
+})