配 PRD v0.9。零 schema、不碰认证逻辑、
/login行为不变。目标:把根路径首屏从「裸登录框」换成「公开介绍页」,既给项目对外门面,又消除 Chrome「Deceptive pages」误判的触发特征。
- 选定:改既有
common.HomeController(它本就@GetMapping("/"),v0.7 FR-133 做已登录的 dashboard/onboarding 分流),在最前面加匿名分支:me == null(匿名)→return "landing"(公开落地页);me != null→ 沿用原逻辑:有周期+账户 →redirect:/dashboard,否则 →onboarding/index。
- ⚠ 教训(2026-06-25 真踩):我起初新建了
web/HomeController,与既有common.HomeController同简单类名 → 同默认 bean 名homeController→ConflictingBeanDefinitionException,Spring 上下文启动失败、beta 崩溃重启循环。根因是我探落点时只grep web/、漏了common/里早有的/映射(正是「没核对既有逻辑就新增」)。改为在既有控制器上加分支,既不冲突、又自然保住 onboarding 行为。 - 备选(否) SecurityConfig
defaultSuccessUrl/ forward:分流逻辑塞进安全配置隐晦难测。 - 备选(否) 模板内
th:if切登录态:已登录用户没必要加载落地页 DOM,redirect更干净。
- 在
SecurityConfig.authorizeHttpRequests的requestMatchers(...permitAll)清单里加 精确根"/"(不是/**)。匿名GET /才能命中 HomeController 返回 landing,而非被anyRequest().authenticated()重定向到/login。 - 安全自检:只加
"/"一条;anyRequest().authenticated()不动;/dashboard、/reports、/accounts等仍需登录(QA v09-LAND-4 回归守护)。
templates/landing.html:<head th:replace="~{fragments/layout :: head('家庭账房 · Family Ledger')}">—— 复用既有骨架(自托管/vendor/tailwind.js+ 三套字体 +style.css,无任何外部 CDN,正好契合自托管/隐私定位)。body 移植 preview 六块,用既有.eyebrow+ Tailwind 工具类(bg-card/border-rule/text-brass-deep/text-forest/text-rust,均映射到style.css的 CSS 变量),全 inline SVG、无 emoji(承 [[feedback_no_emoji]]),文案非技术家人可懂(承 [[feedback_user_friendly_naming]])。- 匿名访问不传
nav(无家庭)→ head 的 favicon 命中nav != null守卫、回落预设icon2;buildVersion由GlobalModelAdvice全局注入,匿名页同样有。
- 把
docs/screenshots/feature_summary_total.jpg复制到src/main/resources/static/img/feature_summary_total.jpg,模板用@{/img/feature_summary_total.jpg(v=${buildVersion})}。 - 备选(否) 沿用 preview 的 jsDelivr 外链:自托管产品的对外首页依赖外部 CDN 不合适(离线可用性 / 隐私 / 墙),且
/img/**已在 permitAll。代价:jar +~0.47MB,可接受。
- 零 schema;不改认证、会话、
/login;已登录访问/仍直达 dashboard。 - QA(
qa-runv09-LAND-*):① 匿名GET /=200 且含定位文案 + GitHub 全 URL + 截图引用;② 匿名/不再 302/login;③ 已登录/→302/dashboard;④ 回归:匿名/dashboard仍要求登录(permitAll 没放过头)。 - 降钓鱼信号(FR-162):首屏 HTML 即品牌 + 说明 + 开源链接的真实页。
- 文档同步:prd/v0.9 + 本文件 + docs/qa-cases + CHANGELOG + README(承 [[feedback_doc_sync]])。
- 布局:参考 brew.sh / ohmyzsh.sh 改居中单列;
landing.html复用 layout head(自托管 tailwind/字体/css),v0.9.1 专属 CSS 内联在模板<style>(grain / github-corner / reveal / cmd-block / ghstar / q-item / pillar-hover),全 inline SVG、无 emoji、prefers-reduced-motion静默。 - 快速开始改真实 4 步:
git clone → cd → bash deploy/docker-init.sh → docker compose up -d(取自 README,不假装一键)。 - GitHub-Star / star 数:客户端
api.github.com拉stargazers_count,0/失败 → 数字段:empty隐藏(不露怯)。
- 问题:landing 的 4 个数字(版本/单测/迁移/黑盒)若写死,会随迭代过时(用户选 B:保留精确数字)。
- 选定:不在运行时算(应用不知道自己的测试数),而在发版预检钉一道硬门 ——
release-prodskillpreflight计算 版本=prd/v0.*.md个数、迁移=db/migration/V*.sql个数(确定可算),单测/黑盒 取 README 的「N 单元 / N 黑盒回归」(发版本就要更 README,landing 跟它走);校验landing.html的data-stat(version/tests/migrations/blackbox)与之一致,任一过时 →die给出应改值。qa-run v09-LAND-6同口径日常守护。 - 备选(否) 构建期注入 / 运行时计算:加构建复杂度,且"测试数/黑盒数"运行时不可得;发版门 + README 单一事实源最省心可靠。
- 备选(否) 数字 round 成约数(A 方案):用户要精确,改走联动。
- landing 用
data-stat="version|tests|migrations|blackbox"锚点,校验脚本据此精确取值(非顺序依赖)。
- 背景:全站写表单审计后,多数必填项直接挂原生
required即可;但有几处是条件必填——应急金fixedBaseline仅当autoBaseline=false时必填、自选股costBasis仅当deductCash勾选时必填。HTML 的required是静态的,无法表达「某控件命中才必填」。 - 备选 A(否)· 每个条件字段写一段内联 JS:监听控制控件、toggle 目标字段
required。直观但散落、重复、易漏绑;新增一处就复制一段。 - 备选 B(否)· 后端校验为主:提交后服务端报错回 toast。但本次目标就是「别发请求」——后端校验是兜底不是前置,且体验差(往返一次才知道)。
- 选定 C · 声明式
data-require-when="控件名=值"+ layout footer 全站一处助手:字段上声明依赖,助手统一扫描并绑定。- 取值规则:同表单内名为「控件名」的控件——radio 取
:checked的 value、checkbox 勾选→其 value(默认'true')未勾→'false'、其它取.value;== 指定值则required,否则摘除。 change实时同步;htmx:load对换入片段再绑(本站条件字段都是整页表单,这是冗余保险)。- 为何更好:声明式、零重复、未来任意表单加一个属性即生效;摘除逻辑天然避免「对隐藏/不适用字段误挂 required → 提交被卡死且无可见报错」这个 HTML5 校验经典坑(承
data-searchable下拉因display:none不可挂 required 的既有教训)。
- 取值规则:同表单内名为「控件名」的控件——radio 取
- 原生
required仍是主力:HTMX 尊重 HTML5 校验,提交前即拦,无需额外代码;data-require-when只补「条件」这一维。 - 防回归:
qa-run v09-FORM-1~6静态扫模板断言各字段挂载到位 + 助手就位;区分「故意可选」与「漏挂必填」写进 qa-cases 注释,防后人误删或误加。 - 纯模板 + 1 处全站脚本,零 schema、向后兼容(承 [[feedback_prod_backward_compat]] [[feedback_doc_sync]])。