diff --git a/host/client_app/app.tsx b/host/client_app/app.tsx
index 58421e29..86a8de39 100644
--- a/host/client_app/app.tsx
+++ b/host/client_app/app.tsx
@@ -6,6 +6,7 @@ import { bootI18nFromInitialPage, subscribeI18nToNavigation } from './i18n';
import { resolvePage } from './pages';
createInertiaApp({
+ title: (title) => (title ? `${title} — SimpleModule` : 'SimpleModule'),
resolve: async (name) => {
const page = await resolvePage(name);
return page;
diff --git a/host/client_app/pages/Error.tsx b/host/client_app/pages/Error.tsx
index c1531af7..4f40bcab 100644
--- a/host/client_app/pages/Error.tsx
+++ b/host/client_app/pages/Error.tsx
@@ -1,4 +1,4 @@
-import { Link } from '@inertiajs/react';
+import { Head, Link } from '@inertiajs/react';
import { keys, useT } from '@simple-module-py/i18n';
import { ErrorScreen } from '@simple-module-py/ui/components/ErrorScreen';
import { Button } from '@simple-module-py/ui/components/ui/button';
@@ -34,18 +34,21 @@ function ErrorPage({ status, message }: Props) {
const description = message || descriptions[status] || t(keys.host.error.generic_description);
return (
-
-
-
-
+ <>
+
+
+
+
+
+ >
);
}
diff --git a/host/client_app/pages/Landing.tsx b/host/client_app/pages/Landing.tsx
index fc5a5938..b37bb36a 100644
--- a/host/client_app/pages/Landing.tsx
+++ b/host/client_app/pages/Landing.tsx
@@ -1,3 +1,4 @@
+import { Head } from '@inertiajs/react';
import { keys, useT } from '@simple-module-py/i18n';
import { Button } from '@simple-module-py/ui/components/ui/button';
import { Card, CardContent } from '@simple-module-py/ui/components/ui/card';
@@ -66,6 +67,7 @@ function Landing() {
return (
<>
+
{/* Hero with mesh blobs */}
-
- {retryable && (
-
-
- Retry task
-
- }
- onConfirm={handleRetry}
- />
- )}
-
- }
- >
-
-
- Details
-
-
-
- Status
- -
-
- {statusLabel(execution.status)}
-
-
-
-
-
-
-
-
-
-
-
-
- {execution.retried_from_id && (
+ <>
+
+
+
+ {retryable && (
+
+
+ Retry task
+
+ }
+ onConfirm={handleRetry}
+ />
+ )}
+
+ }
+ >
+
+
+ Details
+
-
- Retried from
+ - Status
-
-
- {execution.retried_from_id.slice(0, 8)}…
-
+
+ {statusLabel(execution.status)}
+
- )}
-
-
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+ {execution.retried_from_id && (
+
+
Retried from
+
+
+ {execution.retried_from_id.slice(0, 8)}…
+
+
+
+ )}
+
+
-
-
-
-
-
-
-
- {execution.result !== null && (
-
-
+
+
+
- )}
-
- {execution.traceback ? (
-
- {execution.traceback}
-
- ) : (
- No traceback recorded.
+
+
+
+ {execution.result !== null && (
+
+
+
)}
-
+
+ {execution.traceback ? (
+
+ {execution.traceback}
+
+ ) : (
+ No traceback recorded.
+ )}
+
+
-
-
+
+ >
);
}
diff --git a/modules/background_tasks/background_tasks/pages/Index.tsx b/modules/background_tasks/background_tasks/pages/Index.tsx
index 58bcd171..651cb837 100644
--- a/modules/background_tasks/background_tasks/pages/Index.tsx
+++ b/modules/background_tasks/background_tasks/pages/Index.tsx
@@ -1,4 +1,4 @@
-import { Link, router, usePage } from '@inertiajs/react';
+import { Head, Link, router, usePage } from '@inertiajs/react';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import { Button } from '@simple-module-py/ui/components/ui/button';
import { Card } from '@simple-module-py/ui/components/ui/card';
@@ -79,109 +79,112 @@ function Index() {
}
return (
-
-
-
-
- setSearch(e.target.value)}
- className="pl-9"
- />
+ <>
+
+
+
+
+
+ setSearch(e.target.value)}
+ className="pl-9"
+ />
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- Task
- Status
- Queue
- Queued
- Duration
- Worker
- Actions
-
-
-
- {executions.map((e) => (
-
- ))}
- {executions.length === 0 && (
+
+
+
-
-
-
-
- {search
- ? `No tasks match "${search}"`
- : 'No task executions yet. Tasks appear here as soon as modules enqueue work.'}
-
-
-
+ Task
+ Status
+ Queue
+ Queued
+ Duration
+ Worker
+ Actions
- )}
-
-
-
+
+
+ {executions.map((e) => (
+
+ ))}
+ {executions.length === 0 && (
+
+
+
+
+
+ {search
+ ? `No tasks match "${search}"`
+ : 'No task executions yet. Tasks appear here as soon as modules enqueue work.'}
+
+
+
+
+ )}
+
+
+
- {totalPages > 1 && (
-
-
-
- Page {pagination.page} of {totalPages}
-
-
-
- )}
-
+ {totalPages > 1 && (
+
+
+
+ Page {pagination.page} of {totalPages}
+
+
+
+ )}
+
+ >
);
}
diff --git a/modules/background_tasks/background_tasks/pages/Workers.tsx b/modules/background_tasks/background_tasks/pages/Workers.tsx
index 41b82491..13916256 100644
--- a/modules/background_tasks/background_tasks/pages/Workers.tsx
+++ b/modules/background_tasks/background_tasks/pages/Workers.tsx
@@ -1,4 +1,4 @@
-import { Link, usePage } from '@inertiajs/react';
+import { Head, Link, usePage } from '@inertiajs/react';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import { Badge } from '@simple-module-py/ui/components/ui/badge';
import { Button } from '@simple-module-py/ui/components/ui/button';
@@ -91,62 +91,65 @@ function Workers() {
}
return (
-
-
-
-
-
- Last updated {formatTs(snapshot.polled_at)}
-
-
+
+
+
+
+
+ Last updated {formatTs(snapshot.polled_at)}
+
+
+
-
- {!snapshot.broker_reachable ? (
-
-
-
-
-
Broker unreachable
-
- {snapshot.error ?? 'No error message reported.'}
-
-
- Check the SM_BG_TASKS_BROKER_URL setting and confirm the broker process
- is running.
-
+ {!snapshot.broker_reachable ? (
+
+
+
+
+
Broker unreachable
+
+ {snapshot.error ?? 'No error message reported.'}
+
+
+ Check the SM_BG_TASKS_BROKER_URL setting and confirm the broker
+ process is running.
+
+
-
-
- ) : snapshot.workers.length === 0 ? (
-
-
-
-
-
No workers connected
-
- The broker is reachable but no Celery workers are responding. Start one with{' '}
- uv run python scripts/run_worker.py.
-
+
+ ) : snapshot.workers.length === 0 ? (
+
+
+
+
+
No workers connected
+
+ The broker is reachable but no Celery workers are responding. Start one with{' '}
+ uv run python scripts/run_worker.py.
+
+
+
+ ) : (
+
+ {snapshot.workers.map((w) => (
+
+ ))}
-
- ) : (
-
- {snapshot.workers.map((w) => (
-
- ))}
-
- )}
-
+ )}
+
+ >
);
}
diff --git a/modules/dashboard/dashboard/pages/Doctor.tsx b/modules/dashboard/dashboard/pages/Doctor.tsx
index a770a140..df45b47e 100644
--- a/modules/dashboard/dashboard/pages/Doctor.tsx
+++ b/modules/dashboard/dashboard/pages/Doctor.tsx
@@ -1,4 +1,4 @@
-import { usePage } from '@inertiajs/react';
+import { Head, usePage } from '@inertiajs/react';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import { SectionTitle } from '@simple-module-py/ui/components/SectionTitle';
import { StatCard } from '@simple-module-py/ui/components/StatCard';
@@ -77,196 +77,199 @@ function Doctor() {
const unhealthy = system_info.health_checks.filter((c) => c.status !== 'healthy').length;
return (
-
-
-
- >
- }
- >
-
-
-
-
-
-
+ <>
+
+
+
+
+ >
+ }
+ >
+
+
+
+
+
+
-
-
-
-
- just now
- }
- >
- Static checks
-
-
- {STATIC_CHECKS.map((c) => (
-
- ))}
-
-
-
+
+
+
+
+ just now
+ }
+ >
+ Static checks
+
+
+ {STATIC_CHECKS.map((c) => (
+
+ ))}
+
+
+
-
-
-
-
-
-
- }
- >
- Recent migrations
-
-
- {MIGRATIONS.map((m) => (
-
-
- {m.id}
-
-
- {m.module}
-
-
{m.msg}
-
{m.when}
-
- {m.applied ? 'applied' : 'pending'}
-
-
- ))}
-
-
-
+
+
+
+
+
+
+ }
+ >
+ Recent migrations
+
+
+ {MIGRATIONS.map((m) => (
+
+
+ {m.id}
+
+
+ {m.module}
+
+
{m.msg}
+
{m.when}
+
+ {m.applied ? 'applied' : 'pending'}
+
+
+ ))}
+
+
+
-
-
- Installed modules
-
- {system_info.modules.map((m) => (
-
-
-
-
-
-
- {m.name}
+
+
+ Installed modules
+
+ {system_info.modules.map((m) => (
+
+
+
+
+
-
loaded
+
+ active
+
-
- active
-
-
- ))}
-
-
-
-
+ ))}
+
+
+
+
-
-
-
-
-
- running
-
- }
- >
- Dev server
-
-
- {DEV_SERVER.map(([k, v, tone]) => (
-
- {k}
-
- {v}
-
-
- ))}
-
-
-
-
-
-
- Run a command
-
- {['make new-module name=orders', 'make migrate', 'make doctor', 'make dev'].map(
- (c) => (
-
-
$
-
{c}
+
+
+
+
+
+ running
+
+ }
+ >
+ Dev server
+
+
+ {DEV_SERVER.map(([k, v, tone]) => (
+
+ {k}
+
+ {v}
+
- ),
- )}
-
-
-
+ ))}
+
+
+
+
+
+
+ Run a command
+
+ {['make new-module name=orders', 'make migrate', 'make doctor', 'make dev'].map(
+ (c) => (
+
+ $
+ {c}
+
+ ),
+ )}
+
+
+
-
-
-
-
- Environment
-
-
- {ENV_VARS.map(([k, v]) => (
-
- {k}
-
- {v}
-
-
- ))}
-
-
-
+
+
+
+
+ Environment
+
+
+ {ENV_VARS.map(([k, v]) => (
+
+ {k}
+
+ {v}
+
+
+ ))}
+
+
+
+
-
-
+
+ >
);
}
diff --git a/modules/dashboard/dashboard/pages/Home.tsx b/modules/dashboard/dashboard/pages/Home.tsx
index 0732364a..ca5b9df3 100644
--- a/modules/dashboard/dashboard/pages/Home.tsx
+++ b/modules/dashboard/dashboard/pages/Home.tsx
@@ -1,4 +1,4 @@
-import { usePage } from '@inertiajs/react';
+import { Head, usePage } from '@inertiajs/react';
import { keys, useT } from '@simple-module-py/i18n';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import { SectionTitle } from '@simple-module-py/ui/components/SectionTitle';
@@ -38,68 +38,71 @@ function Home() {
const unhealthy = props.system_info.health_checks.filter((c) => c.status !== 'healthy').length;
return (
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
-
-
-
-
- Python {props.system_info.python_version} · {props.module_count} modules
-
- }
- >
- System
-
-
- {props.system_info.modules.map((m) => (
-
-
-
+
+
+
+
+ Python {props.system_info.python_version} · {props.module_count} modules
- {m.name}
-
- ))}
-
-
-
+ }
+ >
+ System
+
+
+ {props.system_info.modules.map((m) => (
+
+
+
+
+ {m.name}
+
+ ))}
+
+
+
- {import.meta.env.DEV &&
}
-
-
+ {import.meta.env.DEV && }
+
+
+ >
);
}
diff --git a/modules/feature_flags/feature_flags/pages/Browse.tsx b/modules/feature_flags/feature_flags/pages/Browse.tsx
index 1699bbbb..5dda3df2 100644
--- a/modules/feature_flags/feature_flags/pages/Browse.tsx
+++ b/modules/feature_flags/feature_flags/pages/Browse.tsx
@@ -1,4 +1,4 @@
-import { router, usePage } from '@inertiajs/react';
+import { Head, router, usePage } from '@inertiajs/react';
import { keys, useT } from '@simple-module-py/i18n';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import { Badge } from '@simple-module-py/ui/components/ui/badge';
@@ -91,189 +91,194 @@ function Browse() {
}
return (
-
-
-
-
-
-
-
-
- {t(keys.feature_flags.table.name)}
-
-
- {t(keys.feature_flags.table.description)}
-
-
- {t(keys.feature_flags.table.default)}
-
-
- {t(keys.feature_flags.table.status)}
-
- {canManage && (
-
- {t(keys.feature_flags.table.actions)}
+
+ {flags.length > 0 && (
+
+ {t(keys.feature_flags.browse.count, { count: flags.length })}
+
+ )}
+
+
+
+
+
+
+
+ {t(keys.feature_flags.table.name)}
- )}
-
-
-
- {flags.map((flag) => (
-
-
-
- {flag.name}
- {flag.overridden && (
-
- {t(keys.feature_flags.table.overridden)}
-
- )}
-
-
-
-
- {flag.description || '—'}
-
-
-
-
- {flag.default_enabled
- ? t(keys.feature_flags.table.enabled)
- : t(keys.feature_flags.table.disabled)}
-
-
-
-
-
handleToggle(flag, checked === true)}
- disabled={!canManage}
- aria-label={flag.name}
- />
-
-
- {flag.enabled
- ? t(keys.feature_flags.table.enabled)
- : t(keys.feature_flags.table.disabled)}
-
- {tenant_id && flag.system_enabled !== null && (
-
- {t(keys.feature_flags.table.system_value, {
- value: flag.system_enabled
- ? t(keys.feature_flags.table.enabled)
- : t(keys.feature_flags.table.disabled),
- })}
+
+ {t(keys.feature_flags.table.description)}
+
+
+ {t(keys.feature_flags.table.default)}
+
+
+ {t(keys.feature_flags.table.status)}
+
+ {canManage && (
+
+ {t(keys.feature_flags.table.actions)}
+
+ )}
+
+
+
+ {flags.map((flag) => (
+
+
+
+ {flag.name}
+ {flag.overridden && (
+
+
+ {t(keys.feature_flags.table.overridden)}
+
)}
-
-
- {canManage && (
-
- {flag.overridden ? (
-
+
+
+ {flag.description || '—'}
+
+
+
+
+ {flag.default_enabled
+ ? t(keys.feature_flags.table.enabled)
+ : t(keys.feature_flags.table.disabled)}
+
+
+
+
+
handleToggle(flag, checked === true)}
+ disabled={!canManage}
+ aria-label={flag.name}
+ />
+
+
+ {flag.enabled
+ ? t(keys.feature_flags.table.enabled)
+ : t(keys.feature_flags.table.disabled)}
-
- ) : (
-
- {tenant_id
- ? t(keys.feature_flags.table.following_system)
- : t(keys.feature_flags.table.following_default)}
-
- )}
+ {tenant_id && flag.system_enabled !== null && (
+
+ {t(keys.feature_flags.table.system_value, {
+ value: flag.system_enabled
+ ? t(keys.feature_flags.table.enabled)
+ : t(keys.feature_flags.table.disabled),
+ })}
+
+ )}
+
+
- )}
-
- ))}
- {flags.length === 0 && (
-
-
-
-
-
-
- {t(keys.feature_flags.browse.empty_title)}
-
- {t(keys.feature_flags.browse.empty_description)}
-
-
-
-
- )}
-
-
-
-
+ {canManage && (
+
+ {flag.overridden ? (
+
+ ) : (
+
+ {tenant_id
+ ? t(keys.feature_flags.table.following_system)
+ : t(keys.feature_flags.table.following_default)}
+
+ )}
+
+ )}
+
+ ))}
+ {flags.length === 0 && (
+
+
+
+
+
+
+ {t(keys.feature_flags.browse.empty_title)}
+
+ {t(keys.feature_flags.browse.empty_description)}
+
+
+
+
+ )}
+
+
+
+
+ >
);
}
diff --git a/modules/file_storage/file_storage/pages/Browse.tsx b/modules/file_storage/file_storage/pages/Browse.tsx
index 1f023d5f..5619c5e0 100644
--- a/modules/file_storage/file_storage/pages/Browse.tsx
+++ b/modules/file_storage/file_storage/pages/Browse.tsx
@@ -1,4 +1,4 @@
-import { router, usePage } from '@inertiajs/react';
+import { Head, router, usePage } from '@inertiajs/react';
import { keys, useT } from '@simple-module-py/i18n';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import {
@@ -81,108 +81,111 @@ function Browse() {
}
return (
- : undefined}
- >
-
-
-
-
- {t(keys.file_storage.table.filename)}
-
- {t(keys.file_storage.table.type)}
-
- {t(keys.file_storage.table.size)}
-
- {t(keys.file_storage.table.uploaded_by)}
-
-
- {t(keys.file_storage.table.actions)}
-
-
-
-
- {files.map((file) => (
-
- {file.filename}
-
- {file.content_type}
-
-
- {formatBytes(file.size_bytes)}
-
-
- {file.uploaded_by ?? UNKNOWN_UPLOADER}
-
-
-
-
- {canDelete && (
-
-
-
-
-
-
-
- {t(keys.file_storage.delete_dialog.title, { name: file.filename })}
-
-
- {t(keys.file_storage.delete_dialog.description)}
-
-
-
-
- {t(keys.file_storage.delete_dialog.cancel)}
-
- handleDelete(file)}
- className="bg-destructive text-white hover:bg-destructive/90"
- >
- {t(keys.file_storage.delete_dialog.confirm)}
-
-
-
-
- )}
-
-
-
- ))}
- {files.length === 0 && pagination.total === 0 && (
+ <>
+
+ : undefined}
+ >
+
+
+
-
-
-
-
-
- {t(keys.file_storage.browse.empty_title)}
-
- {t(keys.file_storage.browse.empty_description)}
-
-
-
+ {t(keys.file_storage.table.filename)}
+
+ {t(keys.file_storage.table.type)}
+
+ {t(keys.file_storage.table.size)}
+
+ {t(keys.file_storage.table.uploaded_by)}
+
+
+ {t(keys.file_storage.table.actions)}
+
- )}
-
-
-
-
+
+
+ {files.map((file) => (
+
+ {file.filename}
+
+ {file.content_type}
+
+
+ {formatBytes(file.size_bytes)}
+
+
+ {file.uploaded_by ?? UNKNOWN_UPLOADER}
+
+
+
+
+ {canDelete && (
+
+
+
+
+
+
+
+ {t(keys.file_storage.delete_dialog.title, { name: file.filename })}
+
+
+ {t(keys.file_storage.delete_dialog.description)}
+
+
+
+
+ {t(keys.file_storage.delete_dialog.cancel)}
+
+ handleDelete(file)}
+ className="bg-destructive text-white hover:bg-destructive/90"
+ >
+ {t(keys.file_storage.delete_dialog.confirm)}
+
+
+
+
+ )}
+
+
+
+ ))}
+ {files.length === 0 && pagination.total === 0 && (
+
+
+
+
+
+
+ {t(keys.file_storage.browse.empty_title)}
+
+ {t(keys.file_storage.browse.empty_description)}
+
+
+
+
+ )}
+
+
+
+
+ >
);
}
diff --git a/modules/permissions/permissions/pages/RoleEdit.tsx b/modules/permissions/permissions/pages/RoleEdit.tsx
index 6a04f0cc..26fb5d19 100644
--- a/modules/permissions/permissions/pages/RoleEdit.tsx
+++ b/modules/permissions/permissions/pages/RoleEdit.tsx
@@ -1,4 +1,4 @@
-import { Link, useForm } from '@inertiajs/react';
+import { Head, Link, useForm } from '@inertiajs/react';
import { keys, useT } from '@simple-module-py/i18n';
import { PageShell } from '@simple-module-py/ui/components/PageShell';
import { Badge } from '@simple-module-py/ui/components/ui/badge';
@@ -63,129 +63,132 @@ function RoleEdit({ role, assigned, groups }: Props) {
const pct = totalRegistered === 0 ? 0 : (data.permissions.length / totalRegistered) * 100;
return (
-
-
-
-
- >
- }
- >
-