diff --git a/.trellis/spec/backend/drizzle-postgres-current.md b/.trellis/spec/backend/drizzle-postgres-current.md
index 972d806..424797a 100644
--- a/.trellis/spec/backend/drizzle-postgres-current.md
+++ b/.trellis/spec/backend/drizzle-postgres-current.md
@@ -377,7 +377,7 @@ await database
- Good: use one additive `care_tasks` table for MVP execution records instead of building a full recurring care-plan engine.
- Good: preserve `/app/health` separation; care execution is operational and not the health admin settings page.
- Base: `elderId` can be nullable for future public-area checks, but seeded MVP examples should use real elders.
-- Bad: render fake care metrics in `ModulePage` after the module becomes real.
+- Bad: render fake care metrics in a static placeholder after the module becomes real.
- Bad: update task status by ID alone without `organizationId`.
- Bad: only disable buttons in the UI while leaving Route Handlers on broad elder permissions.
diff --git a/.trellis/spec/frontend/components.md b/.trellis/spec/frontend/components.md
index 45bc9f0..28d9bc3 100644
--- a/.trellis/spec/frontend/components.md
+++ b/.trellis/spec/frontend/components.md
@@ -165,44 +165,17 @@ The project Dialog adapter must:
### Static Module Data Contract
-Static or reserved module pages must not fabricate operational data. If a module does
-not have a Drizzle-backed query/API and real persisted records, render a clear empty or
-not-connected state instead of generated counters, fake workflows, sample records, or
-mock priority/status rows.
+Operational module pages must not fabricate data. If a module does not have a
+Drizzle-backed query/API and real persisted records, render a clear empty or
+not-connected state inside that module's own route/component instead of generated
+counters, fake workflows, sample records, or mock priority/status rows.
-`modules/shared/components/ModulePage.tsx` is the shared placeholder for these modules.
-Its props should stay descriptive only:
+The old shared reserved-module placeholder components were removed after devices,
+notices, alerts, family, health, care, and emergency became real persisted workspaces.
+Do not reintroduce a generic `ReservedModulePages` layer for operational modules.
-```typescript
-type ModulePageProps = {
- title: string;
- eyebrow: string;
- description: string;
- icon: LucideIcon;
- phase: "待接入" | "二期预留" | "三期预留";
-};
-```
-
-```typescript
-// Bad: static pages pretending to have live business data.
-
-
-// Good: no fabricated records until a real data source exists.
-
-```
-
-When a module becomes real, replace the placeholder with a Server Component that loads
-data from the server boundary and pass only persisted, permission-filtered data into
+When a module becomes real, implement the route as a Server Component that loads data
+from the server boundary and passes only persisted, permission-filtered data into
client components.
### App Shell Tenant and Account Menu Contract
diff --git a/.trellis/tasks/00-bootstrap-guidelines/prd.md b/.trellis/tasks/archive/2026-07/00-bootstrap-guidelines/prd.md
similarity index 98%
rename from .trellis/tasks/00-bootstrap-guidelines/prd.md
rename to .trellis/tasks/archive/2026-07/00-bootstrap-guidelines/prd.md
index dbad710..1c94f8d 100644
--- a/.trellis/tasks/00-bootstrap-guidelines/prd.md
+++ b/.trellis/tasks/archive/2026-07/00-bootstrap-guidelines/prd.md
@@ -21,9 +21,9 @@ the rest conversationally.
## Status (update the checkboxes as you complete each item)
-- [ ] Fill backend guidelines
-- [ ] Fill frontend guidelines
-- [ ] Add code examples
+- [x] Fill backend guidelines
+- [x] Fill frontend guidelines
+- [x] Add code examples
---
diff --git a/.trellis/tasks/00-bootstrap-guidelines/task.json b/.trellis/tasks/archive/2026-07/00-bootstrap-guidelines/task.json
similarity index 92%
rename from .trellis/tasks/00-bootstrap-guidelines/task.json
rename to .trellis/tasks/archive/2026-07/00-bootstrap-guidelines/task.json
index 1f79863..14b4b3a 100644
--- a/.trellis/tasks/00-bootstrap-guidelines/task.json
+++ b/.trellis/tasks/archive/2026-07/00-bootstrap-guidelines/task.json
@@ -3,7 +3,7 @@
"name": "00-bootstrap-guidelines",
"title": "Bootstrap Guidelines",
"description": "Fill in project development guidelines for AI agents",
- "status": "in_progress",
+ "status": "completed",
"dev_type": "docs",
"scope": null,
"package": null,
@@ -11,7 +11,7 @@
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-01",
- "completedAt": null,
+ "completedAt": "2026-07-03",
"branch": null,
"base_branch": null,
"worktree_path": null,
diff --git a/app/(app)/app/settings/health/page.tsx b/app/(app)/app/settings/health/page.tsx
index ee05389..d05c0f9 100644
--- a/app/(app)/app/settings/health/page.tsx
+++ b/app/(app)/app/settings/health/page.tsx
@@ -1,5 +1,5 @@
-import { renderHealthWorkspacePage } from "@/modules/health/components/HealthWorkspacePage";
+import { renderHealthSettingsPage } from "@/modules/health/components/HealthWorkspacePage";
export default async function HealthSettingsPage(): Promise {
- return renderHealthWorkspacePage();
+ return renderHealthSettingsPage();
}
diff --git a/modules/health/components/HealthWorkspacePage.tsx b/modules/health/components/HealthWorkspacePage.tsx
index 95b5da2..16ff9ea 100644
--- a/modules/health/components/HealthWorkspacePage.tsx
+++ b/modules/health/components/HealthWorkspacePage.tsx
@@ -1,13 +1,52 @@
import { redirect } from "next/navigation";
-import { HeartPulse } from "lucide-react";
+import { Activity, FileText, HeartPulse, ShieldAlert, Stethoscope } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Table } from "@/components/ui/table";
import { getCurrentAuthContext } from "@/modules/core/server/auth";
import { hasPermission } from "@/modules/core/server/permissions";
import { HealthAdminClient } from "@/modules/health/components/HealthAdminClient";
import { listHealthAdminData } from "@/modules/health/server/operations";
+import type { HealthReviewSeverity, HealthReviewStatus } from "@/modules/health/types";
+import {
+ CHRONIC_CONDITION_STATUS_LABELS,
+ HEALTH_REVIEW_SEVERITY_LABELS,
+ HEALTH_REVIEW_STATUS_LABELS,
+ VITAL_SOURCE_LABELS,
+} from "@/modules/health/types";
import { getWorkspaceHref } from "@/modules/shared/lib/workspace-routing";
-export async function renderHealthWorkspacePage(): Promise {
+type HealthPageData = {
+ canManage: boolean;
+ data: Awaited>;
+};
+
+function formatDateTime(value: string | undefined): string {
+ return value ? new Date(value).toLocaleString("zh-CN") : "-";
+}
+
+function formatTenths(value: number | undefined, suffix = ""): string {
+ return value === undefined ? "-" : `${(value / 10).toFixed(1)}${suffix}`;
+}
+
+function severityBadgeVariant(severity: HealthReviewSeverity): "secondary" | "warning" | "danger" {
+ if (severity === "critical") {
+ return "danger";
+ }
+
+ return severity === "warning" ? "warning" : "secondary";
+}
+
+function reviewStatusBadgeVariant(status: HealthReviewStatus): "success" | "secondary" | "warning" {
+ if (status === "pending") {
+ return "warning";
+ }
+
+ return status === "resolved" ? "success" : "secondary";
+}
+
+async function loadHealthPageData(): Promise {
const context = await getCurrentAuthContext();
if (!context) {
redirect("/login");
@@ -23,6 +62,17 @@ export async function renderHealthWorkspacePage(): Promise {
}
const data = await listHealthAdminData(organizationId);
+ return {
+ canManage: hasPermission(context.permissions, "health:manage"),
+ data,
+ };
+}
+
+export async function renderHealthWorkspacePage(): Promise {
+ const { data } = await loadHealthPageData();
+ const pendingReviews = data.reviews.filter((review) => review.status === "pending").slice(0, 8);
+ const recentVitals = data.vitals.slice(0, 8);
+ const activeConditions = data.chronicConditions.filter((condition) => condition.status === "active").slice(0, 8);
return (
@@ -33,12 +83,172 @@ export async function renderHealthWorkspacePage(): Promise {