Use this file to quickly orient a new Claude Code session. Keep it up to date as features are added.
| Layer | Technology |
|---|---|
| Backend | Laravel 12.60, PHP 8.2+ |
| Frontend | Vue 3 (Composition API, <script setup>), Inertia 2.3 |
| Build | Vite 7.3 |
| CSS | Tailwind CSS 4.3 (@import "tailwindcss" in resources/css/app.css) |
| UI Components | shadcn-vue 2.7 / reka-ui 2.9 / @lucide/vue 1.16 |
| Database | SQLite (default), MySQL supported |
| Auth | Laravel built-in + Spatie Permission 7.4 (roles: administrator, user) |
Path aliases: @/ → resources/js/
resources/js/app.js — Inertia entry point
resources/css/app.css — Tailwind 4 styles
resources/views/app.blade.php — Blade shell
routes/web.php — All web routes
routes/api.php — API v1 routes
app/Http/Middleware/HandleInertiaRequests.php — Shared props
resources/js/Layouts/AppLayout.vue — Admin sidebar layout
resources/js/Layouts/BlogLayout.vue — Public blog layout
resources/js/Layouts/InstallLayout.vue — 4-step install wizard layout
resources/js/Layouts/PageBuilderLayout.vue — Full-screen block editor layout
resources/js/Components/ — Shared components (PascalCase dir)
resources/js/components/ — Public blog + UI primitives (lowercase dir)
resources/js/Components/BlockRenderer.vue — Renders published blocks on the public frontend (BLOCK_MAP)
resources/js/Components/BlockEditor/ — Block editor (admin canvas + layers panel)
resources/js/Components/Blocks/ — Individual block render components
resources/js/lib/utils.js — cn() helper
resources/js/composables/useLoopBinding.js — Dynamic field binding composable
resources/js/lib/loopSources.js — Loop source field definitions
- Login with rate limiting (5 attempts / 60 s per email+IP)
- Forgot / reset password via email token
- Email verification (required for post/page editing)
- Logout
- Users CRUD (admin-only): role assignment (
administrator/user) - User invite flow: auto-generated password + welcome email
- Admin count enforcement (min 1 admin; self-delete blocked)
- Profile: name/email edit, password change, avatar upload/delete
- Online status (
last_seen_at+isOnline()) - User banning (
BanController, ban/unban with reason) - Two-factor authentication (TOTP, RFC 6238): per-user opt-in from Profile; challenge page at
/two-factor-challenge; 8 single-use recovery codes stored encrypted;pragmarx/google2falibrary; compatible with any TOTP app (Google Authenticator, Authy, 1Password, etc.)
- Full CRUD, author-scoped (owner OR admin can edit/delete)
- Status:
draft/scheduled/published - Scheduled posts with future
published_at - Bulk actions: publish, draft, delete (with per-post auth)
- Search (title/content) + filter by status/category + pagination (15/page)
- Featured image with alt text;
featuredflag - Categories (searchable typeahead pill selector) and Tags (many-to-many)
- SEO fields:
meta_title,meta_description,meta_keywords - Comments enabled/disabled per post
- Autosave (POST/DELETE
/posts/{post}/autosave) - Revisions: list (max 25) + restore
- Block editor toggle (
use_block_editorflag) — same block system as Pages - TipTap editor with HTML source toggle
- Preview:
/preview/posts/{token}— shareable preview URL for drafts - Save-in-place: saving no longer remounts the page (uses
preserveState)
- Full CRUD with block editor (JSON
blockscolumn) - Status:
draft/published - SEO fields (same as posts)
- Autosave + Revisions (same infrastructure as posts)
- Public catch-all route:
/{slug}serves published pages - Full-width editor layout
- Preview:
/preview/pages/{token}— shareable preview URL for drafts
Block types available:
| Group | Types | Public Renderer |
|---|---|---|
| Content | paragraph, heading, image, video, gallery, code, quote |
✓ |
| Content | accordion, tabs, embed, divider, spacer |
✓ |
| Layout | container, columns, section, navigation |
✓ |
| Interactive | button, icon-list, cta, search, loop, link, filter-link, active-filter, template, pagination, table |
✓ |
| Developer | html (admin-only) |
✓ |
| Post | post-title, post-body, post-featured-image, post-meta, post-author, post-taxonomy, post-comments, post-card |
✓ |
| Archive | archive-title |
✓ |
| Site | nav-header, site-footer, masthead, band |
✓ |
accordion-itemandtab-itemare auto-inserted children of their parent blocks, hidden from the palette.columnsis a preset ofcontainerwith a column-count control; it maps toContainerBlockfor rendering.
Canvas features:
- Drag-and-drop with cross-list nesting
- Block labels /
blockName(shown in canvas + layers panel) - Layers panel with infinite depth nesting
- Real-time preview in canvas
- Undo / Redo via the layers panel toolbar
Settings tabs (per block):
- Content tab — block-type-specific fields (text, URLs, media, etc.). Many text-rendering blocks also include a
TypographyControl(font-size, weight, line-height, letter-spacing, text decoration, transform, text-shadow, alignment, color). - Style tab — shared
StyleSettings.vuefor all blocks:- Font family
- Display (flex/grid/inline-flex mode, direction, wrap, justify, align, gap, max-width)
- Spacing (padding, margin — per-side via SpacingControl)
- Border & Shadow — per-corner radius, stroke style/width/color, box-shadow presets
- Background (color, gradient with direction, image with position/size/parallax + dynamic binding)
- Effects (opacity, cursor, overflow, z-index, transition)
- Advanced tab — block label, custom HTML ID, custom CSS classes, custom CSS (CodeMirror editor, scoped automatically)
- Conditions tab — conditional visibility: show/hide block based on a field/operator/value rule (evaluated against loop context)
Dynamic field binding:
- Loop fields (post title, slug, excerpt, featured image URL, author, URL, etc.)
- Post context fields (single-post template)
- Both Library and URL modes on image blocks support binding
- Alt text, caption, heading text, paragraph text all bindable
Loop block:
- Query sources: posts / categories / tags / pages
- Filters (field, operator, value), sort, limit
- Resolved server-side via
QueryBuilderservice +/api/v1/queryendpoint
Template block / partials:
- Partial templates render inline and re-provide
loopItem/postContextso bindings work inside templates used in loops
Pre-shipped system templates (protected — cannot be deleted, can be edited):
Post Card(partial, loop_source=posts)Default Blog Index(blog-index)Default Single Post(single-post)Default Archive(archive)Default Search Results(search-results)
- Full CRUD (authenticated users)
- Auto-generated slugs; description on categories
- Category color (custom picker with Nord palette presets)
- Post count aggregation
- Searchable typeahead selector in post editor (CategoryInput.vue)
- Public archive pages:
/blog/category/{slug},/blog/tag/{slug}
- Upload (configurable MIME types, configurable max MB — default 10 MB)
- Auto-resize images > 1920 px wide
- List (40/page) with type/search filters; non-admins see own uploads only
- Update alt text / description; delete single or bulk
- UUID-based filenames; dimension tracking
- External disk support (
disk='external'→ stores full URL inpath) - In-browser image editor (
ImageEditor.vue): opens before upload for JPEG/PNG/WebP/GIF; also available via "Edit image" button on existing library items- Crop with 9 aspect ratio presets; rotate 90° CW/CCW; flip H/V
- Output size controls (W × H, lockable aspect ratio)
- 8 one-click filter presets (Normal, Vivid, Muted, B&W, Warm, Cool, Fade, Drama)
- Manual adjustments: brightness, contrast, saturation, blur
- Uses
cropperjsv1 for crop interaction; CSSctx.filteron canvas for export POST /media/{media}/replacereplaces the stored file and updates DB dimensions/size
- Public submission (rate-limited + honeypot)
- Pending by default; admin moderation (approve/reject/delete/bulk)
- Nested replies with email notification to commenter
- Pending count badge in admin header
- Per-post
comments_enabledflag
- Site name & URL
- Accent color — Nord aurora palette swatches; applies to admin via
--primary/--sidebar-primaryand to blog frontend via--accent/--accent-ink - Locale: timezone, date_format
- Media: max_upload_mb, resize_max_width
- Mail: driver (smtp/log/mailgun), host, port, username, password, encryption, from/name; test email send
- Comments: enabled toggle, per_page (5–100)
- SEO: title_separator, default_description, default_og_image_url, default_keywords
- Monthly editorial calendar; posts grouped by
published_atdate - Unscheduled drafts section
- Admin sees all; regular users see own published + anyone's drafts
- Post counts (total/published/scheduled/draft)
- Pending comments count
- Upcoming scheduled posts (next 5)
- Recent posts (last 5 updated)
- Blog index: paginated published posts, sidebar (search, categories, tags, recent posts)
- Single post: full content, featured image, author, categories/tags, comments with load-more
- Category + tag archive pages
- Full-text search page (
/search) - RSS feed (
/feed) — 20 most recent published posts - Sitemap XML (
/sitemap.xml) - Admin bar visible to authenticated users (hidden from public visitors)
- Navigation links managed via the
nav-headerblock's Content tab (no separate admin section)
GET /posts— paginated; filters: category slug, tag slug, searchGET /posts/{slug}— full post with bodyGET /categories+GET /categories/{slug}GET /tags+GET /tags/{slug}POST /query— QueryBuilder endpoint (loop block data source)
- CRUD at
/webhooks - Events:
post.published,post.updated,post.deleted,page.published,page.updated,page.deleted - HMAC-SHA256 signature header (
X-Lambda-Signature) when secret set - Dispatched via queued job (
DispatchWebhookJob) - PostObserver + PageObserver fire events on lifecycle hooks
last_triggered_attracked per webhook
- CRUD at
/templates(admin-only) - Types:
partial,blog-index,single-post,archive,search-results,header,footer - System templates (
is_system = true) cannot be deleted TemplateSeederseeds 7 system templates on fresh install: Post Card, Default Blog Index, Default Single Post, Default Archive, Default Search Results, Default Header, Default Footerheaderandfootertemplates are shared asheaderBlocks/footerBlocksvia Inertia middleware and rendered byBlogLayout.vue- Shared as
sharedTemplatesprop via Inertia middleware
- 4-step wizard: Database → Site → Admin → Mail
- Tests DB connection before saving; writes
.env; runs migrations - Seeds default templates and Lambda CMS launch posts after admin creation
not_installed/installedmiddleware gate- Marks installed via
storage/app/installedfile (gitignored — users must run the installer on fresh deployments)
users, posts, categories, tags, pages, comments, media
templates, webhooks, settings
autosaves (morphable), revisions (morphable)
Pivots: category_post, post_tag
Spatie: roles, permissions, model_has_roles, ...
nav_items (table exists from old migration; model/controller removed — nav lives in block data)
GET / blog index
GET /blog/{slug} single post
GET /blog/category/{slug} category archive
GET /blog/tag/{slug} tag archive
GET /blog/{post:slug}/comments paginated approved comments (JSON)
POST /blog/{post:slug}/comments submit comment (rate-limited, honeypot)
GET /search full-text search results
GET /feed RSS feed
GET /sitemap.xml sitemap
GET /preview/posts/{token} draft post preview (token-based)
GET /preview/pages/{token} draft page preview (token-based)
GET /{slug} public page (catch-all)
GET/POST /login
GET/POST /forgot-password
GET/POST /reset-password/{token}
GET/POST /two-factor-challenge 2FA step after successful password auth
GET /email/verify verification notice
GET /email/verify/{id}/{hash} verification link (signed)
POST /email/verification-notification resend verification email
POST /logout
GET /dashboard
/posts (resource + bulk + autosave + revisions)
/categories (resource)
/tags (resource)
/profile (info, password, avatar, two-factor enable/confirm/disable/recovery-codes)
/media (resource + bulk-destroy + usage + replace)
/calendar (index + data)
/pages (resource + autosave + revisions)
/templates (resource + autosave + revisions)
/users (resource + ban/unban)
/comments (list, approve, reject, delete, bulk, reply)
/settings (index + update by group + test-email)
/webhooks (index, store, update, destroy)
/export (index + download)
/import (index + preview + store)
GET /api/v1/posts
GET /api/v1/posts/{slug}
GET /api/v1/categories
GET /api/v1/categories/{slug}
GET /api/v1/tags
GET /api/v1/tags/{slug}
POST /api/v1/query
/install/database (GET/POST)
/install/site (GET/POST)
/install/admin (GET/POST)
/install/mail (GET/POST)
Available on every page via usePage().props:
| Prop | Type | Description |
|---|---|---|
appName |
string |
Site name from config('app.name') |
auth.user |
{id,name,email,role,avatar_url,email_verified,two_factor_enabled} or null |
Authenticated user |
flash.status |
string|null |
Generic success flash |
flash.error |
string|null |
Generic error flash |
flash.mail_status |
string|null |
Mail test success message |
flash.mail_error |
string|null |
Mail test error message |
currentRoute |
string |
Current Laravel route name |
pendingCommentsCount |
number|null |
Pending comment count (admins only) |
accentColor |
string|null |
Hex accent color from site settings |
sharedTemplates |
Array<Template> |
All templates (for block editor) |
headerBlocks |
Array |
Blocks from the active header template |
footerBlocks |
Array |
Blocks from the active footer template |
- Block data: stored as JSON in
blockscolumn; each block has{id, type, data, bindings?, children?, blockName?, fontFamily?, customId?, customClasses?, customCss?, condition?, ...} - Dynamic bindings:
block.bindings[fieldName] = 'loop:field'or'post:field'; resolved inuseLoopBinding.jscomposable - Block rendering:
BlockRenderer.vue(public) maps type → component;blockWrapperStyle()computes inline styles from block data - Block settings: per-type Settings component for Content tab; shared
StyleSettings.vuefor Style tab;AdvancedSettings.vuefor Advanced tab;ConditionSettings.vuefor Conditions tab - Notifications:
useNotifications()composable →Notifications.vuecomponent - Theming: dark/light via
useTheme()composable;data-themeattribute on root - Accent color:
accentColorshared prop →watchEffectinAppLayoutsets--primaryand related admin CSS vars;watchEffectinBlogLayoutsets--accent/--accent-inkblog tokens - Media URLs:
Media::getUrlAttribute()handles bothStorage::disk()anddisk='external'(returnspathdirectly as full URL) - System templates:
is_system = trueblocks deletion;TemplateSeedermarks them - Preview tokens: auto-generated 64-char random string on Post/Page create; routes are public
- Webhooks: fired via observers →
WebhookService::dispatch()→DispatchWebhookJob(queued)
Potential future improvements:
- Import/export for pages and templates (posts, categories, tags, media already done)
- API write access (token-based)
- Multi-language / i18n support
- VLOG / video-focused genre template
claude/explore-codebase-setup-AMYo1(~20 commits): form builder (Form block + admin CRUD + submissions), Slider/Counter/Pricing/Card/Cover/SectionHeader/CategoryChip/StatCard/Testimonial/Progress/PostList/IconBox blocks, icon picker on Paragraph/Heading, device visibility controls, copy styles, block locking, block editor UX (search, click-to-add, collapse, keyboard shortcuts), admin UI animations, various bug fixesfeature/tests: 112 tests, 282 assertions (was created before current codebase diverged significantly — may need updates to pass against current code)
inertia-laravel2 → 3 +@inertiajs/vue32 → 3 (coupled upgrade)@vueuse/core13 → 14intervention/image3 → 4 (may require image-processing code changes)laravel/framework12 → 13 (full framework upgrade)
Last updated: 2026-05-29 — removed Navigation admin section (nav links now live in nav-header block data); fixed installer seeder ordering (TemplateSeeder + LambdaContentSeeder now run after admin creation); fixed dark-mode token bleed into blog frontend; fixed webhook HMAC body mismatch; fixed duplicate scheduler entry; fixed welcome email step order; fixed import path traversal; added export/import for posts, categories, tags, media, templates; corrected installer to 4-step wizard; removed navItems shared prop