Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions messages/ja/settings/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
"saveFailed": "保存に失敗しました",
"saveSettings": "設定を保存",
"saveSuccess": "正常に保存されました",
"publicStatusProjectionWarning": "システム設定は保存されましたが、public status の Redis 投影は更新されませんでした。",
"publicStatusBackgroundRefreshPending": "システム設定は保存されましたが、バックグラウンド更新が成功するまで公開ステータスページに古いデータが表示される場合があります。",
Comment on lines +90 to +91
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n messages/ja/settings/config.json | sed -n '30,100p'

Repository: ding113/claude-code-hub

Length of output: 5258


🏁 Script executed:

head -50 messages/ja/settings/config.json | cat -n

Repository: ding113/claude-code-hub

Length of output: 2862


🏁 Script executed:

tail -20 messages/ja/settings/config.json | cat -n

Repository: ding113/claude-code-hub

Length of output: 1161


删除重复的 JSON 键。

这两个键已在第 39-40 行定义,第 90-91 行的重复定义会导致后面的值覆盖前面的值,造成 JSON 验证和 linting 错误。

建议的修改
-    "publicStatusProjectionWarning": "システム設定は保存されましたが、public status の Redis 投影は更新されませんでした。",
-    "publicStatusBackgroundRefreshPending": "システム設定は保存されましたが、バックグラウンド更新が成功するまで公開ステータスページに古いデータが表示される場合があります。",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"publicStatusProjectionWarning": "システム設定は保存されましたが、public status の Redis 投影は更新されませんでした。",
"publicStatusBackgroundRefreshPending": "システム設定は保存されましたが、バックグラウンド更新が成功するまで公開ステータスページに古いデータが表示される場合があります。",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/ja/settings/config.json` around lines 90 - 91, Remove the duplicate
JSON keys "publicStatusProjectionWarning" and
"publicStatusBackgroundRefreshPending" in the settings config by deleting the
repeated definitions (the later occurrences) so only the original entries
remain; locate the duplicates by searching for those exact key names in the file
and remove the redundant key/value pairs, then run JSON linting to confirm no
duplicate-key errors remain.

"siteTitle": "サイトタイトル",
"siteTitleDesc": "ブラウザタブのタイトルとシステムのデフォルト表示名を設定するために使用されます。",
"siteTitlePlaceholder": "例:Claude Code Hub",
Expand Down
2 changes: 2 additions & 0 deletions messages/ru/settings/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
"saveFailed": "Ошибка сохранения",
"saveSettings": "Сохранить настройки",
"saveSuccess": "Сохранено успешно",
"publicStatusProjectionWarning": "Системные настройки сохранены, но Redis-проекция public status не была обновлена.",
"publicStatusBackgroundRefreshPending": "Системные настройки сохранены, но публичная статус-страница может временно показывать устаревшие данные, пока фоновое обновление не завершится.",
Comment on lines +90 to +91
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check file size and content around the mentioned lines
wc -l messages/ru/settings/config.json

Repository: ding113/claude-code-hub

Length of output: 103


🏁 Script executed:

# Read the file content with line numbers to verify the duplicate keys
cat -n messages/ru/settings/config.json

Repository: ding113/claude-code-hub

Length of output: 13762


删除重复的 JSON 键定义。

行 39-40 和行 90-91 中的 publicStatusProjectionWarningpublicStatusBackgroundRefreshPending 在同一 form 对象内存在重复。后面的定义会覆盖前面的值,造成维护困扰和隐性问题。

建议删除的重复部分
-    "publicStatusProjectionWarning": "Системные настройки сохранены, но Redis-проекция public status не была обновлена.",
-    "publicStatusBackgroundRefreshPending": "Системные настройки сохранены, но публичная статус-страница может временно показывать устаревшие данные, пока фоновое обновление не завершится.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"publicStatusProjectionWarning": "Системные настройки сохранены, но Redis-проекция public status не была обновлена.",
"publicStatusBackgroundRefreshPending": "Системные настройки сохранены, но публичная статус-страница может временно показывать устаревшие данные, пока фоновое обновление не завершится.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/ru/settings/config.json` around lines 90 - 91, The file contains
duplicate JSON keys "publicStatusProjectionWarning" and
"publicStatusBackgroundRefreshPending" in the same form object; remove the
redundant definitions so each key appears only once (keep the correct/desired
translation and delete the duplicate set), ensuring you update the entries for
publicStatusProjectionWarning and publicStatusBackgroundRefreshPending and leave
the remaining JSON structure intact.

