{protectedVisibleText}
} -{protectedVisibleText}
} -管理本人和其他人的出生资料。本人资料用于默认解盘,其他人资料可用于合盘或单独查看。
-请先在个人资料中补全你的出生资料。
} -还没有其他星盘,先添加一份资料。
} - {chartLibrary.filter((record) => record.role === "other").map((record) => ( -{synastryReportCard.headline}
} -{starterGreeting.salutation}
-{natalMinuteAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}
-- {rectificationErrorMessage} -
- )} - -{natalMinuteAvailable - ? "选择一个你现在想解决的问题。" - : "选择一个你现在想解决的问题;出生时间不足以支持的部分,我会明确说明,不会补造具体分钟。"}
-个性化问题暂时不可用,已显示安全的默认问题。
} -请先在个人资料中补全你的出生资料。
} +还没有其他星盘,先添加一份资料。
} + {chartLibrary.filter((record) => record.role === "other").map((record) => ( +{synastryReportCard.headline}
} +{protectedVisibleText}
} +{starterGreeting.salutation}
+{natalMinuteAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}
++ {rectificationErrorMessage} +
+ )} + +{natalMinuteAvailable + ? "选择一个你现在想解决的问题。" + : "选择一个你现在想解决的问题;出生时间不足以支持的部分,我会明确说明,不会补造具体分钟。"}
+个性化问题暂时不可用,已显示安全的默认问题。
} +\{starterGreeting\.salutation\}<\/p>/);
diff --git a/frontend/tests/rectification-agentic-entry.test.ts b/frontend/tests/rectification-agentic-entry.test.ts
index 3474cf38..50663c8c 100644
--- a/frontend/tests/rectification-agentic-entry.test.ts
+++ b/frontend/tests/rectification-agentic-entry.test.ts
@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
+import { homeSurface as page } from "./home-surface.ts";
const component = readFileSync(
new URL("../src/components/conversational-birth-time-rectification.tsx", import.meta.url),
@@ -22,7 +23,6 @@ const route = readFileSync(
new URL("../src/app/api/rectification/agent/route.ts", import.meta.url),
"utf8",
);
-const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const agent = readFileSync(
new URL("../src/mastra/agentic-rectification.ts", import.meta.url),
"utf8",
diff --git a/frontend/tests/settings-mvp-contract.test.ts b/frontend/tests/settings-mvp-contract.test.ts
index 6b29c4c6..f3466e1e 100644
--- a/frontend/tests/settings-mvp-contract.test.ts
+++ b/frontend/tests/settings-mvp-contract.test.ts
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
-const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
+import { homeSurface as page } from "./home-surface.ts";
const sidebar = readFileSync(new URL("../src/components/app-sidebar.tsx", import.meta.url), "utf8");
const overlay = readFileSync(new URL("../src/components/account-dialog-overlay.tsx", import.meta.url), "utf8");
@@ -32,7 +32,7 @@ test("personal profile is limited to account basics and links to chart settings"
});
test("the chart library owns self-profile editing and keeps other-chart save separate", () => {
- const charts = between(page, " renderChartLibrary() {", " renderGeneral() {");
+ const charts = readFileSync(new URL("../src/components/chart-library-panel.tsx", import.meta.url), "utf8");
assert.match(charts, /编辑本人资料/);
assert.match(charts, /onSubmit=\{saveProfile\}/);
assert.match(charts, /onSubmit=\{saveOtherChart\}/);
diff --git a/frontend/tests/starter-questions.test.ts b/frontend/tests/starter-questions.test.ts
index 0f915827..68245e47 100644
--- a/frontend/tests/starter-questions.test.ts
+++ b/frontend/tests/starter-questions.test.ts
@@ -10,7 +10,7 @@ import {
generalGuidedJyotishTopics,
} from "../src/lib/guided-jyotish-topics.ts";
-const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
+import { homeSurface as pageSource } from "./home-surface.ts";
const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const appSidebarSource = readFileSync(new URL("../src/components/app-sidebar.tsx", import.meta.url), "utf8");
const guidedTopicsSource = readFileSync(new URL("../src/lib/guided-jyotish-topics.ts", import.meta.url), "utf8");
diff --git a/scripts/run_quality_gate.py b/scripts/run_quality_gate.py
index 1b6a1ada..3fceaacb 100644
--- a/scripts/run_quality_gate.py
+++ b/scripts/run_quality_gate.py
@@ -63,6 +63,8 @@ CORE_PYTEST_TARGETS = [
# full pytest tree but are workflow_dispatch only, so a stale window_scan
# assertion in this glob stayed red on origin/staging until listed here.
"tests/test_rectification_*.py",
+ # This file regexes frontend source. Home-split and other page.tsx moves must keep it green.
+ "tests/test_supabase_user_data_contract.py",
]
RUNTIME_TRUTH_PYTEST_TARGETS = [
diff --git a/tests/test_birth_time_journey_contract.py b/tests/test_birth_time_journey_contract.py
index 87bd8d99..245628a3 100644
--- a/tests/test_birth_time_journey_contract.py
+++ b/tests/test_birth_time_journey_contract.py
@@ -127,7 +127,13 @@ def test_rectification_cases_are_owner_scoped_and_auditable() -> None:
def test_web_onboarding_uses_the_deterministic_free_journey() -> None:
- page = (FRONTEND / "src" / "app" / "page.tsx").read_text(encoding="utf-8")
+ page = "".join(
+ path.read_text(encoding="utf-8")
+ for path in (
+ FRONTEND / "src" / "app" / "page.tsx",
+ FRONTEND / "src" / "lib" / "home-profile.ts",
+ )
+ )
route = (
FRONTEND / "src" / "app" / "api" / "birth-time-journey" / "route.ts"
).read_text(encoding="utf-8")
diff --git a/tests/test_daily_and_rectification_entrypoints.py b/tests/test_daily_and_rectification_entrypoints.py
index e5ed0c96..73226bd3 100644
--- a/tests/test_daily_and_rectification_entrypoints.py
+++ b/tests/test_daily_and_rectification_entrypoints.py
@@ -2,12 +2,14 @@ from pathlib import Path
PAGE = Path("frontend/src/app/page.tsx")
+STARTER_HOME = Path("frontend/src/components/starter-home.tsx")
+HOME_CLOUD = Path("frontend/src/lib/home-cloud-sync.ts")
DAILY_ROUTE = Path("frontend/src/app/api/daily-starlanguage/route.ts")
RECTIFICATION_ROUTE = Path("frontend/src/app/api/birth-rectification/route.ts")
def test_daily_starlanguage_entrypoint_is_productized() -> None:
- source = PAGE.read_text(encoding="utf-8")
+ source = PAGE.read_text(encoding="utf-8") + STARTER_HOME.read_text(encoding="utf-8") + HOME_CLOUD.read_text(encoding="utf-8")
assert "今日星语" in source
assert "fetchDailyStarlanguage" in source
assert "daily-starlanguage-card" in source
@@ -30,7 +32,7 @@ def test_daily_starlanguage_api_declares_honest_source_boundary() -> None:
def test_birth_time_rectification_entrypoint_is_productized() -> None:
- source = PAGE.read_text(encoding="utf-8")
+ source = PAGE.read_text(encoding="utf-8") + STARTER_HOME.read_text(encoding="utf-8") + HOME_CLOUD.read_text(encoding="utf-8")
assert "生时校正" in source
assert "birth-rectification-card" in source
assert "openRectificationFromHomepage" in source
diff --git a/tests/test_supabase_user_data_contract.py b/tests/test_supabase_user_data_contract.py
index ec93afe5..b583a893 100644
--- a/tests/test_supabase_user_data_contract.py
+++ b/tests/test_supabase_user_data_contract.py
@@ -38,6 +38,24 @@ SYNASTRY_REPORT_MIGRATION = (
/ "20260718101000_repair_missing_synastry_reports.sql"
)
PAGE = Path(__file__).resolve().parents[1] / "frontend" / "src" / "app" / "page.tsx"
+_FRONTEND_SRC = Path(__file__).resolve().parents[1] / "frontend" / "src"
+HOME_SURFACE_FILES = (
+ PAGE,
+ _FRONTEND_SRC / "lib" / "home-types.ts",
+ _FRONTEND_SRC / "lib" / "home-profile.ts",
+ _FRONTEND_SRC / "lib" / "home-cloud-sync.ts",
+ _FRONTEND_SRC / "components" / "birth-location-fields.tsx",
+ _FRONTEND_SRC / "components" / "profile-fields.tsx",
+ _FRONTEND_SRC / "components" / "onboarding-chat-message.tsx",
+ _FRONTEND_SRC / "components" / "chart-library-panel.tsx",
+ _FRONTEND_SRC / "components" / "starter-home.tsx",
+)
+
+
+def _home_surface() -> str:
+ return "".join(path.read_text(encoding="utf-8") for path in HOME_SURFACE_FILES)
+
+
SESSION_CREATE_ROUTE = Path(__file__).resolve().parents[1] / "frontend" / "src" / "app" / "api" / "sessions" / "route.ts"
SESSION_ITEM_ROUTE = Path(__file__).resolve().parents[1] / "frontend" / "src" / "app" / "api" / "sessions" / "[id]" / "route.ts"
ACCOUNT_ROUTE = Path(__file__).resolve().parents[1] / "frontend" / "src" / "app" / "api" / "account" / "route.ts"
@@ -94,12 +112,16 @@ def test_user_profile_and_chat_session_database_contract() -> None:
def test_chat_page_uses_authenticated_cloud_persistence() -> None:
- source = PAGE.read_text(encoding="utf-8")
+ source = _home_surface()
create_route = SESSION_CREATE_ROUTE.read_text(encoding="utf-8")
item_route = SESSION_ITEM_ROUTE.read_text(encoding="utf-8")
- assert '.from("profiles")' in source
- assert '.from("chat_sessions")' in source
+ # Former value: page talked to Supabase with `.from("profiles")` / `.from("chat_sessions")`.
+ # Persistence is now same-origin APIs; the lock is still "no browser-owned writes".
+ assert 'fetch("/api/account"' in source
+ assert 'fetch("/api/sessions"' in source
+ assert '.from("profiles")' not in source
+ assert '.from("chat_sessions")' not in source
assert 'await writeChatSession(session.id, values, mode)' in source
assert 'mode === "create" ? "/api/sessions"' in (
Path(__file__).resolve().parents[1]
@@ -114,16 +136,22 @@ def test_chat_page_uses_authenticated_cloud_persistence() -> None:
assert 'await persistSession(userSession)' not in source
assert source.index('updateSession(sessionId, () => userSession)') < source.index('await persistSession(completedSession)')
assert 'function completedOnboardingTranscript(profile: Profile, greeting: string): Message[]' in source
- assert 'messages: [...preservedMessages, { role: "user", text: question }]' in source
+ # Former value: `messages: [...preservedMessages, { role: "user", text: question }]`
+ # The user turn is still appended unless the question is already present.
+ assert 'messages: questionAlreadyPresent ? preservedMessages : [...preservedMessages, { role: "user", text: question }]' in source
assert 'await persistSession(completedSession)' in source
assert 'const stoppedRequestAwaitingSettlement = useRef