Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
Polaris 首页实现规格
日期:2026-08-26
状态:用户已批准,可进入实施
实现分支:
feat/polaris-homepage
1. 背景与目标
Polaris 是一个轻量、安静、高效的个人浏览器起始页,不是后台管理系统或信息密集型 Dashboard。
本次工作只建立推荐的项目结构并实现完整可交互首页。用户验收首页视觉和交互后,才会明确决定是否继续实现分类页及第一阶段其余目标。
2. 范围
2.1 本次实现
- 初始化 Next.js App Router 项目;
- 建立 Feature-first 混合目录;
- 整理现有设计文档和资产位置;
- 实现低饱和青蓝 CSS 渐变背景与浅色玻璃视觉;
- 实现可折叠且持久化的桌面侧栏;
- 实现实时时钟、日期和分时问候语;
- 实现 Google、Bing、GitHub、Local 四种严格分离的搜索模式;
- 实现 Local 搜索结果、键盘导航和空状态;
- 实现 8 个常用链接;
- 实现统一图标 fallback;
- 建立本地 JSON、service、类型与数据校验边界;
- 完成单元测试、组件交互测试、静态构建和真实页面视觉检查。
2.2 本次不实现
- 正式分类页;
- Go API 与 PostgreSQL;
- 管理页或隐藏管理入口;
- 深色模式;
- 移动端与平板布局;
- 自定义背景上传;
- 自定义搜索引擎、URL 直达、AI 搜索;
- 加载骨架和后端错误重试。
3. 已确认决策
| 决策项 | 结论 |
|---|---|
| 首页范围 | 完整可交互首页 |
| 目录方案 | Feature-first + 共享基础层 |
| 分支粒度 | 项目骨架与首页位于同一个 feat/polaris-homepage 分支 |
| 文档整理 | 三份文档迁入 docs/design/,设计图迁入 docs/assets/ |
| Logo | 现有占位 Logo 迁入 public/brand/,不追加品牌设计 |
| 运行时背景 | CSS 低饱和青蓝渐变;设计图只作构图参考 |
| 外部搜索 | Google、Bing、GitHub 在当前标签页跳转 |
| 本地链接 | Local 结果与常用链接在新标签页打开 |
| 排序模型 | sort 管理分类内顺序;可选 pinnedSort 独立管理首页常用链接顺序 |
| 分类导航 | 首页验收阶段展示但禁用,避免进入未实现路由;分类页完成后启用 |
| 注释 | 关键规则、非显然决策、边界条件和阶段性限制使用简洁中文注释 |
| 提交时机 | 分支全部目标完成并验证后再提交 |
4. 技术基线
- Next.js App Router;
- React;
- TypeScript strict;
- Tailwind CSS v4,使用 CSS-first 配置;
- shadcn/ui,只引入实际使用的基础原语;
- lucide-react;
- simple-icons;
- pnpm;
- Vitest;
- Testing Library;
- ESLint;
- Prettier。
next.config.ts 使用 output: "export",构建产物必须能作为纯静态文件部署。
不引入 Redux、Zustand、React Query、Zod、Storybook、Repository interface 或依赖注入容器。当前规模下这些抽象没有必要。
5. 推荐目录
polaris/
├─ docs/
│ ├─ design/
│ │ ├─ Polaris 项目说明文档.md
│ │ ├─ Polaris 首页设计文档.md
│ │ ├─ Polaris 分类页设计文档.md
│ │ └─ 2026-08-26-Polaris-首页实现规格.md
│ └─ assets/
│ └─ Polaris页面设计图.png
├─ public/
│ ├─ brand/
│ │ └─ logo.svg
│ └─ icons/
├─ src/
│ ├─ app/
│ │ ├─ globals.css
│ │ ├─ layout.tsx
│ │ └─ page.tsx
│ ├─ components/
│ │ ├─ ui/
│ │ └─ shared/
│ ├─ features/
│ │ ├─ home/
│ │ │ ├─ components/
│ │ │ ├─ hooks/
│ │ │ └─ lib/
│ │ └─ navigation/
│ │ └─ components/
│ ├─ data/
│ │ ├─ categories.json
│ │ ├─ links.json
│ │ └─ settings.json
│ ├─ services/
│ │ ├─ categories.ts
│ │ ├─ links.ts
│ │ └─ settings.ts
│ ├─ types/
│ │ ├─ navigation.ts
│ │ ├─ search.ts
│ │ └─ settings.ts
│ ├─ lib/
│ │ └─ utils.ts
│ └─ test/
│ └─ setup.ts
├─ components.json
├─ eslint.config.mjs
├─ next.config.ts
├─ package.json
├─ pnpm-lock.yaml
├─ postcss.config.mjs
├─ prettier.config.mjs
├─ tsconfig.json
└─ vitest.config.ts
5.1 目录职责
app/:路由、metadata、根布局、全局样式和页面组合;features/:按功能聚合组件、hooks 和专属纯函数;components/ui/:shadcn/ui 基础原语;components/shared/:跨功能复用组件,例如GlassPanel、SiteIcon;data/:第一版本地 JSON;services/:页面与数据源之间的唯一访问边界;types/:跨功能共享的数据契约;lib/:真正通用且不依赖业务功能的工具;test/:全局测试环境与共享测试辅助代码。
测试文件与被测试实现就近放置,src/test/ 不作为所有测试的集中目录。
6. 渲染与组件边界
src/app/page.tsx 保持为 Server Component,在构建时调用 service 层读取数据,再将可序列化数据传给页面组件。
app/page.tsx
└─ AppShell
├─ Sidebar
│ ├─ SidebarHeader
│ └─ SidebarNav
└─ HomeContent
├─ ClockGreeting
├─ SearchBox
│ ├─ SearchProviderMenu
│ └─ LocalSearchResults
└─ QuickLinks
└─ QuickLinkCard
└─ SiteIcon
Client Component 边界仅用于浏览器状态:
AppShell:侧栏折叠与持久化;ClockGreeting:当前时间;SearchBox:Provider、query、结果选中项和快捷键;SiteIcon:图标加载失败后的 fallback。
把 Server Component 作为 AppShell 的 children 传入,不因为外层存在 Client Component 就将全部展示代码改成客户端代码。
不为每个小标签或图标创建组件。只有存在独立状态、复用价值或清晰职责时才拆分。
7. 数据模型与数据流
7.1 数据规模
- 7 个分类;
- 开发工具 15 条;
- ChatGPT、Gmail、Notion、YouTube、Bilibili 5 条跨分类链接;
- 共 20 条链接;
- 8 条
pinned常用链接; sort只表示分类内顺序,可选pinnedSort只表示首页常用链接顺序。
首页常用链接固定按以下 pinnedSort 顺序展示:GitHub、ChatGPT、Gmail、Notion、Figma、YouTube、Vercel、Bilibili。该字段解决开发工具分类中 Vercel 位于 Figma 之前、但首页中 Figma 位于 Vercel 之前的双重排序需求。
设计资源、学习资料、生活服务保持无链接;AI 工具、效率工具和娱乐媒体因常用链接而有少量数据。
7.2 访问路径
page.tsx调用getCategories()、getLinks()和getSettings();- service 层复制、排序和筛选数据;
- 页面不直接 import JSON;
- 首页只把所需的分类、全部搜索候选、8 条常用链接和设置传给客户端;
- 未来接 Go API 时保持 service 的对外职责不变,只替换其内部数据来源。
7.3 数据校验
Vitest 必须校验:
- 必填字段类型;
- 分类和链接 ID 唯一;
- URL 合法;
categoryId必须指向现有分类;sort与可选pinnedSort合法;- Provider 配置有效;
pinned数量为 8,且每条 pinned 链接都有唯一pinnedSort。
8. 视觉系统
8.1 背景
使用多层 CSS 渐变和柔和光斑模拟晨雾、天空和水面氛围,不把 Polaris页面设计图.png 作为运行时背景。
增加浅色遮罩和适度模糊,保证未来替换背景时仍具有可读性。
8.2 布局
- 侧栏外边距:
20px; - 展开宽度:
240px; - 折叠宽度:
72px; - 主内容视觉重心位于中上方;
- 使用
minmax()与clamp()适配 1024px 以上桌面窗口; - 搜索框最大宽度约
760px、高度约68px; - 常用链接最大四列、共两行,缩窄窗口时允许减少列数;
- 第一版不实现移动端抽屉侧栏。
8.3 Design tokens
在 globals.css 中定义 Polaris 语义变量,并通过 Tailwind v4 @theme inline 映射:
- 主文本、次级文本、弱文本;
- 主色、hover 色、柔和主色;
- 侧栏、搜索框、卡片表面;
- 玻璃边框;
- 软阴影、中等阴影;
- 圆角与模糊强度。
第一版只实现浅色值,但变量命名允许以后通过主题选择器覆盖。
8.4 动效与无障碍
- hover、focus 和折叠过渡约为 180ms;
- 不使用夸张缩放或弹跳;
- 尊重
prefers-reduced-motion; - 所有图标按钮有可访问名称;
- 键盘焦点清晰;
- 搜索输入与结果使用合理的 combobox/listbox 语义;
- 禁用分类项使用
aria-disabled并提供“分类页将在下一阶段提供”的轻量提示。
9. 首页交互
9.1 时钟与问候
问候规则:
05:00 - 11:59 早上好
12:00 - 13:59 中午好
14:00 - 17:59 下午好
18:00 - 23:59 晚上好
00:00 - 04:59 夜深了
客户端按分钟更新;跨午夜后日期、星期和问候语同步变化。
9.2 Provider 状态
- 初始值来自
settings.search.defaultProvider; - 用户切换后保存到
localStorage; - 无效值或存储不可用时回退到设置默认值;
- 外部 Provider 与 Local 模式严格分离。
9.3 外部搜索
Google、Bing、GitHub:
- 输入先
trim(); - 空内容不提交;
- 使用
URLSearchParams编码; - 在当前标签页跳转;
- 不显示 Local 结果下拉。
9.4 Local 搜索
匹配字段:
- 链接标题;
- 关键词;
- 描述;
- 分类名称;
- 分类 ID。
优先级:
标题完全匹配
标题前缀或包含
关键词匹配
描述匹配
分类匹配
同分时按链接 sort 和标题稳定排序,最多返回 8 条。
交互:
- 默认选中第一项;
↑/↓移动;Enter打开选中项;- 鼠标点击直接打开;
- 结果在新标签页打开;
Esc清空输入并关闭面板;- 无结果显示“没有匹配的网站”。
9.5 快捷键
- 首页加载后自动聚焦搜索框;
- Windows/Linux:
Ctrl+K; - macOS:
⌘K; - 快捷键阻止浏览器默认行为后聚焦;
- 监听器只存在于首页生命周期内。
9.6 侧栏
- 折叠状态保存到
localStorage; - 存储不可用或值损坏时回退到设置默认值;
- hydration 后同步持久化值;
- 同步完成前禁用布局过渡,减少闪动;
- 首页项为当前选中态;
- 分类项本次显示但禁用,不进入 404。
9.7 常用链接
- 对
pinned链接按pinnedSort排序取前 8 条; pinned: true时必须提供唯一的pinnedSort;- 使用真实
<a>; target="_blank";rel="noopener noreferrer";- 卡片仅显示图标与名称。
10. 图标策略
运行时优先级:
自定义本地图标
明确映射的 Simple Icons
favicon
标题首字符文字占位
当前 20 条数据尽量命中显式 Simple Icons 映射,避免正常页面依赖远程 favicon。远程图标失败时不得显示破图。
侧栏分类图标使用 lucide-react,不参与站点图标 fallback。
11. 状态与故障处理
- 静态 JSON 无 Loading UI;
- 数据结构错误由测试和构建阶段阻止;
localStorage读写使用容错封装;- favicon 失败显示文字占位;
- 空搜索不跳转;
- Local 无结果显示轻量空状态;
- 非法 Provider 回退到配置默认值;
- Server 初始值与 Client 首次渲染保持一致,避免 hydration error。
12. 注释规范
必须为以下非显然逻辑添加简洁中文注释,解释“为什么”:
- Local 搜索权重及稳定排序;
- Server/Client 边界选择;
- 图标 fallback 顺序;
- hydration 后同步
localStorage; - 分类导航临时禁用;
- 数据校验中的跨分类引用规则。
普通 JSX、显然的变量赋值和框架惯用代码不添加复述式注释。
13. 测试设计
13.1 纯函数
- 问候语全部时间边界;
- 日期格式;
- 搜索 URL 与特殊字符编码;
- Local 搜索字段、权重、排序和数量上限;
- 图标引用解析;
- 数据结构及交叉引用。
13.2 组件交互
- 自动聚焦;
- Provider 严格分离;
- Provider 持久化与非法值回退;
- Local 键盘导航、Enter、Esc、点击和无结果;
Ctrl+K/⌘K;- 侧栏折叠、恢复和异常存储回退;
- 新标签页链接安全属性;
- 分类项禁用状态。
14. 验证与视觉验收
完成后必须运行:
pnpm lint
pnpm typecheck
pnpm test
pnpm build
随后实际启动页面并检查:
1440 × 900;1920 × 1080;1024 × 768。
检查项:
- 无横向溢出;
- 背景渐变层次和文本对比度;
- 玻璃透明度、边框与阴影一致;
- 侧栏展开/折叠比例;
- 搜索聚焦与 Provider 交互;
- Local 结果定位和键盘交互;
- 卡片排列、hover 和 focus;
prefers-reduced-motion;- 控制台无 hydration error 或运行时错误。
设计图用于构图和氛围参照,不要求像素级复制图片背景。
15. 完成定义
只有以下条件全部满足,feat/polaris-homepage 才完成:
- 推荐目录和工程配置建立;
- 文档及资产迁移完成,内部链接有效;
- 完整可交互首页实现;
- 自动化检查全部通过;
- 真实页面完成三个尺寸的视觉检查;
- 实际结果、视觉观察和已知偏差如实报告;
- 未提前实现正式分类页;
- 完成后再执行提交操作。