diff --git a/client/src/hooks/usePageTitle.ts b/client/src/hooks/usePageTitle.ts
new file mode 100644
index 0000000..90a1c27
--- /dev/null
+++ b/client/src/hooks/usePageTitle.ts
@@ -0,0 +1,17 @@
+import { useEffect } from 'react';
+
+const APP_NAME = 'Liqa';
+
+/**
+ * Sets document.title to "
| Liqa" for the current page.
+ * Pass undefined or empty string to fall back to just "Liqa".
+ */
+export function usePageTitle(title: string | null | undefined): void {
+ useEffect(() => {
+ const prev = document.title;
+ document.title = title ? `${title} | ${APP_NAME}` : APP_NAME;
+ return () => {
+ document.title = prev;
+ };
+ }, [title]);
+}
diff --git a/client/src/pages/credential/CreateCredentialPage.tsx b/client/src/pages/credential/CreateCredentialPage.tsx
index d55fb78..d7e4cf0 100644
--- a/client/src/pages/credential/CreateCredentialPage.tsx
+++ b/client/src/pages/credential/CreateCredentialPage.tsx
@@ -1,6 +1,7 @@
import { useState, SubmitEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { KeyRound, X, Save } from 'lucide-react';
import { credentials } from '../../api';
import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui';
@@ -8,6 +9,7 @@ import styles from '../Page.module.css';
export function CreateCredentialPage() {
const { t } = useTranslation();
+ usePageTitle(t('credentials.create_title'));
const navigate = useNavigate();
const toast = useToast();
diff --git a/client/src/pages/credential/CredentialDetailPage.tsx b/client/src/pages/credential/CredentialDetailPage.tsx
index 42b69ff..52e974c 100644
--- a/client/src/pages/credential/CredentialDetailPage.tsx
+++ b/client/src/pages/credential/CredentialDetailPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Upload, Pencil, Trash2, KeyRound } from 'lucide-react';
import { CodeBlock } from '../../ui';
import { stringify as yamlStringify } from 'yaml';
@@ -16,6 +17,7 @@ export function CredentialDetailPage() {
const navigate = useNavigate();
const [credential, setCredential] = useState(null);
const [loading, setLoading] = useState(true);
+ usePageTitle(credential?.name ?? null);
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
diff --git a/client/src/pages/credential/CredentialsPage.tsx b/client/src/pages/credential/CredentialsPage.tsx
index 63763c3..d74d996 100644
--- a/client/src/pages/credential/CredentialsPage.tsx
+++ b/client/src/pages/credential/CredentialsPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Settings, Pencil, Trash2, Plus, Download, KeyRound } from 'lucide-react';
import { parse as yamlParse } from 'yaml';
import { credentials } from '../../api';
@@ -100,6 +101,7 @@ function AddCredentialCard() {
export function CredentialsPage() {
const { t } = useTranslation();
+ usePageTitle(t('credentials.title'));
const navigate = useNavigate();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
diff --git a/client/src/pages/credential/EditCredentialPage.tsx b/client/src/pages/credential/EditCredentialPage.tsx
index 47834fa..71d544e 100644
--- a/client/src/pages/credential/EditCredentialPage.tsx
+++ b/client/src/pages/credential/EditCredentialPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { KeyRound, X, Save } from 'lucide-react';
import { credentials } from '../../api';
import type { Credential } from '../../api';
@@ -15,6 +16,7 @@ export function EditCredentialPage() {
const [credential, setCredential] = useState(null);
const [name, setName] = useState('');
+ usePageTitle(credential ? `${t('credentials.edit_title')} — ${credential.name}` : t('credentials.edit_title'));
const [data, setData] = useState('');
const [nameError, setNameError] = useState('');
const [dataError, setDataError] = useState('');
diff --git a/client/src/pages/environment/CreateEnvironmentPage.tsx b/client/src/pages/environment/CreateEnvironmentPage.tsx
index 601c700..dad804c 100644
--- a/client/src/pages/environment/CreateEnvironmentPage.tsx
+++ b/client/src/pages/environment/CreateEnvironmentPage.tsx
@@ -1,6 +1,7 @@
import { useState, SubmitEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Globe, Save } from 'lucide-react';
import { environments } from '../../api';
import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui';
@@ -8,6 +9,7 @@ import styles from '../Page.module.css';
export function CreateEnvironmentPage() {
const { t } = useTranslation();
+ usePageTitle(t('environments.create_title'));
const navigate = useNavigate();
const toast = useToast();
diff --git a/client/src/pages/environment/EditEnvironmentPage.tsx b/client/src/pages/environment/EditEnvironmentPage.tsx
index bfa598b..c4143a6 100644
--- a/client/src/pages/environment/EditEnvironmentPage.tsx
+++ b/client/src/pages/environment/EditEnvironmentPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Globe, Save } from 'lucide-react';
import { environments } from '../../api';
import type { Environment } from '../../api';
@@ -15,6 +16,7 @@ export function EditEnvironmentPage() {
const [env, setEnv] = useState(null);
const [name, setName] = useState('');
+ usePageTitle(env ? `${t('environments.edit_title')} — ${env.name}` : t('environments.edit_title'));
const [description, setDescription] = useState('');
const [dataJson, setDataJson] = useState('{}');
const [nameError, setNameError] = useState('');
diff --git a/client/src/pages/environment/EnvironmentDetailPage.tsx b/client/src/pages/environment/EnvironmentDetailPage.tsx
index 83b8f2b..0a83477 100644
--- a/client/src/pages/environment/EnvironmentDetailPage.tsx
+++ b/client/src/pages/environment/EnvironmentDetailPage.tsx
@@ -1,6 +1,7 @@
import { Globe, Pencil, Trash2, Upload } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { useNavigate, useParams } from 'react-router-dom';
import { stringify as yamlStringify } from 'yaml';
import type { Environment } from '../../api';
@@ -25,6 +26,7 @@ export function EnvironmentDetailPage() {
const navigate = useNavigate();
const [env, setEnv] = useState(null);
const [loading, setLoading] = useState(true);
+ usePageTitle(env?.name ?? null);
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
const dataEntries = Object.entries(env?.data ?? {}).filter(([, v]) => v);
diff --git a/client/src/pages/environment/EnvironmentsPage.tsx b/client/src/pages/environment/EnvironmentsPage.tsx
index 556e3ea..8076682 100644
--- a/client/src/pages/environment/EnvironmentsPage.tsx
+++ b/client/src/pages/environment/EnvironmentsPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Settings, ExternalLink, Pencil, Trash2, Plus, Globe, Download } from 'lucide-react';
import { parse as yamlParse } from 'yaml';
import { stringify as yamlStringify } from 'yaml';
@@ -117,6 +118,7 @@ function AddEnvironmentCard() {
export function EnvironmentsPage() {
const { t } = useTranslation();
+ usePageTitle(t('environments.title'));
const navigate = useNavigate();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
diff --git a/client/src/pages/run/AllRunsPage.tsx b/client/src/pages/run/AllRunsPage.tsx
index 8ead479..d877bfb 100644
--- a/client/src/pages/run/AllRunsPage.tsx
+++ b/client/src/pages/run/AllRunsPage.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Activity } from 'lucide-react';
import { runs } from '../../api';
import type { ScenarioRun, ScenarioRunStatus } from '../../api';
@@ -38,6 +39,7 @@ type AllRunRow = ScenarioRun & {
export function AllRunsPage() {
const { t } = useTranslation();
+ usePageTitle(t('runs.title'));
const navigate = useNavigate();
const [sortBy, setSortBy] = useState('createdAt');
diff --git a/client/src/pages/run/RunDetailPage.tsx b/client/src/pages/run/RunDetailPage.tsx
index c274821..a66d3db 100644
--- a/client/src/pages/run/RunDetailPage.tsx
+++ b/client/src/pages/run/RunDetailPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate, useParams, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { runs } from '../../api';
import type {
Scenario,
@@ -69,6 +70,7 @@ export function RunDetailPage() {
const [scenario, setScenario] = useState(null);
const [run, setRun] = useState(null);
const [loading, setLoading] = useState(true);
+ usePageTitle(scenario ? `${t('runs.title')} — ${scenario.name}` : t('runs.title'));
const [error, setError] = useState(null);
const [polling, setPolling] = useState(false);
const [pulseKey, setPulseKey] = useState(0);
diff --git a/client/src/pages/run/RunsPage.tsx b/client/src/pages/run/RunsPage.tsx
index f765838..ba9f321 100644
--- a/client/src/pages/run/RunsPage.tsx
+++ b/client/src/pages/run/RunsPage.tsx
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Play, ClipboardList, Activity } from 'lucide-react';
import { environments, scenarios, runs } from '../../api';
import type {
@@ -50,6 +51,7 @@ export function RunsPage() {
const [scenario, setScenario] = useState(null);
const [items, setItems] = useState([]);
+ usePageTitle(scenario ? `${t('runs.title')} — ${scenario.name}` : t('runs.title'));
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [pulseKey, setPulseKey] = useState(0);
diff --git a/client/src/pages/scenario/CreateScenarioPage.tsx b/client/src/pages/scenario/CreateScenarioPage.tsx
index 58cefb3..eaf04c1 100644
--- a/client/src/pages/scenario/CreateScenarioPage.tsx
+++ b/client/src/pages/scenario/CreateScenarioPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Save, ClipboardList } from 'lucide-react';
import { scenarios, environments } from '../../api';
import type { Environment } from '../../api';
@@ -9,6 +10,7 @@ import styles from '../Page.module.css';
export function CreateScenarioPage() {
const { t } = useTranslation();
+ usePageTitle(t('scenarios.create_title'));
const navigate = useNavigate();
const toast = useToast();
diff --git a/client/src/pages/scenario/CreateStepPage.tsx b/client/src/pages/scenario/CreateStepPage.tsx
index 65a9621..fc01299 100644
--- a/client/src/pages/scenario/CreateStepPage.tsx
+++ b/client/src/pages/scenario/CreateStepPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Save, ClipboardList } from 'lucide-react';
import { scenarios, steps } from '../../api';
import type { Scenario } from '../../api';
@@ -14,6 +15,7 @@ export function CreateStepPage() {
const toast = useToast();
const [scenario, setScenario] = useState(null);
+ usePageTitle(t('steps.create_title'));
const [title, setTitle] = useState('');
const [execCode, setExecCode] = useState('');
const [timeoutSeconds, setTimeoutSeconds] = useState('');
diff --git a/client/src/pages/scenario/EditScenarioPage.tsx b/client/src/pages/scenario/EditScenarioPage.tsx
index 695e9e9..800b485 100644
--- a/client/src/pages/scenario/EditScenarioPage.tsx
+++ b/client/src/pages/scenario/EditScenarioPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Save, ClipboardList } from 'lucide-react';
import { scenarios, environments } from '../../api';
import type { Scenario, Environment } from '../../api';
@@ -15,6 +16,7 @@ export function EditScenarioPage() {
const [scenario, setScenario] = useState(null);
const [name, setName] = useState('');
+ usePageTitle(scenario ? `${t('scenarios.edit_title')} — ${scenario.name}` : t('scenarios.edit_title'));
const [description, setDescription] = useState('');
const [environmentId, setEnvironmentId] = useState('');
const [timeoutSeconds, setTimeoutSeconds] = useState('');
diff --git a/client/src/pages/scenario/EditStepPage.tsx b/client/src/pages/scenario/EditStepPage.tsx
index 9f310ae..0714e59 100644
--- a/client/src/pages/scenario/EditStepPage.tsx
+++ b/client/src/pages/scenario/EditStepPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Save, ClipboardList } from 'lucide-react';
import { scenarios, steps } from '../../api';
import type { Scenario, ScenarioStep } from '../../api';
@@ -15,6 +16,7 @@ export function EditStepPage() {
const [scenario, setScenario] = useState(null);
const [step, setStep] = useState(null);
+ usePageTitle(step ? t('steps.edit_title', { order: step.order }) : t('steps.edit_title', { order: '' }));
const [title, setTitle] = useState('');
const [execCode, setExecCode] = useState('');
const [timeoutSeconds, setTimeoutSeconds] = useState('');
diff --git a/client/src/pages/scenario/ScenarioDetailPage.tsx b/client/src/pages/scenario/ScenarioDetailPage.tsx
index 7cfebf8..24d2c3e 100644
--- a/client/src/pages/scenario/ScenarioDetailPage.tsx
+++ b/client/src/pages/scenario/ScenarioDetailPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import {
Play,
Pencil,
@@ -51,6 +52,7 @@ export function ScenarioDetailPage() {
const toast = useToast();
const [scenario, setScenario] = useState<(Scenario & { steps: ScenarioStep[] }) | null>(null);
const [loading, setLoading] = useState(true);
+ usePageTitle(scenario?.name ?? null);
// Credentials state
const [scenarioCreds, setScenarioCreds] = useState([]);
diff --git a/client/src/pages/scenario/ScenariosPage.tsx b/client/src/pages/scenario/ScenariosPage.tsx
index aba85ed..41697b0 100644
--- a/client/src/pages/scenario/ScenariosPage.tsx
+++ b/client/src/pages/scenario/ScenariosPage.tsx
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Play, Plus, Trash2, Download, ClipboardList } from 'lucide-react';
import { parse as yamlParse } from 'yaml';
import { environments, scenarios } from '../../api';
@@ -38,6 +39,7 @@ const LAST_RUN_LABEL: Record = {
export function ScenariosPage() {
const { t } = useTranslation();
+ usePageTitle(t('scenarios.title'));
const navigate = useNavigate();
const toast = useToast();
const [items, setItems] = useState([]);
diff --git a/client/src/pages/session/SessionDetailPage.tsx b/client/src/pages/session/SessionDetailPage.tsx
index 2a65db3..70d7789 100644
--- a/client/src/pages/session/SessionDetailPage.tsx
+++ b/client/src/pages/session/SessionDetailPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Trash2, Monitor } from 'lucide-react';
import { sessions } from '../../api';
import type { Session } from '../../api';
@@ -14,6 +15,7 @@ export function SessionDetailPage() {
const navigate = useNavigate();
const [session, setSession] = useState(null);
const [loading, setLoading] = useState(true);
+ usePageTitle(session?.sessionName ?? null);
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
diff --git a/client/src/pages/session/SessionsPage.tsx b/client/src/pages/session/SessionsPage.tsx
index e5b179b..1c57321 100644
--- a/client/src/pages/session/SessionsPage.tsx
+++ b/client/src/pages/session/SessionsPage.tsx
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Trash2, Monitor } from 'lucide-react';
import { sessions } from '../../api';
import type { Session } from '../../api';
@@ -19,6 +20,7 @@ import styles from '../Page.module.css';
export function SessionsPage() {
const { t } = useTranslation();
+ usePageTitle(t('sessions.title'));
const navigate = useNavigate();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
diff --git a/client/src/pages/snippet/CreateSnippetPage.tsx b/client/src/pages/snippet/CreateSnippetPage.tsx
index 14fcfc8..6101334 100644
--- a/client/src/pages/snippet/CreateSnippetPage.tsx
+++ b/client/src/pages/snippet/CreateSnippetPage.tsx
@@ -1,6 +1,7 @@
import { useState, SubmitEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Save, Braces } from 'lucide-react';
import { snippets } from '../../api';
import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui';
@@ -8,6 +9,7 @@ import styles from '../Page.module.css';
export function CreateSnippetPage() {
const { t } = useTranslation();
+ usePageTitle(t('snippets.create_title'));
const navigate = useNavigate();
const toast = useToast();
diff --git a/client/src/pages/snippet/EditSnippetPage.tsx b/client/src/pages/snippet/EditSnippetPage.tsx
index 109b90d..3f6c3e6 100644
--- a/client/src/pages/snippet/EditSnippetPage.tsx
+++ b/client/src/pages/snippet/EditSnippetPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { X, Save, Braces } from 'lucide-react';
import { snippets } from '../../api';
import type { Snippet } from '../../api';
@@ -15,6 +16,7 @@ export function EditSnippetPage() {
const [snippet, setSnippet] = useState(null);
const [alias, setAlias] = useState('');
+ usePageTitle(snippet ? `${t('snippets.edit_title')} — ${snippet.title}` : t('snippets.edit_title'));
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [code, setCode] = useState('');
diff --git a/client/src/pages/snippet/SnippetDetailPage.tsx b/client/src/pages/snippet/SnippetDetailPage.tsx
index 963e8e2..db931a7 100644
--- a/client/src/pages/snippet/SnippetDetailPage.tsx
+++ b/client/src/pages/snippet/SnippetDetailPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Upload, Pencil, Trash2, Braces } from 'lucide-react';
import { CodeBlock } from '../../ui';
import { stringify as yamlStringify } from 'yaml';
@@ -24,6 +25,7 @@ export function SnippetDetailPage() {
const navigate = useNavigate();
const [snippet, setSnippet] = useState(null);
const [loading, setLoading] = useState(true);
+ usePageTitle(snippet?.title ?? null);
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
diff --git a/client/src/pages/snippet/SnippetsPage.tsx b/client/src/pages/snippet/SnippetsPage.tsx
index 876919c..1ef3730 100644
--- a/client/src/pages/snippet/SnippetsPage.tsx
+++ b/client/src/pages/snippet/SnippetsPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { usePageTitle } from '../../hooks/usePageTitle';
import { Settings, Pencil, Trash2, Plus, Download, Braces } from 'lucide-react';
import { parse as yamlParse } from 'yaml';
import { snippets } from '../../api';
@@ -114,6 +115,7 @@ function AddSnippetCard() {
export function SnippetsPage() {
const { t } = useTranslation();
+ usePageTitle(t('snippets.title'));
const navigate = useNavigate();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);