- advantages section gains section--soft (white cards were invisible on white) - .pro-art placeholder redesigned: brand gradient + initials + small kind label, ring ornament hidden (was heavy gray block) - plan: new Task 10j (port cms header/footer chrome visuals) per user feedback that overall look should match cms - verified live: mirror build 7f1e8c47f0c4 (374/384 real images pulled, 203 pages, 0 errors) Co-Authored-By: Claude Code <noreply@anthropic.com>
23 KiB
trade-pro 第 5 主题移植实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 把 cms Astro 主题移植为 SiteForge V8_10 第 5 套内置主题 trade-pro(英文外贸 B2B),平台适配 + 骨架跑通 + 完整模板 + 验收。
Architecture: 三步走——先做平台适配(主题发现单一源、宏覆盖机制、主题资产优先、6 处前端入口),再以骨架主题包在真实 hesupipe 站点(483295c29ea0,193 条内容)跑通构建,最后按 cms 设计源(D:\project\cms\runtimes\astro-site-runtime)逐模板移植视觉。设计依据:spec R3。
Tech Stack: Python 3.12 / FastAPI / Jinja2(FileSystemLoader 多目录)/ pytest / 原生 JS。
环境事实(执行者必读):
- 工作目录:
C:\Users\30997\Downloads\SiteForge_Factory_V5_Web_Docker\SiteForge_Enterprise_V8_10(下称$ROOT,bash 里用正斜杠) - Docker 实例已运行:
http://127.0.0.1:8787(容器 siteforge-v8-10-siteforge-1/-worker-1),admin 密码在.env - 测试命令:
python -m pytest -q(当前 210 项全绿,任何改动后必须保持);JS 检查node --check app/static/<file>.js - 宿主机快速循环(V8_12 起):
PYTHONUTF8=1 .venv/Scripts/python -m pytest -q,基线 227 passed + 2 known-fail(test_p0_backup.py::test_sqlite_backup_and_restore_with_media、test_p0_p4_completion_v89.py::test_restore_replaces_directory_atomically_and_preserves_previous——均为 Windows sqlite 文件锁环境问题,改动前即失败);任何新失败都是回归。权威全量验证用容器:MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd -W):/project" -w /project python:3.12-slim sh -c "python -m pip install -q -r requirements-dev.lock && python -m pytest -q"(容器基线 226 passed + 1 node 环境失败) - cms 设计源:
D:/project/cms/runtimes/astro-site-runtime/src(tokens.css 250 行、base.css 445 行、components/.astro、pages/[locale]/.astro);像素基准dist/hesupipe/en/ - 改动 Python 后需
docker compose up -d --build重建容器再验证 HTTP 层(构建约 1 分钟,有缓存) - 契约清单已固化于 spec §2(F1-F13),本计划直接引用
Phase 1+2:平台适配与骨架跑通(Task 1-8)
Task 1: Git 基线 —— ✅ 已完成(2026-10-22,幂等跳过)
已执行并有扩展:仓库初始化、基线提交、远程 http://219.157.255.213:25311/xinyouhe/cms-SiteForge.git 配置并推送(main + feat/trade-pro-theme),远程原有 V5 基线历史经 -s ours 无损合并,docs/COMPLETION_PLAN.md 带入当前树;.env/密码确认未入库。随后追加 V8_12 原地升级(26 文件覆盖 + bug 修复重放 + compose 项目名钉回 siteforge-v8-10 保护数据卷)。执行者无需重跑本任务。
Task 2: 主题发现单一源(F1)
Files:
-
Modify:
app/core.py:12 -
Modify:
app/theme_pro.py:16-18 -
Modify:
app/theme_store.py:14 -
Test:
tests/test_theme_registry.py(新建) -
Step 1: 写失败测试
"""Theme discovery must be a single source of truth."""
import json
from pathlib import Path
from app import core, theme_pro, theme_store
ROOT = Path(core.__file__).resolve().parent.parent
def test_discover_returns_builtin_themes_with_manifests():
ids = core.discover_themes()
for tid in ('automotive', 'industrial', 'corporate', 'minimal'):
assert tid in ids
for tid in ids:
manifest = json.loads((ROOT / 'themes' / tid / 'manifest.json').read_text(encoding='utf-8'))
assert manifest.get('id') == tid
def test_three_whitelists_agree():
assert set(core.THEMES) == set(theme_pro.THEMES) == set(theme_store.BUILTIN)
assert isinstance(core.THEMES, tuple)
- Step 2:
python -m pytest tests/test_theme_registry.py -q→ 预期 FAIL(discover_themes不存在) - Step 3: 实现——
app/core.py:12替换为:
ROOT_PATH = __import__('pathlib').Path(__file__).resolve().parent.parent
def discover_themes():
"""Single source of truth: scan themes/*/manifest.json (id must match folder)."""
import json
themes_dir = ROOT_PATH / 'themes'
found = []
for folder in sorted(themes_dir.iterdir()):
if not folder.is_dir() or folder.name.startswith('_'):
continue
manifest = folder / 'manifest.json'
if not manifest.is_file():
continue
try:
if json.loads(manifest.read_text(encoding='utf-8')).get('id') == folder.name:
found.append(folder.name)
except (ValueError, UnicodeError):
continue
return tuple(found)
THEMES = discover_themes()
(core.py 顶部已有 import json,可复用;实现时去掉重复 import,风格与文件一致。)
app/theme_pro.py:16-18 改为:
from .core import sanitize, route_path, THEMES
(删除原第 18 行的元组定义。)
app/theme_store.py:14 改为:
from .core import THEMES as BUILTIN
- Step 4:
python -m pytest tests/test_theme_registry.py -q→ PASS;python -m pytest -q全绿 - Step 5:
git add -A && git commit -m "feat: single-source theme discovery replaces three hardcoded whitelists"
Task 3: theme_pro 每主题环境(宏覆盖机制,F4)
Files:
-
Modify:
app/theme_pro.py:18-20(LOADER 旁新增 env_for) -
Modify:
app/theme_pro.py:190(render 用 env_for) -
Test:
tests/test_theme_registry.py(追加) -
Step 1: 追加失败测试
def test_env_for_theme_prefers_theme_dir_then_shared_root():
from app import theme_pro
env = theme_pro.env_for('industrial')
paths = [str(p) for p in env.loader.searchpaths]
assert paths[0].replace('\\', '/').endswith('themes/industrial')
assert paths[1].replace('\\', '/').endswith('themes')
# 共享宏库必须仍可解析(回落路径)
assert env.get_template('_professional_components.html') is not None
def test_env_for_is_cached_per_theme():
from app import theme_pro
assert theme_pro.env_for('industrial') is theme_pro.env_for('industrial')
- Step 2: 跑测试 → FAIL(env_for 不存在)
- Step 3: 实现——
app/theme_pro.py在 LOADER 定义后追加:
_ENVS = {}
def env_for(theme):
"""Per-theme Jinja env: theme dir first, shared themes/ root as fallback.
Lets a theme override shared macros (_professional_components.html) while
inheriting everything else. Cached: one env per theme across 5000-page builds.
"""
env = _ENVS.get(theme)
if env is None:
theme_dir = ROOT / 'themes' / theme
search = [str(theme_dir), str(ROOT / 'themes')] if theme_dir.is_dir() else [str(ROOT / 'themes')]
env = Environment(loader=FileSystemLoader(search), autoescape=select_autoescape(['html', 'xml']))
_ENVS[theme] = env
return env
render() 末行(:190)改为:
return env_for(theme).get_template('pro.html').render(**ctx)
- Step 4:
python -m pytest -q全绿(现有 test_theme_pro_v3.py 即回归) - Step 5: commit
feat: per-theme Jinja env enables theme-level macro override
Task 4: builder 主题资产优先(F5)
Files:
-
Modify:
app/builder.py:191 -
Test:
tests/test_theme_registry.py(追加) -
Step 1: 追加失败测试
def test_theme_blocks_css_overrides_platform(tmp_path):
from app import builder
from pathlib import Path
dist = tmp_path / 'assets'; dist.mkdir()
theme_dir = tmp_path / 'theme'; theme_dir.mkdir()
(theme_dir / 'blocks.css').write_text('/* theme version */', encoding='utf-8')
builder.copy_blocks_css(theme_dir, dist)
assert (dist / 'blocks.css').read_text(encoding='utf-8') == '/* theme version */'
# 无主题版时回落平台版
dist2 = tmp_path / 'assets2'; dist2.mkdir()
builder.copy_blocks_css(tmp_path / 'nonexistent', dist2)
assert (dist2 / 'blocks.css').is_file()
- Step 2: 跑 → FAIL
- Step 3: 实现——
app/builder.py:191的shutil.copy2(ROOT/'app'/'static'/'blocks.css',dist/'assets'/'blocks.css')替换为调用新函数,并在文件合适位置(output_path 附近)定义:
def copy_blocks_css(theme_dir, dist_assets):
"""Theme-provided blocks.css wins; platform default is the fallback."""
src = theme_dir / 'blocks.css' if (theme_dir / 'blocks.css').is_file() else ROOT / 'app' / 'static' / 'blocks.css'
shutil.copy2(src, dist_assets / 'blocks.css')
- Step 4:
python -m pytest -q全绿;commitfeat: theme-level blocks.css takes priority over platform asset
Task 5: trade-pro 骨架主题包(Phase 2 核心)
Files:
-
Create:
themes/trade-pro/manifest.json、site.html、pro.html、_professional_components.html、theme.css、blocks.css、pro.css、page-presets.json、pages/(14 个 html)、visual/.gitkeep、versions/.gitkeep -
Test:
tests/test_theme_registry.py(追加) -
Step 1: 追加失败测试
def test_trade_pro_registered_and_compatible():
assert 'trade-pro' in core.THEMES
result = theme_store.compatibility('trade-pro')
assert result['valid'], result['errors']
assert not result['errors']
def test_trade_pro_builds_real_site(tmp_path):
# 复用 tests/test_theme_pro_v3.py 中"建站→切主题→构建→断言预览页"的既有写法
# (夹具工厂在 tests/test_factory_v2.py:15-30),theme 用 'trade-pro'。
# 产物读取走夹具的 HTTP 形式(与 v3 一致):
html = factory.get(f'/preview/{sid}/{bid}/').text
assert 'lang="en"' in html
assert '>首页<' not in html
(执行时先读 test_theme_pro_v3.py 的相邻测试,逐行复制其夹具调用方式,仅替换 theme 参数与断言;禁止保留 ... 空壳。)
- Step 2: 跑 → FAIL
- Step 3: 创建文件
manifest.json:
{
"id": "trade-pro",
"name": "Trade Pro — International B2B",
"description": "English-first international B2B theme ported from the hesupipe Astro design: SVG icon set, responsive trade catalogue, product specs and inquiry CTA.",
"version": "1.0.0",
"style": "international-b2b",
"entry": "pro.html",
"styles": ["theme.css", "blocks.css"],
"page_templates": ["home","about","contact","listing","category","product","article","brand","case","service","faq","standard","notfound"],
"blocks": ["hero","richtext","features","stats","collection","image_text","gallery","faq","cta","attributes","spacer","layout","carousel","video","tabs","pricing","map","form_link"],
"responsive": true,
"auto_binding": true,
"professional_layout": true,
"commercial_ui": true,
"accent": "#0b3d91"
}
site.html:以 themes/industrial/site.html 为底(它是 4.4KB 的完整独立模板,自带 header/footer 结构,不引用共享宏库),改 lang="zh-CN" → lang="en"。注意:pro 主题的正式渲染走 pro.html,site.html 主要服务非 pro 路径/兜底。
已知新行为(评审发现,记录不阻断):Task 2 后 trade-pro 进入 BUILTIN,上传主题 ZIP 可声明 layout_family:'trade-pro'(theme_store.install 会拷贝 trade-pro 的 site/pro.html),而 ZIP 包渲染走共享宏回落——与依赖自有宏库的 trade-pro 存在潜在错配。本轮接受(上传包本就以声明式为主),后续如出问题在 install() 校验 layout_family 白名单。
_professional_components.html:先做透传骨架(从共享库复制 card/cover 等宏,Task 10 起逐个换成 cms 设计 + SVG 图标)。
pro.html:以 industrial 的 pro.html 为底最小化:lang="en";页眉/页脚消费 site_design chrome 但按 spec 4.2.5 值比对输出英文兜底(中文默认值视为未配置);正文区渲染 page content 与 custom_blocks;按钮类名一律 sf-tp-*。
theme.css:先放 tokens 骨架(Task 9 全量移植):
:root{--sf-tp-primary:#0b3d91;--sf-tp-accent:#f5a623;--sf-tp-bg:#ffffff;--sf-tp-text:#1a2332;--sf-tp-muted:#5a6a7e;--sf-tp-radius:8px;--sf-tp-font:'Inter',system-ui,-apple-system,sans-serif}
blocks.css:空文件 + 注释 /* trade-pro component styles: filled in Phase 3 */。
pro.css:空文件(兼容性硬性要求,spec F8)。
page-presets.json:复制 industrial 的同名文件原样(Phase 4 精修;13 种 key 齐全即可)。
pages/:home.html 写最小可用骨架(hero + showcase 列表 + cta 引用共享宏);其余 13 个(about/contact/listing/product/brand/article/case/service/faq/category/search/standard/notfound)先各写薄变体(复用 home 的结构、引用同样宏),文件必须存在(spec F7 硬性)。
- Step 4:
python -m pytest -q全绿(含新测试) - Step 5:
docker compose up -d --build后:curl -s http://127.0.0.1:8787/api/health版本不变、curl -s "http://127.0.0.1:8787/api/factory/themes/trade-pro/compatibility"(带登录 cookie)返回 valid=true - Step 6: commit
feat: trade-pro skeleton theme package (Phase 2)
Task 6: theme_color 映射 + 6 处前端入口(F3/F11)
Files:
-
Modify:
app/theme_pro.py(theme_color 字典,make_context 内,约 :171) -
Modify:
app/templates/admin.html:13(siteTheme select) -
Modify:
app/templates/workspace.html:41(建站模态框 theme select) -
Modify:
app/templates/theme-gallery.html:4(theme select) -
Modify:
app/static/workspace.js:5(themeName) -
Modify:
app/static/admin.js:5(cfg) -
Modify:
app/static/factory-v2.js:30(schemes) -
Test:
tests/test_theme_registry.py(追加) -
Step 1: 追加失败测试
def test_frontend_entries_include_trade_pro():
import pathlib
base = pathlib.Path(__file__).resolve().parent.parent
for rel, needle in [
('app/templates/admin.html', 'value="trade-pro"'),
('app/templates/workspace.html', 'value="trade-pro"'),
('app/templates/theme-gallery.html', 'value="trade-pro"'),
('app/static/workspace.js', "'trade-pro'"),
('app/static/admin.js', "'trade-pro'"),
('app/static/factory-v2.js', "'trade-pro'"),
]:
assert needle in (base / rel).read_text(encoding='utf-8'), rel
def test_theme_color_map_covers_trade_pro():
# 真实断言:trade-pro 构建出的首页含 meta theme-color #0b3d91
# (复用 Task 5 的构建夹具与 /preview/ HTTP 读取写法)
html = factory.get(f'/preview/{sid}/{bid}/').text
assert '<meta name="theme-color" content="#0b3d91"' in html
- Step 2: 跑 → FAIL
- Step 3: 实现——逐处追加(保持原文风格):
- theme_color 字典追加
'trade-pro':'#0b3d91' - 三个 HTML select 各追加:
<option value="trade-pro">Trade Pro · 外贸 B2B(英文)</option>(theme-gallery 用Trade Pro · International B2B) workspace.jsthemeName 追加'trade-pro':'Trade Pro'admin.jscfg 追加trade-pro:{label:'Trade Pro',desc:'英文外贸 · B2B 目录与询盘',prev:'corp',title:'Go global.'}factory-v2.jsschemes 追加'trade-pro':['#0b3d91','#e8f1fb','GO GLOBAL']
- theme_color 字典追加
- Step 4:
python -m pytest -q全绿 +for f in app/static/*.js; do node --check "$f"; done通过 - Step 5: commit
feat: register trade-pro in theme color map and all six frontend entry points
Task 7: E2E 验收——真实站点切主题构建(Phase 2 门槛)
Files: 无代码(验证 + 记录)
- Step 1: 登录拿 cookie/CSRF(参照已验证流程:POST /api/auth/login → GET /api/auth/me)
- Step 2:
PUT /api/factory/sites/483295c29ea0/themebody{"theme":"trade-pro"}(确认返回成功) - Step 3:
POST /api/sites/483295c29ea0/builds(Idempotency-Key + CSRF)→ 轮询 job 至 done - Step 4: 断言:pages ≥203、errors 空;预览首页 HTTP 200、含
lang="en"、不含"首页/联系我们"(平台中文默认,spec F9 验收);/products/、/product/alloy-resin-system-window/200 - Step 5: 断言 6 入口:新工作台/
/classic建站表单、theme-gallery、factory-studio 主题卡里均出现 trade-pro(浏览器人工点验,截图存docs/superpowers/或记录于验收文件) - Step 6: 把结果写入
docs/superpowers/specs/2026-10-22-trade-pro-theme-design.md附录"Phase 2 验收记录";commit
Phase 3:完整视觉移植(Task 8-12)
以下任务的代码无法在计划里全文预写(从 cms 源逐段移植),但每步给出精确源文件、提取规则与验收断言。共用纪律:每完成一个 Task 跑全量 pytest + commit。
Task 8: 设计令牌与基础样式全量移植
- 源:
D:/project/cms/runtimes/astro-site-runtime/src/styles/tokens.css(250 行)+base.css(445 行) - 变量重命名加
--sf-tp-前缀(spec 4.2.6);按钮/交互元素全部sf-tp-类名(避开平台.btn/.primary/.request/.button/.nav-btn) - 验收断言:
themes/trade-pro/theme.css覆盖 tokens 全集(颜色/字体/圆角/间距);构建产物assets/theme.css含--sf-tp-变量;平台 custom.css 的.btn{...}与主题按钮类无交集(grep 断言) - pytest(现有快照不受影响)+ commit
Task 9: 图标宏库(新增设计——不是 cms 移植)
评审修正(2026-10-22):cms 源码与 dist 实测 0 个 SVG,其图标实为文本箭头
→(index 15 处、product 17 处)。用户需求"图标需要优化、现在很多都是箭头"意味着图标是超出 cms 源的新增设计,不与 dist 对照。
themes/trade-pro/_professional_components.html定义icon(name)宏:自设计内联 SVG 集合,线条风格与--sf-tp-*令牌一致,≥8 个:arrow-right / check / phone / mail / location / download / search / quote- 尺寸/描边随 CSS 变量(
currentColor、1.5pxstroke),跟随深浅文字色 - 验收:home/product 渲染中交互元素的图标均为 SVG,不再以裸
→作为唯一图标(对照对象是本主题自身,非 dist);aria-hidden="true"保证可访问性 - commit
Task 10: 九种独立布局逐个移植(每布局一个 commit)
顺序与源对照(cms src/pages/[locale]/ ↔ dist en/ ↔ trade-pro pages/):
home:index.astro ↔ dist 首页(Hero/FeatureGrid/CardGrid/CTA 结构)product:products/[slug].astro ↔ dist/products/alloy-resin-system-window/(参数表 specs、多图 gallery、询盘 CTA)listing:news/[...page].astro + products 列表 ↔ 按 kind 分支(Phase 0 已确认模板可拿到 page/kind 上下文;若不可分支由 category 承担产品列表,spec §7 兜底)category:product-category ↔ 分类页article:news/[slug] ↔ 文章详情about/ 7.contact(LeadForm 样式 + form_endpoint)/ 8.case(projects)/ 9.standard(applications、support)
每个布局的统一验收:构建对应真实页面 200;关键结构选择器与 dist 对照(header/hero/卡片/参数表/页脚);英文文案断言(无中文默认);手机宽度可读(人工抽查一次,全量视觉验收在 Task 12)。
Task 11: 五个变体布局 + search
- brand/service/faq/notfound/search 以 standard/listing 变体补齐为"结构合理"版本(后续迭代精修,spec 4.3)
Task 12: 预设、编辑器与全量验收(Phase 4)
page-presets.json13 种按 cms 首页/产品页实际区块结构重写(编辑器初始布局与真实设计一致)- 编辑器验证:hero/cta/features/collection/attributes 在 trade-pro 下可编辑、预览、保存草稿、发布(spec §6)。已知观察(Task 4 质量审查):main.py 编辑器侧读平台版 blocks.css,而构建产物可能用主题版——编辑器预览与最终站点可能样式不一致;本项验收时确认影响,必要时编辑器预览也走主题版
- 全站设计草稿验证:页眉/页脚/导航配置生效(显式值渲染、中文默认被值比对替换为英文兜底)
- 全量回归:
python -m pytest -q(≥210+新增全绿)、node --check全部 JS - 实战验收:站点 483295c29ea0 最终构建 ≥203 页零错误;视觉对照两层标准(spec §6)逐页记录
- 更新 spec 附录验收记录;commit
与 spec 的已记录偏差(评审裁定可接受)
- 发现函数放 core 而非 spec 4.2.1 写的 theme_store:等价实现。core 已被全部引用方(builder/main/theme_pro)导入,放 core 消除反向耦合;theme_store.BUILTIN 经 import 别名保持一致。
- builder.ENV 保持单目录(spec 4.2.4 提到两处构造点):已验证 site.html 是不引用共享宏的独立模板,且带主题前缀的路径加载在单目录下安全;仅 theme_pro.LOADER 改为 per-theme env_for。
- spec §6 "渲染快照"以 E2E 页面断言实现(构建后逐页 200 + 结构选择器 + 文案断言),不另建 Jinja 快照测试文件。
- /api/themes(main.py:267)经 core.THEMES 动态化自动生效,不设专项测试(catalog 由既有测试覆盖)。
- 前端入口 option 文案:三个 HTML 下拉用"Trade Pro · 外贸 B2B(英文)"中文风格与各文件现状一致(theme-gallery 用英文)。
- 平台数据层中文残留(Task 5 质量审查发现):bootstrap 生成的 8 个标准页(/about/ /contact/ /products/ /news/ 等)的
<title>、<h1>、面包屑来自平台造的中文标题(关于我们/联系我们/产品中心…),主题层无法翻译用户数据。Phase 3 视觉验收时接受(真实站点内容为英文导入数据时自然英文),若 bootstrap 英文化成为需求属平台任务另行立项。
用户反馈修复队列(2026-10-23 首页预览反馈,10b 收口后立即执行)
-
[新增 10j] cms 页眉/页脚移植(用户反馈"整体不是 cms 效果"):在平台 site_design 生成的 DOM 结构上,用 .sf-theme-trade-pro 作用域 CSS 完整还原 cms Header/Footer 视觉(Header.astro/Footer.astro 源码样式:白底细分割线、导航字距/悬停态、蓝色 GET A QUOTE 按钮、cms 页脚深色带与栏目布局)。保持平台 chrome 数据流(导航/公告/CTA 配置仍生效)。排在 10c-10i 之后、Task 11 之前。
-
.sf-tp-feature补 cms FeatureGrid.astro 的卡片皮肤:白底(--sf-tp-color-surface-default)+ hairline 边框(--sf-tp-color-border-subtle)+ 圆角 + 居中 + padding,900px 两列、620px 横排(源码 scoped CSS 全量对照) -
.sf-tp-card对照 cms dist 的 .card scoped CSS 补边框卡片皮肤(含 hover 抬升已有则保留) -
.sf-theme-trade-pro作用域内重设计.pro-art无图占位:brand 渐变底(surface-inverse → brand-primary 混合)+ 白色首字母居中 + 小写 kind 标签,替代共享宏灰块("10 PRODUCT"噪字) -
Hero 双按钮改横排(flex gap,620px 换行)——核对 pro.css hero 区现状
-
验收方式:浏览器截图前后对照(Playwright),产品卡/特性卡/占位块三处逐一确认
风险与回退
- 每个 Task 独立 commit,出问题
git revert单点回退 - 平台适配层(Task 2-4)有 4 主题回归保护:任何现有测试变红立即停止
- trade-pro 目录独立,删目录 + revert 白名单改动即完全移除