"siteTitle": "Название сайта",
"siteTitleDesc": "Используется для установки заголовка вкладки браузера и имени системы по умолчанию.",
"siteTitlePlaceholder": "например: Claude Code Hub",
Expand Down
2 changes: 2 additions & 0 deletions messages/zh-TW/settings/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
"saveFailed": "儲存失敗",
"saveSettings": "儲存設定",
"saveSuccess": "儲存成功",
"publicStatusProjectionWarning": "系統設定已儲存,但 public status Redis 投影尚未刷新。",
"publicStatusBackgroundRefreshPending": "系統設定已儲存,但公開狀態頁可能會在背景刷新成功前暫時維持舊資料。",
Comment on lines +90 to +91
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n messages/zh-TW/settings/config.json | head -100

Repository: ding113/claude-code-hub

Length of output: 6192


删除 form 对象中重复的键。

publicStatusProjectionWarningpublicStatusBackgroundRefreshPending 已在第39-40行定义,第90-91行的重复定义会导致键覆盖,违反 JSON 最佳实践。应删除这两行的重复定义。

建议删除的重复定义
-    "publicStatusProjectionWarning": "系統設定已儲存,但 public status Redis 投影尚未刷新。",
-    "publicStatusBackgroundRefreshPending": "系統設定已儲存,但公開狀態頁可能會在背景刷新成功前暫時維持舊資料。",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"publicStatusProjectionWarning": "系統設定已儲存,但 public status Redis 投影尚未刷新。",
"publicStatusBackgroundRefreshPending": "系統設定已儲存,但公開狀態頁可能會在背景刷新成功前暫時維持舊資料。",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/zh-TW/settings/config.json` around lines 90 - 91, The JSON contains
duplicate keys "publicStatusProjectionWarning" and
"publicStatusBackgroundRefreshPending" (they already exist earlier in the same
object); remove the duplicate entries in the later occurrence so each key
appears only once in the form object, leaving the original definitions intact,
then validate the JSON to ensure there are no remaining duplicate keys or syntax
errors.

"siteTitle": "站台標題",
"siteTitleDesc": "用於設定瀏覽器分頁標題以及系統預設顯示名稱。",
"siteTitlePlaceholder": "例:Claude Code Hub",
Expand Down
4 changes: 2 additions & 2 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function generateMetadata({
try {
const metadata = await resolveSiteMetadataSource({ isPublicStatusRequest });
const title = metadata?.siteTitle?.trim() || DEFAULT_SITE_TITLE;
const description = metadata?.siteDescription?.trim() || DEFAULT_SITE_TITLE;
const description = metadata?.siteDescription?.trim() || title;

// Generate alternates for all locales
const alternates: Record<string, string> = {};
Expand Down Expand Up @@ -77,7 +77,7 @@ export default async function RootLayout({
}

// Load translation messages
const messages = await getMessages();
const messages = await getMessages({ locale });
const timeZone = await resolveLayoutTimeZone({ isPublicStatusRequest });
// Create a stable `now` timestamp to avoid SSR/CSR hydration mismatch for relative time
const now = new Date();
Expand Down
101 changes: 52 additions & 49 deletions src/app/[locale]/status/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { getTranslations } from "next-intl/server";
import { readPublicSiteMeta } from "@/lib/public-site-meta";
import { readCurrentPublicStatusConfigSnapshot } from "@/lib/public-status/config-snapshot";
import {
readCurrentPublicStatusConfigSnapshot,
readPublicStatusSiteMetadata,
} from "@/lib/public-status/config-snapshot";
import { readPublicStatusPayload } from "@/lib/public-status/read-store";
import { schedulePublicStatusRebuild } from "@/lib/public-status/rebuild-hints";
import { resolveSiteTitle } from "@/lib/site-title";
import { PublicStatusView } from "./_components/public-status-view";

export const dynamic = "force-dynamic";

const FALLBACK_SITE_TITLE = "Claude Code Hub";

export default async function PublicStatusPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations("settings");
const t = await getTranslations({ locale, namespace: "settings.statusPage.public" });
const configSnapshot = await readCurrentPublicStatusConfigSnapshot();
const siteMeta = await readPublicSiteMeta();
const siteMetadata = await readPublicStatusSiteMetadata();
const intervalMinutes = configSnapshot?.defaultIntervalMinutes ?? 5;
const rangeHours = configSnapshot?.defaultRangeHours ?? 24;
const followServerDefaults = !configSnapshot;
Expand All @@ -27,13 +30,11 @@ export default async function PublicStatusPage({
hasConfiguredGroups: configSnapshot ? configSnapshot.groups.length > 0 : undefined,
nowIso: new Date().toISOString(),
triggerRebuildHint: async (reason) => {
if (followServerDefaults) {
await schedulePublicStatusRebuild({
intervalMinutes,
rangeHours,
reason,
});
}
await schedulePublicStatusRebuild({
intervalMinutes,
rangeHours,
reason,
});
Comment on lines 32 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep stale-page fallback from failing on rebuild hint errors

readPublicStatusPayload is supposed to return stale/no-data payloads when data is missing, but this callback now unconditionally awaits schedulePublicStatusRebuild. That helper performs Redis writes without swallowing Redis command failures, so a transient set/get error can bubble up and make /status return a 500 instead of serving the fallback payload. This turns an intended best-effort hint into a hard dependency in the public request path.

Useful? React with 👍 / 👎.

},
});

Expand All @@ -44,51 +45,53 @@ export default async function PublicStatusPage({
rangeHours={rangeHours}
followServerDefaults={followServerDefaults}
locale={locale}
siteTitle={resolveSiteTitle(configSnapshot?.siteTitle, siteMeta.siteTitle)}
siteTitle={
siteMetadata?.siteTitle?.trim() || configSnapshot?.siteTitle?.trim() || FALLBACK_SITE_TITLE
}
timeZone={configSnapshot?.timeZone ?? "UTC"}
labels={{
systemStatus: t("statusPage.public.systemStatus"),
heroPrimary: t("statusPage.public.heroPrimary"),
heroSecondary: t("statusPage.public.heroSecondary"),
generatedAt: t("statusPage.public.generatedAt"),
history: t("statusPage.public.history"),
availability: t("statusPage.public.availability"),
ttfb: t("statusPage.public.ttfb"),
freshnessWindow: t("statusPage.public.freshnessWindow"),
fresh: t("statusPage.public.fresh"),
stale: t("statusPage.public.stale"),
staleDetail: t("statusPage.public.staleDetail"),
rebuilding: t("statusPage.public.rebuilding"),
noData: t("statusPage.public.noData"),
emptyDescription: t("statusPage.public.emptyDescription"),
systemStatus: t("systemStatus"),
heroPrimary: t("heroPrimary"),
heroSecondary: t("heroSecondary"),
generatedAt: t("generatedAt"),
history: t("history"),
availability: t("availability"),
ttfb: t("ttfb"),
freshnessWindow: t("freshnessWindow"),
fresh: t("fresh"),
stale: t("stale"),
staleDetail: t("staleDetail"),
rebuilding: t("rebuilding"),
noData: t("noData"),
emptyDescription: t("emptyDescription"),
requestTypes: {
openaiCompatible: t("statusPage.public.requestTypes.openaiCompatible"),
codex: t("statusPage.public.requestTypes.codex"),
anthropic: t("statusPage.public.requestTypes.anthropic"),
gemini: t("statusPage.public.requestTypes.gemini"),
openaiCompatible: t("requestTypes.openaiCompatible"),
codex: t("requestTypes.codex"),
anthropic: t("requestTypes.anthropic"),
gemini: t("requestTypes.gemini"),
},
statusBadge: {
operational: t("statusPage.public.statusBadge.operational"),
degraded: t("statusPage.public.statusBadge.degraded"),
failed: t("statusPage.public.statusBadge.failed"),
noData: t("statusPage.public.statusBadge.noData"),
operational: t("statusBadge.operational"),
degraded: t("statusBadge.degraded"),
failed: t("statusBadge.failed"),
noData: t("statusBadge.noData"),
},
tooltip: {
availability: t("statusPage.public.tooltip.availability"),
ttfb: t("statusPage.public.tooltip.ttfb"),
tps: t("statusPage.public.tooltip.tps"),
historyAriaLabel: t("statusPage.public.tooltip.historyAriaLabel"),
availability: t("tooltip.availability"),
ttfb: t("tooltip.ttfb"),
tps: t("tooltip.tps"),
historyAriaLabel: t("tooltip.historyAriaLabel"),
},
searchPlaceholder: t("statusPage.public.searchPlaceholder"),
customSort: t("statusPage.public.customSort"),
resetSort: t("statusPage.public.resetSort"),
emptyByFilter: t("statusPage.public.emptyByFilter"),
modelsLabel: t("statusPage.public.modelsLabel"),
issuesLabel: t("statusPage.public.issuesLabel"),
clearSearch: t("statusPage.public.clearSearch"),
dragHandle: t("statusPage.public.dragHandle"),
toggleGroup: t("statusPage.public.toggleGroup"),
openGroupPage: t("statusPage.public.openGroupPage"),
searchPlaceholder: t("searchPlaceholder"),
customSort: t("customSort"),
resetSort: t("resetSort"),
emptyByFilter: t("emptyByFilter"),
modelsLabel: t("modelsLabel"),
issuesLabel: t("issuesLabel"),
clearSearch: t("clearSearch"),
dragHandle: t("dragHandle"),
toggleGroup: t("toggleGroup"),
openGroupPage: t("openGroupPage"),
}}
/>
);
Expand Down
5 changes: 0 additions & 5 deletions src/app/api/public-status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export async function GET(request: Request): Promise<Response> {
const defaultRange = configSnapshot?.defaultRangeHours ?? 24;
const intervalMinutes = clampInterval(url.searchParams.get("interval"), defaultInterval);
const rangeHours = clampRange(url.searchParams.get("rangeHours"), defaultRange);
const canTriggerRebuild = intervalMinutes === defaultInterval && rangeHours === defaultRange;

const payload = await readPublicStatusPayload({
intervalMinutes,
Expand All @@ -35,9 +34,6 @@ export async function GET(request: Request): Promise<Response> {
hasConfiguredGroups: configSnapshot ? configSnapshot.groups.length > 0 : undefined,
nowIso: new Date().toISOString(),
triggerRebuildHint: async (reason) => {
if (!canTriggerRebuild) {
return;
}
await schedulePublicStatusRebuild({
intervalMinutes,
rangeHours,
Comment on lines 37 to 39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reintroduce default-query gate before scheduling rebuilds

The triggerRebuildHint callback now schedules rebuilds for every clamped interval/rangeHours request, including unauthenticated custom queries. Since the scheduler scans all public-status:v1:rebuild-hint:* keys and rebuilds each target (eventually querying message_request via queryPublicStatusRequests), a client can fan out to hundreds of combinations (4 intervals × 168 ranges) and keep forcing DB-heavy work. The removed canTriggerRebuild guard previously prevented this amplification for non-default queries, so this change introduces a production-facing DoS/performance regression.

Useful? React with 👍 / 👎.

Expand All @@ -47,6 +43,5 @@ export async function GET(request: Request): Promise<Response> {
});

const status = payload.rebuildState === "rebuilding" && !payload.generatedAt ? 503 : 200;

return NextResponse.json(payload, { status });
}
9 changes: 7 additions & 2 deletions src/lib/config/system-settings-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const DEFAULT_SETTINGS: Pick<
| "enableClaudeMetadataUserIdInjection"
| "enableResponseFixer"
| "responseFixerConfig"
| "publicStatusWindowHours"
| "publicStatusAggregationIntervalMinutes"
> = {
enableHttp2: false,
enableHighConcurrencyMode: false,
Expand All @@ -65,6 +67,8 @@ const DEFAULT_SETTINGS: Pick<
maxJsonDepth: 200,
maxFixSize: 1024 * 1024,
},
publicStatusWindowHours: 24,
publicStatusAggregationIntervalMinutes: 5,
};

/**
Expand Down Expand Up @@ -135,14 +139,15 @@ export async function getCachedSystemSettings(): Promise<SystemSettings> {
enableClaudeMetadataUserIdInjection: DEFAULT_SETTINGS.enableClaudeMetadataUserIdInjection,
enableResponseFixer: DEFAULT_SETTINGS.enableResponseFixer,
responseFixerConfig: DEFAULT_SETTINGS.responseFixerConfig,
publicStatusWindowHours: DEFAULT_SETTINGS.publicStatusWindowHours,
publicStatusAggregationIntervalMinutes:
DEFAULT_SETTINGS.publicStatusAggregationIntervalMinutes,
quotaDbRefreshIntervalSeconds: 10,
quotaLeasePercent5h: 0.05,
quotaLeasePercentDaily: 0.05,
quotaLeasePercentWeekly: 0.05,
quotaLeasePercentMonthly: 0.05,
quotaLeaseCapUsd: null,
publicStatusWindowHours: 24,
publicStatusAggregationIntervalMinutes: 5,
ipExtractionConfig: null,
ipGeoLookupEnabled: true,
createdAt: new Date(),
Expand Down
36 changes: 34 additions & 2 deletions src/lib/public-status/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ const CONFIG_CACHE_TTL_MS = 60 * 1000;
let cachedConfiguredGroups: EnabledPublicStatusGroup[] | null = null;
let cachedConfiguredGroupsAt = 0;

export class DuplicatePublicStatusGroupSlugError extends Error {
constructor(
public readonly publicGroupSlug: string,
groupNames: string[]
) {
super(
`Duplicate normalized publicGroupSlug "${publicGroupSlug}" for groups: ${groupNames.join(", ")}`
);
this.name = "DuplicatePublicStatusGroupSlugError";
}
}

function sanitizeString(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
Expand Down Expand Up @@ -136,6 +148,11 @@ export function slugifyPublicGroup(input: string): string {
.slice(0, 64);
}

function normalizePublicGroupSlug(groupName: string, publicGroupSlug?: string): string {
const normalized = slugifyPublicGroup(publicGroupSlug?.trim() || groupName);
return normalized || slugifyPublicGroup(groupName);
}
Comment on lines +151 to +154
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

避免返回空的 publicGroupSlug

slugifyPublicGroup() 对中文、日文或纯符号名称会返回 "";启用组未显式配置 URL-safe slug 时,这里会把空 slug 写入公开状态投影。请在规范化阶段抛出可被上层翻译的校验错误,或生成稳定的非空 fallback。

建议修正方向
 function normalizePublicGroupSlug(groupName: string, publicGroupSlug?: string): string {
   const normalized = slugifyPublicGroup(publicGroupSlug?.trim() || groupName);
-  return normalized || slugifyPublicGroup(groupName);
+  const fallback = normalized || slugifyPublicGroup(groupName);
+  if (!fallback) {
+    throw new Error(`Public status group "${groupName}" must define a URL-safe publicGroupSlug`);
+  }
+  return fallback;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/public-status/config.ts` around lines 151 - 154,
normalizePublicGroupSlug currently returns an empty string when
slugifyPublicGroup yields "" for names like Chinese/Japanese or symbol-only
group names; change normalizePublicGroupSlug to ensure a non-empty result by
either throwing a validation error that can be translated by callers or
generating a deterministic non-empty fallback slug (e.g., a stable hashed or
transliterated fallback) instead of returning "". Locate
normalizePublicGroupSlug and slugifyPublicGroup: if publicGroupSlug is provided,
trim and pass to slugifyPublicGroup, then if the result is empty either throw a
ValidationError (so the caller can translate it) or produce a stable fallback
value (e.g., "group-" + shortHash(groupName)) and return that; ensure the chosen
behavior is consistent with upstream error-handling expectations and tests.


