ims-flow-dashboard 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/scan.mjs +30 -12
  3. package/template.html +13 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ims-flow-dashboard",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "IMS Flow 控制台 — 可视化 OpenSpec 变更生命周期、产物完整性与任务进度",
5
5
  "type": "module",
6
6
  "files": [
package/scan.mjs CHANGED
@@ -541,14 +541,19 @@ function loadRoots() {
541
541
  console.error('[warn] projects.json 读取失败:', e.message);
542
542
  }
543
543
  if (roots.length === 0) {
544
- console.error('未配置扫描根目录。请在以下任一位置创建 projects.json (参考 projects.example.json):');
545
- console.error(' ' + path.join(TOOL_DIR, 'projects.json') + ' (源码 clone 模式)');
546
- console.error(' ' + path.join(USER_CONFIG_DIR, 'projects.json') + ' (npm 全局安装模式)');
544
+ console.error('未配置扫描根目录。两种方式配置:');
545
+ console.error(' 1. 直接运行 imsflow(会自动进入服务模式),在页面顶栏「工作区」面板里添加(推荐,自动写入配置)');
546
+ console.error(' 2. 手动创建 ' + PROJECTS_FILE + '(参考 projects.example.json');
547
547
  process.exit(1);
548
548
  }
549
549
  return roots;
550
550
  }
551
551
 
