chore: checkpoint web Blender migration source, tools and docs
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""只读查看桌面 Blender UI → Web Blender UI 完全对标主线。
复用既有进度快照;不启动执行器、写运行状态、构建、部署或运行功能验证。
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from view_wbv2_progress import (
FOCUS_NAMES, STATE_NAMES, WORK_NAMES, duration, interval_value, local_time,
snapshot as project_snapshot,
)
HEALTH_NAMES = {
'normal': '进程和心跳正常(不代表功能推进正常)',
'unknown': '缺少运行记录', 'stopped': '执行器已停止',
'process_missing': '执行器退出或 PID 已复用',
'process_unknown': '进程身份无法确认', 'heartbeat_stale': '心跳过期',
'worker_exited_pending_handoff': '工作进程退出,等待交接',
}
SIGNALS = {
'provider_retry_or_reconnect': '有服务失败或重连记录',
'artifact_command_active': '正在生成产物',
'candidate_refresh_due': '候选已到刷新条件',
'source_idle_over_3m': '超过三分钟未观察到源码修改(不单独判为空转)',
'source_progress_stale': '近期源码修改间隔较长',
'focus_repeated_no_progress': '重复无进展或阻塞,需要改变定位策略',
}
def snapshot(recent_limit=3):
# Use the same bounded reads, exact gap IDs and remaining-over-completed
# reconciliation as the original viewer; do not fork scheduler semantics.
data = project_snapshot(recent_limit)
ui = data.get('uiParity') or {}
return {
'observedAt': data['observedAt'], 'task': data.get('task'),
'scope': ui.get('phase'), 'planPath': ui.get('planPath'),
'goal': ui.get('goal'), 'currentModule': ui.get('currentModule'),
'associatedModules': ui.get('associatedModules', []),
'modules': ui.get('modules', []), 'runner': data.get('runner', {}),
'provider': data.get('provider', {}), 'candidate': data.get('candidate'),
'latestImplementation': data.get('latestImplementation', {}),
'recent': data.get('recent', []), 'build': data.get('build', {}),
'focuses': data.get('progress', {}).get('focuses', []),
'alerts': data.get('alerts', []), 'notes': data.get('notes', []),
'validation': data.get('validation', 'UNKNOWN'),
'acceptance': data.get('acceptance', 'UNKNOWN'),
'basis': '只读既有交接;计数仅覆盖已登记子项,不是界面覆盖率或验收结果',
}
def display(data, module_id=None, details=False):
print(f"桌面 Blender UI → Web Blender UI 对标 | {local_time(data['observedAt'])}")
print(f"任务:{data.get('task') or '未知'}")
runner = data['runner']
if runner:
print(f"执行:{STATE_NAMES.get(runner.get('recordedState'), runner.get('recordedState'))};"
f"{HEALTH_NAMES.get(runner.get('health'), '状态未知')}")
print(f"当前第 {runner.get('cycle')} 轮 | {FOCUS_NAMES.get(runner.get('focus'), runner.get('focus'))} | "
f"{WORK_NAMES.get(runner.get('workState'), runner.get('workState') or '未知')}")
print(f"本轮已用 {duration(runner.get('elapsedSeconds'))};心跳 {duration(runner.get('heartbeatAgeSeconds'))};"
f"最近动作 {duration(runner.get('lastActionAgeSeconds'))};源码空闲 {duration(runner.get('sourceIdleSeconds'))}")
print(f"活动命令 {runner.get('activeCommandCount', 0)};构建/打包 {runner.get('activeArtifactCommands', 0)};"
f"本轮观察到源码文件 {len(runner.get('observedProductionFiles') or [])} 个")
if runner.get('reason'):
print(f"原因:{runner['reason']}")
if data['currentModule']:
print(f"当前模块:{data['currentModule']}")
else:
print('当前精确模块未记录;执行方向关联:' + ('、'.join(data['associatedModules']) or '未记录'))
provider = data['provider']
if provider:
print(f"服务:失败记录 {provider.get('consecutiveFailures', 0)};重连 {'是' if provider.get('reconnecting') else '否'};"
f"退避 {duration(provider.get('retrySeconds'))};错误 {provider.get('error') or '无当前分类'}")
for focus in data['focuses']:
if focus.get('deferredPathSeconds') or focus.get('retrySeconds'):
print(f"路径等待:{focus.get('title')} / {focus.get('deferredPathId') or '当前方向'};"
f"暂缓上限 {duration(focus.get('deferredPathSeconds', 0))},重试 {duration(focus.get('retrySeconds', 0))}")
for signal in data['alerts']:
print('执行信号:' + SIGNALS.get(signal, HEALTH_NAMES.get(signal, signal)))
print('\n模块状态(只统计专属登记项;零项不表示未实现,也不表示已完成):')
for module in data['modules']:
if module_id and module.get('id') != module_id:
continue
print(f" [{module['id']}] {module.get('title', '')}:{module['status']}")
if module.get('gaps'):
counts = module['counts']
print(f" 登记子项:已报告实现 {counts['implemented_unverified']} / 定位 {counts['locate']} / 待实现或集成 {counts['open']}")
elif module.get('progressHandoff'):
handoff = module['progressHandoff']
print(f" 模块交接:已接入 {len(handoff.get('completedRoutes') or [])} 条 / "
f"剩余 {len(handoff.get('remainingRoutes') or [])} 条;来源 {handoff.get('reportPath', 'manifest')}")
print(f" 样式与完整入口对标:{module.get('visualParity', '未记录')};整模块尚未确认完成")
if details or module_id:
print(f" 路由:{module.get('route') or '尚无模块专属路由记录'}")
if module.get('relatedHandoffPath'):
print(f" 相关方向交接:{module['relatedHandoffPath']}(不代表整模块交接)")
if module.get('ownerDocs'):
print(f" 文档:{module['ownerDocs']}")
for gap in module.get('gaps', []):
implemented = gap.get('status') == 'implemented_unverified'
phase = '定位' if gap.get('phase') == 'locate' else '实现'
label = '已报告实现,未验证' if implemented else '尚未闭合'
print(f" [{gap['id']}] {phase} / {label}:{gap.get('entry')}")
if not implemented:
finding = gap.get('latestFinding') or {}
residual = gap.get('remainingSubroutes') or []
print(f" 剩余:{';'.join(residual) if residual else finding.get('reason') or gap.get('missing') or '未记录'}")
print(f" 下一步:{finding.get('action') or gap.get('nextAction') or '未记录'}")
if gap.get('sourcePaths'):
print(' 源码落点:' + '、'.join(gap['sourcePaths']))
if module.get('progressHandoff'):
handoff = module['progressHandoff']
latest = handoff.get('latestImplementation') or {}
if latest:
print(f" 最近补漏:{latest.get('reportPath', 'manifest')};候选 {latest.get('candidatePath', '未记录')};验证 {latest.get('validation', 'UNKNOWN')}")
for route in handoff.get('remainingRoutes') or []:
print(f" 模块余项:{route}")
print(f" 模块下一步:{handoff.get('nextAction', '未记录')}")
latest = data['latestImplementation']
latest_time = latest.get('at')
print(f"\n最近源码交接:第 {latest.get('cycle', '—')} 轮 / {local_time(latest_time)}")
print(' ' + (latest.get('businessEntry') or latest.get('workflow') or '尚无记录'))
for row in data['recent']:
print(f" [{row['cycle']}] {row.get('summary') or row.get('businessEntry') or '未提供摘要'}")
candidate = data['candidate']
if candidate:
print(f"\n匹配候选记录:{candidate.get('path')}")
gap = candidate.get('sourceGap')
threshold = candidate.get('refreshGap')
pending = str(gap) if gap is not None else '未知'
print(f" 待组合 {pending} 轮;刷新阈值 {threshold or '未配置'};"
f"{'已到刷新条件' if candidate.get('refreshDue') else '未到已知刷新条件'}")
print(f" 部署:{candidate.get('deployment') or '未知'};源码/运行对应关系未验证")
else:
print('\n匹配候选:尚无可读取记录')
if details and data['build'].get('logPath'):
print('最近构件日志:' + data['build']['logPath'])
print(f"验证:{data['validation']};验收:{data['acceptance']}。不以心跳、轮次或构建判断 UI 已完全对标。")
for note in data['notes']:
print('提示:' + note)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--json', action='store_true', help='JSON 输出;持续模式为每行一个快照')
parser.add_argument('--watch', action='store_true', help='持续刷新,Ctrl+C 退出')
parser.add_argument('--interval', type=interval_value, default=10, metavar='秒', help='刷新间隔,默认 10 秒,至少 1 秒')
parser.add_argument('--module', metavar='ID', help='只显示指定模块;同时展开该模块细节')
parser.add_argument('--details', action='store_true', help='展开路由、缺口、下一动作、源码和报告位置')
parser.add_argument('--recent', type=int, choices=range(0, 11), default=3, metavar='0..10', help='最近交接条数,默认 3')
args = parser.parse_args()
try:
while True:
data = snapshot(args.recent)
if args.module:
if args.module not in {item['id'] for item in data['modules']}:
print(f'模块 {args.module} 未在当前可读记录中登记。', file=sys.stderr)
for note in data['notes']:
print(note, file=sys.stderr)
return 1
if args.json:
data['modules'] = [item for item in data['modules'] if item['id'] == args.module]
data['moduleFilter'] = args.module
if args.json:
print(json.dumps(data, ensure_ascii=False, indent=None if args.watch else 2), flush=True)
else:
if args.watch and sys.stdout.isatty():
print('\033[2J\033[H', end='')
display(data, args.module, args.details)
sys.stdout.flush()
if not args.watch:
return 0 if data.get('task') and data.get('runner') else 1
time.sleep(args.interval)
except (KeyboardInterrupt, BrokenPipeError):
return 0
if __name__ == '__main__':
raise SystemExit(main())