export function parsePublicStatusDescription(
description: string | null | undefined
): ParsedPublicStatusDescription {
Expand Down Expand Up @@ -252,15 +269,30 @@ export function serializePublicStatusDescription(
export function collectEnabledPublicStatusGroups(
groups: PublicStatusConfiguredGroupInput[]
): EnabledPublicStatusGroup[] {
const seenGroupNamesBySlug = new Map<string, string>();

return groups
.map((group) => {
const publicModels = sanitizePublicModels(group.publicStatus?.publicModels);
const publicGroupSlug = normalizePublicGroupSlug(
group.groupName,
group.publicStatus?.publicGroupSlug
);

const existingGroupName = seenGroupNamesBySlug.get(publicGroupSlug);
if (existingGroupName) {
throw new DuplicatePublicStatusGroupSlugError(publicGroupSlug, [
Comment on lines +282 to +284
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate duplicate slugs after dropping empty public groups

Duplicate slug detection is executed before empty/invalid groups are removed (.filter((group) => group.publicModels.length > 0)), so a disabled group can still throw DuplicatePublicStatusGroupSlugError and cause savePublicStatusSettings to fail even though that group would not be published. This regresses prior behavior where empty groups were effectively ignored, and it can block legitimate saves when a non-public group shares a normalized slug with an enabled one.

Useful? React with 👍 / 👎.

existingGroupName,
group.groupName,
]);
}

seenGroupNamesBySlug.set(publicGroupSlug, group.groupName);

return {
groupName: group.groupName,
displayName: group.publicStatus?.displayName?.trim() || group.groupName,
publicGroupSlug:
group.publicStatus?.publicGroupSlug?.trim() || slugifyPublicGroup(group.groupName),
publicGroupSlug,
explanatoryCopy: group.publicStatus?.explanatoryCopy?.trim() || null,
sortOrder: group.publicStatus?.sortOrder ?? 0,
publicModels,
Expand Down
16 changes: 7 additions & 9 deletions src/lib/public-status/layout-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DEFAULT_SITE_TITLE, resolveSiteTitle } from "@/lib/site-title";
const FALLBACK_SITE_TITLE = "Claude Code Hub";

export async function resolveSiteMetadataSource(input: {
isPublicStatusRequest: boolean;
Expand All @@ -7,19 +7,17 @@ export async function resolveSiteMetadataSource(input: {
siteDescription: string;
} | null> {
if (input.isPublicStatusRequest) {
const { readPublicSiteMeta } = await import("@/lib/public-site-meta");
const metadata = await readPublicSiteMeta();
return {
siteTitle: metadata.siteTitle,
siteDescription: metadata.siteDescription,
};
const { readPublicStatusSiteMetadata } = await import("./config-snapshot");
return await readPublicStatusSiteMetadata();
}

const { getSystemSettings } = await import("@/repository/system-config");
const settings = await getSystemSettings();
const title = settings.siteTitle?.trim() || FALLBACK_SITE_TITLE;

return {
siteTitle: resolveSiteTitle(settings.siteTitle, DEFAULT_SITE_TITLE),
siteDescription: resolveSiteTitle(settings.siteTitle, DEFAULT_SITE_TITLE),
siteTitle: title,
siteDescription: title,
};
}

Expand Down
Loading