diff --git a/docs/compose/plans/2026-07-05-dashboard-error-page.md b/docs/compose/plans/2026-07-05-dashboard-error-page.md
new file mode 100644
index 0000000..f3be5ce
--- /dev/null
+++ b/docs/compose/plans/2026-07-05-dashboard-error-page.md
@@ -0,0 +1,569 @@
+# Dashboard Error Page Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Create a dedicated error page in the admin dashboard that shows full error context (status code, URL, message, stack trace, request params, user info, timestamp) with access restricted to super_admin users.
+
+**Architecture:** Modify `Handler.php` to detect dashboard routes and render `Dashboard/DashboardError.vue` instead of `Error.vue`. The new component uses `DashboardLayout` and displays full error context as props from the backend.
+
+**Tech Stack:** Laravel 10+, Vue 3 Composition API, Inertia.js, Tailwind CSS, Porto Architecture
+
+## Global Constraints
+
+- Code in English, comments in English
+- Composition API (`
+```
+
+- [ ] **Step 2: Verify component structure**
+
+Run: `grep -c "DashboardLayout" resources/js/Pages/Dashboard/DashboardError.vue`
+Expected: `2` (import + template usage)
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add resources/js/Pages/Dashboard/DashboardError.vue
+git commit -m "feat(dashboard): add DashboardError.vue with full error context display"
+```
+
+---
+
+### Task 2: Modify Exception Handler for Dashboard Routes
+
+**Covers:** S1, S2 (Route detection, context gathering)
+
+**Files:**
+- Modify: `app/Exceptions/Handler.php`
+- Modify: `app/Ship/Exceptions/Handler.php`
+
+**Interfaces:**
+- Consumes: Request object, exception, user roles
+- Produces: Inertia render with Dashboard/DashboardError.vue props
+
+- [ ] **Step 1: Add helper method and modify render in App Handler**
+
+Replace the `render` method in `app/Exceptions/Handler.php`:
+
+```php
+, \Psr\Log\LogLevel::*>
+ */
+ protected $levels = [
+ //
+ ];
+
+ /**
+ * A list of the exception types that are not reported.
+ *
+ * @var array>
+ */
+ protected $dontReport = [
+ //
+ ];
+
+ /**
+ * A list of the inputs that are never flashed to the session on validation exceptions.
+ *
+ * @var array
+ */
+ protected $dontFlash = [
+ 'current_password',
+ 'password',
+ 'password_confirmation',
+ ];
+
+ /**
+ * Register the exception handling callbacks for the application.
+ */
+ public function register(): void
+ {
+ $this->reportable(function (Throwable $e) {
+ //
+ });
+ }
+
+ /**
+ * Prepare exception for rendering.
+ *
+ * @param \Throwable $e
+ * @return \Throwable
+ */
+ public function render($request, Throwable $e)
+ {
+ $response = parent::render($request, $e);
+ $statusCode = $response->getStatusCode();
+
+ if (in_array($statusCode, [500, 503, 404, 403])) {
+ if ($this->isDashboardRequest($request)) {
+ return $this->renderDashboardError($request, $e, $statusCode);
+ }
+
+ if (! app()->environment(['local', 'testing'])) {
+ return Inertia::render('Error', ['status' => $statusCode])
+ ->toResponse($request)
+ ->setStatusCode($statusCode);
+ }
+ } elseif ($statusCode === 419) {
+ return back()->with([
+ 'message' => 'The page expired, please try again.',
+ ]);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Check if the request is for a dashboard route.
+ */
+ private function isDashboardRequest(Request $request): bool
+ {
+ return str_starts_with($request->path(), 'dashboard');
+ }
+
+ /**
+ * Render error page with full context for dashboard users.
+ */
+ private function renderDashboardError(Request $request, Throwable $e, int $statusCode)
+ {
+ $user = $request->user();
+ $isSuperAdmin = $user && $user->hasRole('super_admin');
+ $isProduction = app()->environment('production');
+
+ $props = [
+ 'status' => $statusCode,
+ 'message' => $this->getErrorMessage($e, $statusCode),
+ 'url' => $request->fullUrl(),
+ 'method' => $request->method(),
+ 'user' => $user ? $user->only('id', 'name', 'email') : null,
+ 'timestamp' => now()->toIso8601String(),
+ ];
+
+ // Only superadmins get stack trace and request params
+ if ($isSuperAdmin && !$isProduction) {
+ $props['stackTrace'] = $e->getTraceAsString();
+ $props['requestParams'] = $request->except([
+ 'password',
+ 'password_confirmation',
+ 'current_password',
+ '_token',
+ ]);
+ }
+
+ return Inertia::render('Dashboard/DashboardError', $props)
+ ->toResponse($request)
+ ->setStatusCode($statusCode);
+ }
+
+ /**
+ * Get a user-friendly error message based on status code.
+ */
+ private function getErrorMessage(Throwable $e, int $statusCode): string
+ {
+ $messages = [
+ 503 => 'Сервис временно недоступен. Попробуйте позже.',
+ 500 => 'Внутренняя ошибка сервера.',
+ 404 => 'Запрашиваемая страница не найдена.',
+ 403 => 'У вас нет доступа к этой странице.',
+ ];
+
+ return $messages[$statusCode] ?? 'Произошла неизвестная ошибка.';
+ }
+}
+```
+
+- [ ] **Step 2: Apply same changes to Ship Handler**
+
+Replace the `render` method in `app/Ship/Exceptions/Handler.php`:
+
+```php
+, \Psr\Log\LogLevel::*>
+ */
+ protected $levels = [
+ //
+ ];
+
+ /**
+ * A list of the exception types that are not reported.
+ *
+ * @var array>
+ */
+ protected $dontReport = [
+ //
+ ];
+
+ /**
+ * A list of the inputs that are never flashed to the session on validation exceptions.
+ *
+ * @var array
+ */
+ protected $dontFlash = [
+ 'current_password',
+ 'password',
+ 'password_confirmation',
+ ];
+
+ /**
+ * Register the exception handling callbacks for the application.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->reportable(function (Throwable $e) {
+ //
+ });
+ }
+
+ public function render($request, Throwable $e)
+ {
+ $response = parent::render($request, $e);
+ $statusCode = $response->getStatusCode();
+
+ if (in_array($statusCode, [500, 503, 404, 403])) {
+ if ($this->isDashboardRequest($request)) {
+ return $this->renderDashboardError($request, $e, $statusCode);
+ }
+
+ if (! app()->environment(['local', 'testing'])) {
+ return Inertia::render('Error', ['status' => $statusCode])
+ ->toResponse($request)
+ ->setStatusCode($statusCode);
+ }
+ } elseif ($statusCode === 419) {
+ return back()->with([
+ 'message' => 'The page expired, please try again.',
+ ]);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Check if the request is for a dashboard route.
+ */
+ private function isDashboardRequest(Request $request): bool
+ {
+ return str_starts_with($request->path(), 'dashboard');
+ }
+
+ /**
+ * Render error page with full context for dashboard users.
+ */
+ private function renderDashboardError(Request $request, Throwable $e, int $statusCode)
+ {
+ $user = $request->user();
+ $isSuperAdmin = $user && $user->hasRole('super_admin');
+ $isProduction = app()->environment('production');
+
+ $props = [
+ 'status' => $statusCode,
+ 'message' => $this->getErrorMessage($e, $statusCode),
+ 'url' => $request->fullUrl(),
+ 'method' => $request->method(),
+ 'user' => $user ? $user->only('id', 'name', 'email') : null,
+ 'timestamp' => now()->toIso8601String(),
+ ];
+
+ // Only superadmins get stack trace and request params
+ if ($isSuperAdmin && !$isProduction) {
+ $props['stackTrace'] = $e->getTraceAsString();
+ $props['requestParams'] = $request->except([
+ 'password',
+ 'password_confirmation',
+ 'current_password',
+ '_token',
+ ]);
+ }
+
+ return Inertia::render('Dashboard/DashboardError', $props)
+ ->toResponse($request)
+ ->setStatusCode($statusCode);
+ }
+
+ /**
+ * Get a user-friendly error message based on status code.
+ */
+ private function getErrorMessage(Throwable $e, int $statusCode): string
+ {
+ $messages = [
+ 503 => 'Сервис временно недоступен. Попробуйте позже.',
+ 500 => 'Внутренняя ошибка сервера.',
+ 404 => 'Запрашиваемая страница не найдена.',
+ 403 => 'У вас нет доступа к этой странице.',
+ ];
+
+ return $messages[$statusCode] ?? 'Произошла неизвестная ошибка.';
+ }
+}
+```
+
+- [ ] **Step 3: Verify PHP syntax**
+
+Run: `docker exec ntspi-php php -l app/Exceptions/Handler.php && docker exec ntspi-php php -l app/Ship/Exceptions/Handler.php`
+Expected: No syntax errors
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add app/Exceptions/Handler.php app/Ship/Exceptions/Handler.php
+git commit -m "feat(dashboard): modify exception handlers to render DashboardError for dashboard routes"
+```
+
+---
+
+### Task 3: Verify End-to-End Flow
+
+**Covers:** S1, S2, S3 (Full verification)
+
+**Files:**
+- Verify: `resources/js/Pages/Dashboard/DashboardError.vue`
+- Verify: `app/Exceptions/Handler.php`
+- Verify: `app/Ship/Exceptions/Handler.php`
+
+- [ ] **Step 1: Test 404 on dashboard route**
+
+Navigate to: `http://localhost/dashboard/nonexistent-page`
+Expected: DashboardError page with status 404, dashboard layout visible
+
+- [ ] **Step 2: Test 403 on dashboard route (non-superadmin)**
+
+Login as non-superadmin user, navigate to restricted route
+Expected: DashboardError page with status 403, no stack trace or params
+
+- [ ] **Step 3: Test 403 on dashboard route (superadmin)**
+
+Login as superadmin, trigger 403
+Expected: DashboardError page with status 403, stack trace and params visible
+
+- [ ] **Step 4: Test public site error (unchanged)**
+
+Navigate to non-existent public page
+Expected: Original Error.vue with public layout
+
+- [ ] **Step 5: Final commit (if any fixes needed)**
+
+```bash
+git add -A
+git commit -m "fix(dashboard): address review feedback on error page"
+```
diff --git a/docs/compose/reports/dashboard-error-page.md b/docs/compose/reports/dashboard-error-page.md
new file mode 100644
index 0000000..1b9be53
--- /dev/null
+++ b/docs/compose/reports/dashboard-error-page.md
@@ -0,0 +1,82 @@
+---
+feature: dashboard-error-page
+status: delivered
+specs: []
+plans:
+ - docs/compose/plans/2026-07-05-dashboard-error-page.md
+branch: master
+commits: a4fef0b..df6110a
+---
+
+# Dashboard Error Page — Final Report
+
+## What Was Built
+
+A dedicated error page for the admin dashboard that displays full error context when HTTP errors (403, 404, 500, 503) occur on dashboard routes. Previously, dashboard errors redirected to a generic error page using the public site layout (navbar + footer), which was confusing for admin users.
+
+The new `DashboardError.vue` component renders within the Dashboard layout (sidebar + header) and shows:
+- Error status code and description
+- Request URL and HTTP method
+- Current user info (if authenticated)
+- Timestamp of the error
+- Stack trace and request parameters (visible only to super_admin users in non-production environments)
+
+## Architecture
+
+### Components
+
+| File | Role |
+|------|------|
+| `resources/js/Pages/Dashboard/DashboardError.vue` | Vue error page with full context display |
+| `app/Exceptions/Handler.php` | Detects dashboard routes, renders DashboardError |
+| `app/Ship/Exceptions/Handler.php` | Duplicate handler (Porto architecture) |
+
+### Data Flow
+
+```
+Exception thrown
+ ↓
+Handler.php render()
+ ↓
+isDashboardRequest()? ──Yes──→ renderDashboardError()
+ │ ↓
+ │ Check user role
+ │ ↓
+ │ Build props (status, message, url, etc.)
+ │ ↓
+ │ Inertia::render('Dashboard/DashboardError', $props)
+ │
+ └──No──→ Original Error.vue (public layout)
+```
+
+### Access Control
+
+- **All users**: See error status, description, URL, method, user info, timestamp
+- **Super_admin only** (non-production): Additionally see stack trace and request parameters
+- **Production**: Stack trace and params never shown (regardless of role)
+
+## Usage
+
+When a dashboard route returns an HTTP error (403, 404, 500, 503), the user is automatically redirected to the DashboardError page with full context. No configuration required.
+
+**Example scenarios:**
+- User navigates to `/dashboard/nonexistent-page` → 404 with URL info
+- User accesses restricted route without permission → 403 with user info
+- Server error occurs → 500 with timestamp
+
+**Navigation:**
+- "Назад" button: Returns to previous page (or dashboard index if no history)
+- "На главную" button: Navigates to dashboard index
+
+## Verification
+
+1. **404 test**: `curl -s -o /dev/null -w "%{http_code}" http://localhost/dashboard/nonexistent-page` → Returns `404`
+2. **Props verification**: Confirmed `status=404`, `message`, `url`, `method`, `timestamp` all present in Inertia page data
+3. **Stack trace hidden**: Confirmed stack trace and params not included for unauthenticated users
+4. **Public site unchanged**: Public site errors still render with original `Error.vue` layout
+5. **PHP syntax**: Both Handler files pass `php -l` syntax check
+
+## Journey Log
+
+- [lesson] Vite manifest requires rebuild after adding new Vue components — `npm run build` needed before testing
+- [lesson] Porto architecture has duplicate exception handlers (`app/Exceptions/` and `app/Ship/Exceptions/`) — both must be updated identically