552
+ // 零配置空数据:serve 允许「先打开页面、再在面板里加根」,空态是合法状态而非错误
553
+ function emptyData() {
554
+ return { generatedAt: new Date().toISOString(), roots: [], workspaces: [] };
555
+ }
556
+
552
557
  function filterRoots(roots, filterWord) {
553
558
  if (!filterWord) return roots;
554
559
  const w = filterWord.toLowerCase();
@@ -641,10 +646,16 @@ function buildHtml(data) {
641
646
  const args = process.argv.slice(2);
642
647
  const wantOpen = args.includes('--open');
643
648
  const positional = args.filter(a => !a.startsWith('--'));
644
- const serveMode = positional.includes('serve');
649
+ let serveMode = positional.includes('serve');
645
650
  // 非 flag 位置参数(除 serve 外)= 根路径过滤词(按名称或路径模糊匹配)
646
651
  const filterWord = positional.filter(a => a !== 'serve').pop() || null;
647
652
 
653
+ // 零配置: 快照页面只读、无法在页面上添加根 → 自动转服务模式(页面「工作区」面板可添加, 添加即持久化)
654
+ if (!serveMode && safeLoadRoots().length === 0) {
655
+ console.log('[info] 未配置扫描根目录 → 自动进入服务模式,打开页面后在顶栏「工作区」面板中添加扫描根');
656
+ serveMode = true;
657
+ }
658
+
648
659
  if (serveMode) {
649
660
  // ---- 服务模式: imsflow serve [--port=N] ----
650
661
  // 页面内「重新扫描」按钮调 /api/rescan 实时重扫, 解决快照模式的刷新问题
@@ -687,16 +698,16 @@ if (serveMode) {
687
698
  saveRoots(roots);
688
699
  } else if (action === 'remove') {
689
700
  if (!roots.some(r => samePath(r, raw))) { sendJson(400, { ok: false, error: '该路径不在扫描列表中' }); return; }
690
- if (roots.length <= 1) { sendJson(400, { ok: false, error: '至少保留一个扫描根,移除后列表为空会导致服务无内容可扫' }); return; }
701
+ // 允许删到 0:空态是合法状态,页面上还能继续添加(面板始终可用)
691
702
  saveRoots(roots.filter(r => !samePath(r, raw)));
692
703
  } else {
693
704
  sendJson(400, { ok: false, error: 'action 必须是 add 或 remove' });
694
705
  return;
695
706
  }
696
- const data = scanAll(null);
697
- refresh(data);
698
- console.log(`[roots:${action}] ${raw} · 现在 ${data.workspaces.length} 个工作区`);
699
- sendJson(200, { ok: true, roots: data.roots });
707
+ const configured = safeLoadRoots();
708
+ refresh(configured.length > 0 ? scanAll(null, configured) : emptyData());
709
+ console.log(`[roots:${action}] ${raw} · 现在 ${lastData.workspaces.length} 个工作区`);
710
+ sendJson(200, { ok: true, roots: configured });
700
711
  } else if (url === '/api/rescan') {
701
712
  // GET = 全量重扫(页面路径筛选器负责聚焦); POST {roots:[...]} = 只扫勾选的根, 不落盘
702
713
  let subset = null;
@@ -706,7 +717,7 @@ if (serveMode) {
706
717
  subset = [...new Set((Array.isArray(body.roots) ? body.roots : []).filter(r => configured.some(c => samePath(c, String(r)))))];
707
718
  if (subset.length === 0) { sendJson(400, { ok: false, error: '未选择任何已配置的扫描根' }); return; }
708
719
  } else if (safeLoadRoots().length === 0) {
709
- sendJson(500, { ok: false, error: 'projects.json 未配置任何扫描根' });
720
+ sendJson(500, { ok: false, error: '未配置扫描根,请在顶栏「工作区」面板添加' });
710
721
  return;
711
722
  }
712
723
  const t0 = Date.now();
@@ -717,8 +728,15 @@ if (serveMode) {
717
728
  res.end(JSON.stringify(data));
718
729
  } else if (url === '/' || url === '/index.html') {
719
730
  if (!cachedHtml) {
720
- refresh(scanAll(null));
721
- console.log(`初始扫描完成, 页面 ${(cachedHtml.length / 1024).toFixed(0)} KB`);
731
+ const configured = safeLoadRoots();
732
+ if (configured.length > 0) {
733
+ refresh(scanAll(null, configured));
734
+ console.log(`初始扫描完成, 页面 ${(cachedHtml.length / 1024).toFixed(0)} KB`);
735
+ } else {
736
+ // 零配置: 渲染空态页面, 让用户在「工作区」面板里添加根(不能在这里 exit —— 会杀掉整个 serve)
737
+ refresh(emptyData());
738
+ console.log('未配置扫描根, 页面显示空态(配置文件: ' + PROJECTS_FILE + ')');
739
+ }
722
740
  } else if (templateMtime() !== cachedTpl && lastData) {
723
741
  // 模板文件改过了 → 用上次扫描数据重建(不必重扫),刷新页面即可看到新 UI
724
742
  refresh(lastData);
package/template.html CHANGED
@@ -1031,6 +1031,13 @@ const EMPTY_HTML = '<div class="placeholder">'
1031
1031
  + '<div class="ph-2">生命周期 · 产物清单 · 任务状态 · 产物全文</div>'
1032
1032
  + '</div>';
1033
1033
 
1034
+ /* 零配置引导空态 —— serve 模式且没有任何扫描根时显示(首次安装的典型状态) */
1035
+ const NO_ROOT_HTML = '<div class="placeholder">'
1036
+ + '<svg class="ph-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7a2 2 0 0 1 2-2h4l2 2.5h7a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2Z"/><path d="M12 11v5M9.5 13.5h5"/></svg>'
1037
+ + '<div class="ph-1">未配置扫描根目录</div>'
1038
+ + '<div class="ph-2">在右侧弹出的「工作区」面板中粘贴目录路径并添加,配置会自动保存</div>'
1039
+ + '</div>';
1040
+
1034
1041
  /* ---------- 轻量 markdown 渲染 ---------- */
1035
1042
  function renderMd(src) {
1036
1043
  if (!src) return '<div class="empty-panel">(空文件)</div>';
@@ -1524,6 +1531,12 @@ function resetView() {
1524
1531
  };
1525
1532
  updateStats();
1526
1533
  renderSidebar();
1534
+ // 零配置(首次安装典型状态): serve 模式 + 无任何扫描根 → 引导空态 + 自动弹出「工作区」面板
1535
+ if (/^https?:$/.test(location.protocol) && (DATA.workspaces || []).length === 0 && (DATA.roots || []).length === 0) {
1536
+ document.getElementById('main').innerHTML = NO_ROOT_HTML;
1537
+ const b = document.getElementById('wsBtn');
1538
+ if (b) setTimeout(() => { if (!b.disabled) b.click(); }, 150); // 等面板事件绑定完成后再弹
1539
+ }
1527
1540
  // 恢复重扫前选中的变更(serve 模式整页重载后从 sessionStorage 找回)
1528
1541
  try {
1529
1542
  const savedSel = sessionStorage.getItem('imsflow.sel');