diff --git a/.workbuddy/memory/2026-09-17.md b/.workbuddy/memory/2026-09-17.md
new file mode 100644
index 0000000..b4259b5
--- /dev/null
+++ b/.workbuddy/memory/2026-09-17.md
@@ -0,0 +1,105 @@
+# 2026-09-17 工作日志
+
+## 修复详情页空壳 + 评估 components/views 目录 ✅(15:12–15:30)
+用户问 `components/views/` 是否还有必要(pages 不就是 view 吗),并指出 detail 组件是空的,疑似之前拆文件拆错。
+
+### 根因(已查清)
+- 详情实现原本在 `src/components/views/ItemPage.astro`(一个组件用 `type` 参数通吃 runway/street,含收藏/历史脚本)。
+- commit `70a3304`(拆分)把 4 个 `[id].astro` 的 import 从 `ItemPage`(带 `type`)改成 `RunwayLooksDetail`/`StreetSnapsDetail`,但**这两个新文件建成空的(0 字节)**,且把 `ItemPage.astro` **删除了**。
+- 后果:走秀/街拍详情页(`prerender=false` 的动态路由)此后一直渲染空壳,无报错不易察觉。
+- 验证手段:`git log --all -- '*ItemPage*'` 定位 → `git show ad028c9:...ItemPage.astro` 找回 158 行完整实现;`70a3304` 该路径已不存在(在另一分支)。
+
+### 修复(恢复原始可用结构)
+- 从 `ad028c9` 恢复 `src/components/views/ItemPage.astro`(内容逐字还原,含 Props 接口、SSR 取数、TDK、收藏/历史 `
diff --git a/src/components/views/StreetSnapsDetail.astro b/src/components/views/StreetSnapsDetail.astro
index e69de29..9d6e12c 100644
--- a/src/components/views/StreetSnapsDetail.astro
+++ b/src/components/views/StreetSnapsDetail.astro
@@ -0,0 +1,72 @@
+---
+import Layout from "@/layouts/Layout.astro"
+import StreetSnapCard from "@/components/StreetSnapCard.astro"
+import { getSsrStreetSnap } from "@/lib/ssr"
+import { getI18n } from "@/lib/i18n"
+import { config } from "@/lib/config"
+
+// 街拍(street snap)详情视图,en/cn 两页共用(语言由 Astro.currentLocale 运行时决定)。
+// 注意:export const prerender = false 必须留在页面文件(Astro 只读页面模块的该导出),
+// 故 id 由页面从 Astro.params 取出后作为 prop 透传进来。
+interface Props {
+ id?: string
+}
+
+const { id } = Astro.props
+const { locale, t } = getI18n(Astro)
+
+let snap = null
+let snapErr = null
+let preview = false
+let imageTotal = 0
+
+const data = await getSsrStreetSnap(id, Astro.currentLocale)
+snap = "snap" in data ? data.snap : null
+snapErr = data.error
+preview = data.preview ?? false
+imageTotal = data.imageTotal ?? 0
+
+// 供客户端「收藏 / 历史」使用的精简元信息(服务端已拿到,直接透传,避免前端再发请求)。
+const canonId = String(snap?.id ?? id ?? "")
+const meta = { id: canonId, type: "street", title: snap?.title ?? "", cover: snap?.cover ?? "", brand: "" }
+
+// 详情页动态 TDK:标题用街拍标题,描述/关键词按语言生成(避免嵌套反引号,用拼接)。
+const SITE_NAME = config.siteName ?? "Xisoa"
+const baseName = snap?.title || ""
+const detailTitle = baseName || SITE_NAME
+const detailDesc = locale === "cn"
+ ? `${detailTitle} —— 来自全球街头的时尚抓拍,收录于 ${SITE_NAME} 时尚档案。`
+ : `${detailTitle} — street style snaps from cities around the world, in the ${SITE_NAME} fashion archive.`
+const detailKeywords = `${snap?.title || ""},street style,street snaps,fashion,${SITE_NAME}`.replace(/^,/, "")
+
+const saveLabel = t("save")
+const savedLabel = t("saved")
+---
+
+
+
+
+
+
+
+{snap && (
+
+)}
+
+
diff --git a/src/lib/detailFav.ts b/src/lib/detailFav.ts
new file mode 100644
index 0000000..67e8c1c
--- /dev/null
+++ b/src/lib/detailFav.ts
@@ -0,0 +1,59 @@
+import { getUser } from "@/lib/auth"
+import { isFavorited, toggleFavorite, hydrateFavorited } from "@/lib/favorites"
+import { recordHistory } from "@/lib/history"
+
+/**
+ * 详情页「收藏 / 历史」按钮初始化。
+ * 按钮本体(#fav-fab)由详情视图渲染,元信息经 data-* 透传(不依赖 define:vars,避免打包期 import 进 IIFE 内报错)。
+ * runway / street 两类详情共用本逻辑,区别仅在于透传的 data-type 值。
+ * 注意:本函数只在详情页(含 #fav-fab 的页面)调用一次。
+ */
+export function initDetailFav() {
+ const btn = document.getElementById("fav-fab")
+ const label = document.getElementById("fav-label")
+ if (!btn) return
+
+ const meta = {
+ id: btn.dataset.id || "",
+ type: btn.dataset.type || "",
+ title: btn.dataset.title || "",
+ cover: btn.dataset.cover || "",
+ brand: btn.dataset.brand || "",
+ }
+ const saveLabel = btn.dataset.saveLabel || "save"
+ const savedLabel = btn.dataset.savedLabel || "saved"
+
+ if (!meta.id) return
+
+ // 浏览历史:点开文章详情即记一条(登录态;列表页不再记)。同一篇反复打开,后端 upsert 刷新时间。
+ if (getUser()) recordHistory(meta.id)
+
+ const sync = () => {
+ const fav = isFavorited(meta.id)
+ if (label) label.textContent = fav ? savedLabel : saveLabel
+ btn.classList.toggle("is-active", fav)
+ }
+ sync()
+
+ // 登录态收藏校正已合并进详情接口:articleView / streetSnapView 取到服务端收藏态后,
+ // 通过 fav:set 事件广播当前图集是否已收藏;此处监听并写回本地(换设备后与服务端一致)。
+ window.addEventListener("fav:set", (e: Event) => {
+ const detail = (e as CustomEvent<{ id: string; fav: boolean }>).detail
+ if (!detail || detail.id !== meta.id) return
+ if (detail.fav) {
+ hydrateFavorited(meta.id, meta.type as "runway" | "street", meta.title, meta.cover, meta.brand)
+ }
+ sync()
+ })
+
+ btn.addEventListener("click", () => {
+ toggleFavorite({
+ id: meta.id,
+ type: meta.type,
+ title: meta.title,
+ cover: meta.cover,
+ brand: meta.brand,
+ } as any)
+ sync()
+ })
+}