docs: research member notes ecosystem
This commit is contained in:
276
.planning/research/STACK.md
Normal file
276
.planning/research/STACK.md
Normal 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.
|
||||
Reference in New Issue
Block a user