diff --git a/.gitignore b/.gitignore
index 6b8c6cc..ea0a568 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ dist/
# dependencies
node_modules/
+Frontend/.pnpm-store/
# logs
npm-debug.log*
@@ -28,4 +29,4 @@ bin/
.cgcignore
-POSIX/
\ No newline at end of file
+POSIX/
diff --git a/Assets/favicon/android-chrome-192x192.png b/Assets/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000..e54cadb
Binary files /dev/null and b/Assets/favicon/android-chrome-192x192.png differ
diff --git a/Assets/favicon/android-chrome-512x512.png b/Assets/favicon/android-chrome-512x512.png
new file mode 100644
index 0000000..eac0355
Binary files /dev/null and b/Assets/favicon/android-chrome-512x512.png differ
diff --git a/Assets/favicon/apple-touch-icon-120x120.png b/Assets/favicon/apple-touch-icon-120x120.png
new file mode 100644
index 0000000..8223fd2
Binary files /dev/null and b/Assets/favicon/apple-touch-icon-120x120.png differ
diff --git a/Assets/favicon/apple-touch-icon-152x152.png b/Assets/favicon/apple-touch-icon-152x152.png
new file mode 100644
index 0000000..6dd338b
Binary files /dev/null and b/Assets/favicon/apple-touch-icon-152x152.png differ
diff --git a/Assets/favicon/apple-touch-icon-76x76.png b/Assets/favicon/apple-touch-icon-76x76.png
new file mode 100644
index 0000000..e7940ce
Binary files /dev/null and b/Assets/favicon/apple-touch-icon-76x76.png differ
diff --git a/Assets/favicon/apple-touch-icon.png b/Assets/favicon/apple-touch-icon.png
new file mode 100644
index 0000000..95ded3b
Binary files /dev/null and b/Assets/favicon/apple-touch-icon.png differ
diff --git a/Assets/favicon/browserconfig.xml b/Assets/favicon/browserconfig.xml
new file mode 100644
index 0000000..15e19a1
--- /dev/null
+++ b/Assets/favicon/browserconfig.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+ #ffffff
+
+
+
\ No newline at end of file
diff --git a/Assets/favicon/favicon-16x16.png b/Assets/favicon/favicon-16x16.png
new file mode 100644
index 0000000..7c365c6
Binary files /dev/null and b/Assets/favicon/favicon-16x16.png differ
diff --git a/Assets/favicon/favicon-32x32.png b/Assets/favicon/favicon-32x32.png
new file mode 100644
index 0000000..16ee74d
Binary files /dev/null and b/Assets/favicon/favicon-32x32.png differ
diff --git a/Assets/favicon/favicon-48x48.png b/Assets/favicon/favicon-48x48.png
new file mode 100644
index 0000000..109843b
Binary files /dev/null and b/Assets/favicon/favicon-48x48.png differ
diff --git a/Assets/favicon/favicon.ico b/Assets/favicon/favicon.ico
new file mode 100644
index 0000000..be8cb46
Binary files /dev/null and b/Assets/favicon/favicon.ico differ
diff --git a/Assets/favicon/mstile-150x150.png b/Assets/favicon/mstile-150x150.png
new file mode 100644
index 0000000..17ab98e
Binary files /dev/null and b/Assets/favicon/mstile-150x150.png differ
diff --git a/Assets/favicon/mstile-310x310.png b/Assets/favicon/mstile-310x310.png
new file mode 100644
index 0000000..c867751
Binary files /dev/null and b/Assets/favicon/mstile-310x310.png differ
diff --git a/Assets/favicon/site.webmanifest b/Assets/favicon/site.webmanifest
new file mode 100644
index 0000000..5c71353
--- /dev/null
+++ b/Assets/favicon/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "Moku Work",
+ "short_name": "Moku Work",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
\ No newline at end of file
diff --git a/Frontend/public/favicon.ico b/Frontend/public/favicon.ico
deleted file mode 100644
index fb282da..0000000
Binary files a/Frontend/public/favicon.ico and /dev/null differ
diff --git a/Frontend/public/favicon/android-chrome-192x192.png b/Frontend/public/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000..e54cadb
Binary files /dev/null and b/Frontend/public/favicon/android-chrome-192x192.png differ
diff --git a/Frontend/public/favicon/android-chrome-512x512.png b/Frontend/public/favicon/android-chrome-512x512.png
new file mode 100644
index 0000000..eac0355
Binary files /dev/null and b/Frontend/public/favicon/android-chrome-512x512.png differ
diff --git a/Frontend/public/favicon/apple-touch-icon-120x120.png b/Frontend/public/favicon/apple-touch-icon-120x120.png
new file mode 100644
index 0000000..8223fd2
Binary files /dev/null and b/Frontend/public/favicon/apple-touch-icon-120x120.png differ
diff --git a/Frontend/public/favicon/apple-touch-icon-152x152.png b/Frontend/public/favicon/apple-touch-icon-152x152.png
new file mode 100644
index 0000000..6dd338b
Binary files /dev/null and b/Frontend/public/favicon/apple-touch-icon-152x152.png differ
diff --git a/Frontend/public/favicon/apple-touch-icon-76x76.png b/Frontend/public/favicon/apple-touch-icon-76x76.png
new file mode 100644
index 0000000..e7940ce
Binary files /dev/null and b/Frontend/public/favicon/apple-touch-icon-76x76.png differ
diff --git a/Frontend/public/favicon/apple-touch-icon.png b/Frontend/public/favicon/apple-touch-icon.png
new file mode 100644
index 0000000..95ded3b
Binary files /dev/null and b/Frontend/public/favicon/apple-touch-icon.png differ
diff --git a/Frontend/public/favicon/browserconfig.xml b/Frontend/public/favicon/browserconfig.xml
new file mode 100644
index 0000000..3d89ee9
--- /dev/null
+++ b/Frontend/public/favicon/browserconfig.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+ #ffffff
+
+
+
diff --git a/Frontend/public/favicon/favicon-16x16.png b/Frontend/public/favicon/favicon-16x16.png
new file mode 100644
index 0000000..7c365c6
Binary files /dev/null and b/Frontend/public/favicon/favicon-16x16.png differ
diff --git a/Frontend/public/favicon/favicon-32x32.png b/Frontend/public/favicon/favicon-32x32.png
new file mode 100644
index 0000000..16ee74d
Binary files /dev/null and b/Frontend/public/favicon/favicon-32x32.png differ
diff --git a/Frontend/public/favicon/favicon-48x48.png b/Frontend/public/favicon/favicon-48x48.png
new file mode 100644
index 0000000..109843b
Binary files /dev/null and b/Frontend/public/favicon/favicon-48x48.png differ
diff --git a/Frontend/public/favicon/favicon.ico b/Frontend/public/favicon/favicon.ico
new file mode 100644
index 0000000..be8cb46
Binary files /dev/null and b/Frontend/public/favicon/favicon.ico differ
diff --git a/Frontend/public/favicon/mstile-150x150.png b/Frontend/public/favicon/mstile-150x150.png
new file mode 100644
index 0000000..17ab98e
Binary files /dev/null and b/Frontend/public/favicon/mstile-150x150.png differ
diff --git a/Frontend/public/favicon/mstile-310x310.png b/Frontend/public/favicon/mstile-310x310.png
new file mode 100644
index 0000000..c867751
Binary files /dev/null and b/Frontend/public/favicon/mstile-310x310.png differ
diff --git a/Frontend/public/favicon/site.webmanifest b/Frontend/public/favicon/site.webmanifest
new file mode 100644
index 0000000..e3a7b76
--- /dev/null
+++ b/Frontend/public/favicon/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "Moku Work",
+ "short_name": "Moku Work",
+ "icons": [
+ {
+ "src": "/favicon/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/favicon/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/Frontend/src/components/app-shell/AppShell/AppShell.tsx b/Frontend/src/components/app-shell/AppShell/AppShell.tsx
index 99a871d..c1e4916 100644
--- a/Frontend/src/components/app-shell/AppShell/AppShell.tsx
+++ b/Frontend/src/components/app-shell/AppShell/AppShell.tsx
@@ -1,11 +1,11 @@
// Path: Frontend/src/components/app-shell/AppShell/AppShell.tsx
import { useLocation } from "@solidjs/router";
-import { createSignal, onCleanup, onMount, Show, type JSX } from "solid-js";
+import { createEffect, createSignal, onCleanup, onMount, Show, type JSX } from "solid-js";
import { getDocumentTheme, setTheme, type Theme } from "../../../helper/themeRuntime";
import { BootstrapWizard } from "../../bootstrap/BootstrapWizard/BootstrapWizard";
import { AppShellDataProvider, useAppShellData } from "../data/app-shell.context";
-import { getWorkspaceBreadcrumbSegments } from "../data/workspace-routes";
+import { getWorkspaceBreadcrumbSegments, getWorkspaceDocumentTitle } from "../data/workspace-routes";
import { LeftRail } from "../../workspace-navigation/LeftRail/LeftRail";
import { MobileBottomNav } from "../MobileBottomNav/MobileBottomNav";
import { MobileWorkspaceBrowser } from "../../workspace-navigation/MobileWorkspaceBrowser/MobileWorkspaceBrowser";
@@ -94,6 +94,16 @@ const AppShellContent = (props: { children: JSX.Element }): JSX.Element => {
}),
].join(" / ");
+ createEffect((): void => {
+ if (typeof document === "undefined") {
+ return;
+ }
+
+ document.title = getWorkspaceDocumentTitle(location.pathname, {
+ activeProjectName: appShellData.activeProject().name,
+ });
+ });
+
return (
;
export type SettingsSectionKind = "account" | "workspace" | "server" | "theme" | "security" | "members";
@@ -11,6 +12,11 @@ export type WorkspaceBreadcrumbContext = {
activeTeamName?: string;
};
+export type WorkspaceTitleContext = {
+ activeProjectName: string;
+ appName?: string;
+};
+
const workspaceSurfaceRoutes: Record = {
home: "/workspace/home",
settings: "/settings",
@@ -77,3 +83,38 @@ export const getWorkspaceBreadcrumbSegments = (
return [context.activeProjectName, getWorkspaceSurfaceBreadcrumbLabel(pathname)];
};
+
+export const getWorkspaceDocumentTitle = (
+ pathname: string,
+ context: WorkspaceTitleContext,
+): string => {
+ const appName = context.appName?.trim() || undefined;
+ const settingsSection = getSettingsSectionFromPathname(pathname);
+
+ if (pathname === workspaceSurfaceRoutes.settings || pathname === "/workspace/settings") {
+ return buildPageTitle(appName, "Settings");
+ }
+
+ if (settingsSection) {
+ switch (settingsSection) {
+ case "account":
+ return buildPageTitle(appName, "Account Settings");
+ case "workspace":
+ return buildPageTitle(appName, "Workspace Settings");
+ case "server":
+ return buildPageTitle(appName, "Server Settings");
+ case "theme":
+ return buildPageTitle(appName, "Theme Settings");
+ case "security":
+ return buildPageTitle(appName, "Security Settings");
+ case "members":
+ return buildPageTitle(appName, "Members & Roles");
+ }
+ }
+
+ if (pathname.startsWith(workspaceSurfaceRoutes.home)) {
+ return buildPageTitle(appName, context.activeProjectName);
+ }
+
+ return buildPageTitle(appName);
+};
diff --git a/Frontend/src/entry-server.tsx b/Frontend/src/entry-server.tsx
index 0f1d7e8..46e3569 100644
--- a/Frontend/src/entry-server.tsx
+++ b/Frontend/src/entry-server.tsx
@@ -34,7 +34,15 @@ const renderDocument = ({ assets, children, scripts }: DocumentRenderProps): JSX
-
+
+
+
+
+
+
+
+
+
{assets}
diff --git a/Frontend/src/helper/pageTitle.ts b/Frontend/src/helper/pageTitle.ts
new file mode 100644
index 0000000..fbabd6d
--- /dev/null
+++ b/Frontend/src/helper/pageTitle.ts
@@ -0,0 +1,23 @@
+// Path: Frontend/src/helper/pageTitle.ts
+
+export const APP_PAGE_TITLE = "Moku Work";
+
+const normalizeTitleSegment = (segment: string | null | undefined | false): string | null => {
+ const value = typeof segment === "string" ? segment.trim() : "";
+ return value.length > 0 ? value : null;
+};
+
+export const buildPageTitle = (...segments: Array): string => {
+ const normalizedSegments = segments.map(normalizeTitleSegment).filter((segment): segment is string => segment !== null);
+ return [APP_PAGE_TITLE, ...normalizedSegments].join(" ยท ");
+};
+
+export const setPageTitle = (...segments: Array): string => {
+ const title = buildPageTitle(...segments);
+
+ if (typeof document !== "undefined") {
+ document.title = title;
+ }
+
+ return title;
+};