Files
usher-manage-stack/.planning/research/STACK.md

12 KiB

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

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:

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, Witty Programming Alpine.js patterns, Code with Hugo best practices)

2. Laravel AJAX Endpoint Pattern

Recommended Approach: Resource controller with JSON responses

Route Structure:

// 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:

// 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, Gergő Tar API best practices, Medium standardized responses)

3. Database Schema Pattern

Recommended Approach: Polymorphic one-to-many relationship

Migration:

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:

// 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, Notable package patterns)

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:

{
  "devDependencies": {
    "alpinejs": "^3.4.2",  // ✓ Already installed
    "axios": "^1.6.4"       // ✓ Already installed
  }
}

Backend setup (migrations only):

# 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)

<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)

Medium Confidence (Community Best Practices)

Supporting References


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.