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

276
.planning/research/STACK.md Normal file
View File

@@ -0,0 +1,276 @@
# Technology Stack Research
**Project:** Member Notes/CRM Annotation System
**Domain:** Inline AJAX note-taking for Laravel 10 admin panel
**Researched:** 2026-02-13
**Confidence:** HIGH
## Recommended Stack
### Core Technologies (Already in Place)
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| Alpine.js | 3.4.2 | Frontend reactivity for inline CRUD | Lightweight (15KB), works seamlessly with Laravel Blade, perfect for progressive enhancement without adding full SPA complexity. Already integrated in project. |
| Axios | 1.6.4 | HTTP client for AJAX requests | Already included via bootstrap.js, automatically handles CSRF tokens through Laravel's XSRF-TOKEN cookie. Battle-tested with Laravel. |
| Laravel 10 Eloquent | 10.x | ORM for database operations | Polymorphic relationships native support allows single `notes` table to serve multiple parent models. No additional ORM needed. |
| Blade Templates | 10.x | Server-side rendering | Existing template system, supports seamless Alpine.js integration with `x-data`, `x-init` directives in markup. |
### Supporting Patterns (No Additional Libraries Needed)
| Pattern | Purpose | When to Use | Rationale |
|---------|---------|-------------|-----------|
| Native Fetch API | Alternative to Axios for lightweight operations | When making simple GET/POST without interceptors | Modern browser support (100%), no dependencies, async/await syntax. **Not recommended** for this project since Axios already handles CSRF automatically. |
| Polymorphic Relations (morphMany/morphTo) | Database structure for notes attached to multiple models | All note CRUD operations | Laravel native feature, eliminates need for separate notes tables per entity, supports future expansion to other models. |
| JSON Resource Classes | Standardized API responses | All AJAX endpoint responses | Laravel native (10.x), ensures consistent response structure, type safety for frontend consumers. |
| Alpine.js Component Pattern | Isolated state management | Each note list/form on the page | Encapsulates data and methods within `x-data`, prevents global state pollution, reusable across pages. |
## Pattern Recommendations
### 1. Alpine.js Inline CRUD Pattern
**Recommended Approach:** Alpine component with native Axios
**Structure:**
```javascript
x-data="{
notes: [],
newNote: '',
isLoading: false,
isAdding: false,
async fetchNotes() {
this.isLoading = true;
const response = await axios.get(`/admin/members/${memberId}/notes`);
this.notes = response.data.data;
this.isLoading = false;
},
async addNote() {
if (!this.newNote.trim()) return;
this.isAdding = true;
try {
const response = await axios.post(`/admin/members/${memberId}/notes`, {
content: this.newNote
});
this.notes.unshift(response.data.data);
this.newNote = '';
} catch (error) {
alert('新增備註失敗');
}
this.isAdding = false;
}
}"
x-init="fetchNotes()"
```
**Why this pattern:**
- Axios automatically includes CSRF token from `XSRF-TOKEN` cookie (configured in bootstrap.js)
- `x-init` loads data on component mount without user action
- Async/await syntax cleaner than Promise chains
- State (`notes`, `newNote`, `isLoading`) colocated with methods
- No global state pollution
**Confidence:** HIGH (based on [official Laravel docs](https://laravel.com/docs/10.x/csrf), [Witty Programming Alpine.js patterns](https://www.wittyprogramming.dev/articles/using-native-fetch-with-alpinejs/), [Code with Hugo best practices](https://codewithhugo.com/alpinejs-x-data-fetching/))
### 2. Laravel AJAX Endpoint Pattern
**Recommended Approach:** Resource controller with JSON responses
**Route Structure:**
```php
// In routes/web.php under admin middleware group
Route::prefix('admin')->name('admin.')->middleware(['auth', 'admin'])->group(function () {
Route::get('/members/{member}/notes', [MemberNoteController::class, 'index'])->name('members.notes.index');
Route::post('/members/{member}/notes', [MemberNoteController::class, 'store'])->name('members.notes.store');
});
```
**Controller Response Pattern:**
```php
// Return single resource
return response()->json([
'data' => new NoteResource($note)
], 201);
// Return collection
return response()->json([
'data' => NoteResource::collection($notes)
]);
// Return error
return response()->json([
'message' => '新增備註失敗',
'errors' => $validator->errors()
], 422);
```
**Why this pattern:**
- Consistent `data` wrapper matches Laravel API resource conventions
- HTTP status codes (201, 422) enable frontend error handling
- JSON Resource classes transform models consistently
- Traditional Chinese error messages match existing UI
- No need for JSON:API spec compliance (overkill for internal AJAX)
**Confidence:** HIGH (based on [official Laravel 10 docs](https://laravel.com/docs/10.x/eloquent-resources), [Gergő Tar API best practices](https://gergotar.com/blog/posts/handling-api-controllers-and-json-responses-in-laravel/), [Medium standardized responses](https://medium.com/@marcboko.uriel/laravel-standardized-api-json-response-5daf9cc17212))
### 3. Database Schema Pattern
**Recommended Approach:** Polymorphic one-to-many relationship
**Migration:**
```php
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->text('content');
$table->morphs('notable'); // Creates notable_id, notable_type
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->index(['notable_type', 'notable_id']);
});
```
**Model Structure:**
```php
// Note model
class Note extends Model
{
protected $fillable = ['content', 'notable_type', 'notable_id', 'user_id'];
public function notable(): MorphTo
{
return $this->morphTo();
}
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
// Member model (add relationship)
public function notes(): MorphMany
{
return $this->morphMany(Note::class, 'notable')->latest();
}
```
**Why this pattern:**
- Single `notes` table serves Members now, extendable to Issues, Payments later
- `morphs()` helper creates both ID and type columns with proper indexing
- `user_id` tracks note author (append-only audit trail)
- `latest()` scope pre-sorts by most recent
- No additional packages required (native Eloquent)
**Confidence:** HIGH (based on [official Laravel 10 Eloquent relationships docs](https://laravel.com/docs/10.x/eloquent-relationships), [Notable package patterns](https://github.com/EG-Mohamed/Notable))
## What NOT to Use
| Avoid | Why | Use Instead |
|-------|-----|-------------|
| Livewire | Adds 60KB+ payload, WebSocket overhead, overkill for simple inline notes | Alpine.js + Axios (already in stack) |
| Vue.js / React | Requires build step changes, component conversion, state management complexity | Alpine.js (declarative, works with Blade) |
| Alpine AJAX Plugin | Adds 8KB, limited docs, uses `x-target` pattern less flexible than Axios | Native Axios (CSRF handling built-in) |
| jQuery | Legacy library (87KB), deprecated patterns, not in current stack | Alpine.js for DOM, Axios for AJAX |
| Full JSON:API Spec | Overkill for internal AJAX, adds complexity (`included`, `relationships` nesting) | Simple `{ data: {...} }` wrapper |
| Separate notes_members table | Violates DRY, requires duplicate code for each entity type | Polymorphic `notes` table |
**Rationale:** Project already has Alpine.js 3.4 + Axios 1.6 working perfectly. Adding new libraries increases bundle size, introduces breaking changes risk, and requires learning new patterns when existing stack handles requirements natively.
**Confidence:** HIGH
## Installation
**No new packages required.** All dependencies already present in `package.json`:
```json
{
"devDependencies": {
"alpinejs": "^3.4.2", // ✓ Already installed
"axios": "^1.6.4" // ✓ Already installed
}
}
```
**Backend setup (migrations only):**
```bash
# Create notes table migration
php artisan make:migration create_notes_table
# Create Note model
php artisan make:model Note
# Create API Resource
php artisan make:resource NoteResource
# Create controller
php artisan make:controller MemberNoteController
```
## Alternatives Considered
| Category | Recommended | Alternative | When to Use Alternative |
|----------|-------------|-------------|-------------------------|
| Frontend Library | Alpine.js + Axios | Livewire | If building multi-step forms with real-time validation (not this use case) |
| AJAX Library | Axios | Native Fetch API | If removing Axios dependency in future (requires manual CSRF handling) |
| Response Format | Simple JSON wrapper | JSON:API spec | If building public API consumed by third parties (not internal AJAX) |
| Database Pattern | Polymorphic relations | Separate tables | If notes have vastly different schemas per entity (not this use case) |
## Version Compatibility
| Package | Compatible With | Notes |
|---------|-----------------|-------|
| Alpine.js 3.4.2 | Laravel 10.x Vite 5.0 | Already integrated via `resources/js/app.js`, no version conflicts |
| Axios 1.6.4 | Laravel 10.x CSRF | Configured in `resources/js/bootstrap.js`, auto-handles XSRF token cookie |
| Eloquent Polymorphic | Laravel 10.x | Native feature, no additional packages needed |
## Implementation Checklist
- [ ] Create `notes` migration with polymorphic columns (`notable_id`, `notable_type`)
- [ ] Create `Note` model with `morphTo` relationship
- [ ] Add `notes()` relationship to `Member` model using `morphMany`
- [ ] Create `NoteResource` for consistent JSON transformation
- [ ] Create `MemberNoteController` with `index()` and `store()` methods
- [ ] Add routes under `/admin/members/{member}/notes`
- [ ] Add Alpine.js component to `resources/views/admin/members/index.blade.php`
- [ ] Add audit logging for note creation (using existing `AuditLogger` class)
- [ ] Add Spatie permission check (`can('manage_member_notes')` or use existing admin middleware)
## Dark Mode Support
**Pattern:** Use Tailwind `dark:` prefix classes (already supported in project)
```html
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<textarea class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300">
</textarea>
</div>
```
**Rationale:** Project already uses `darkMode: 'class'` in Tailwind config. All existing admin views follow this pattern.
**Confidence:** HIGH (verified in existing codebase)
## Sources
### High Confidence (Official Documentation)
- [Laravel 10.x CSRF Protection](https://laravel.com/docs/10.x/csrf) - CSRF token handling with Axios
- [Laravel 10.x Eloquent Relationships](https://laravel.com/docs/10.x/eloquent-relationships) - Polymorphic relationships structure
- [Laravel 10.x Eloquent Resources](https://laravel.com/docs/10.x/eloquent-resources) - JSON response formatting
### Medium Confidence (Community Best Practices)
- [Using Native Fetch with Alpine.js - Witty Programming](https://www.wittyprogramming.dev/articles/using-native-fetch-with-alpinejs/) - Alpine.js fetch patterns
- [Practical Alpine.js Data Fetching - Code with Hugo](https://codewithhugo.com/alpinejs-x-data-fetching/) - State management best practices
- [Alpine AJAX Inline Edit Example](https://alpine-ajax.js.org/examples/inline-edit/) - Inline editing patterns
- [Handling API Controllers in Laravel - Gergő Tar](https://gergotar.com/blog/posts/handling-api-controllers-and-json-responses-in-laravel) - Controller response patterns
- [Standardized API JSON Response - Medium](https://medium.com/@marcboko.uriel/laravel-standardized-api-json-response-5daf9cc17212) - Response structure conventions
### Supporting References
- [Notable Package - GitHub](https://github.com/EG-Mohamed/Notable) - Polymorphic notes implementation example
- [Polymorphic Relationships - LogRocket](https://blog.logrocket.com/polymorphic-relationships-laravel/) - Use cases and patterns
---
**Stack Research Completed:** 2026-02-13
**Researcher Confidence:** HIGH - All recommendations use existing stack, no new dependencies, patterns verified in official Laravel 10 docs and Alpine.js community resources.