@beastmode-develeap/beastmode 0.1.275 → 0.1.277

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.
@@ -15,7 +15,7 @@
15
15
  }
16
16
  </script>
17
17
  <!--BOARD_DATA-->
18
- <script>window.__BUILD_STAMP__ = "20260516-124812-2221634";</script>
18
+ <script>window.__BUILD_STAMP__ = "20260516-140206-eb6aa87";</script>
19
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
20
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
21
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -1259,6 +1259,11 @@ input[type="range"]::-webkit-slider-thumb {
1259
1259
  .card-status-age.status-age-info { color: var(--accent); background: var(--accent-subtle); }
1260
1260
  .card-status-age.status-age-warn { color: #fb923c; background: rgba(249, 115, 22, 0.15); }
1261
1261
  .card-status-age.status-age-stale { color: #f87171; background: rgba(239, 68, 68, 0.15); }
1262
+ .card-badge-wrap {
1263
+ /* Flex child wrapper: blockified by parent flex, but sized to content.
1264
+ The inner .card-badge is NOT a direct flex item, so it keeps display:inline-block. */
1265
+ line-height: 0;
1266
+ }
1262
1267
  .card-badge {
1263
1268
  display: inline-block;
1264
1269
  padding: 2px 8px;
@@ -6319,7 +6324,7 @@ function PipelineView({
6319
6324
  ${(() => {
6320
6325
  const cost = costsByItem && costsByItem[String(item.id)];
6321
6326
  const label = cost ? formatCost(cost.total_cost_usd) : null;
6322
- return label ? html`<span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span>` : null;
6327
+ return label ? html`<span class="card-badge-wrap"><span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span></span>` : null;
6323
6328
  })()}
6324
6329
  ${(() => {
6325
6330
  const env = item.extra && item.extra.current_env;
@@ -6736,8 +6741,7 @@ function BoardPage({ selectedProject }) {
6736
6741
  // append ?board=<proj> here. Errors are swallowed: if the cost
6737
6742
  // batch endpoint is unreachable, cards render without badges.
6738
6743
  const fetchCosts = useCallback(() => {
6739
- const proj = localStorage.getItem('beastmode-selected-project') || '';
6740
- const boardParam = (proj && proj !== 'all') ? '?board=' + encodeURIComponent(proj) : '';
6744
+ const boardParam = (selectedProject && selectedProject !== 'all') ? '?board=' + encodeURIComponent(selectedProject) : '';
6741
6745
  fetch('/api/costs/by-items' + boardParam)
6742
6746
  .then(r => r.ok ? r.json() : {})
6743
6747
  .then(data => setCostsByItem(data || {}))
@@ -7531,7 +7535,7 @@ function BoardPage({ selectedProject }) {
7531
7535
  ${(() => {
7532
7536
  const cost = costsByItem && costsByItem[String(item.id)];
7533
7537
  const label = cost ? formatCost(cost.total_cost_usd) : null;
7534
- return label ? html`<span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span>` : null;
7538
+ return label ? html`<span class="card-badge-wrap"><span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span></span>` : null;
7535
7539
  })()}
7536
7540
  ${(() => {
7537
7541
  const env = item.extra && item.extra.current_env;
@@ -8568,7 +8572,21 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
8568
8572
  setGuideErrors(prev => ({ ...prev, [name]: null }));
8569
8573
  return resp;
8570
8574
  } catch (e) {
8571
- setGuideErrors(prev => ({ ...prev, [name]: e.message }));
8575
+ // 404 means no guide exists yet — treat as empty sentinel so the
8576
+ // render shows the status-based fallback instead of a raw error.
8577
+ const noGuide = e.message && (
8578
+ e.message.includes('No codebase guide') ||
8579
+ e.message.includes('Guide file not found') ||
8580
+ e.message === 'HTTP 404'
8581
+ );
8582
+ if (noGuide) {
8583
+ setGuideContent(prev => ({
8584
+ ...prev,
8585
+ [name]: { ...(prev[name] || {}), [level]: '' },
8586
+ }));
8587
+ } else {
8588
+ setGuideErrors(prev => ({ ...prev, [name]: e.message }));
8589
+ }
8572
8590
  return null;
8573
8591
  }
8574
8592
  }, []);
@@ -8579,12 +8597,14 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
8579
8597
  return;
8580
8598
  }
8581
8599
  setExpandedProject(name);
8582
- // Fetch L1 content lazily on first expand if analysis is complete.
8583
- const st = analysisStatuses[name];
8584
- if (st && st.status === 'complete' && !(guideContent[name] && guideContent[name].l1)) {
8600
+ // Optimistically fetch L1 guide on expand regardless of analysis status.
8601
+ // Guide files exist even when a re-analysis is in progress (previous run's
8602
+ // output persists). fetchGuide silently treats 404 as empty so this is
8603
+ // safe to call unconditionally — it never shows a raw HTTP error.
8604
+ if (!(guideContent[name] && guideContent[name].l1)) {
8585
8605
  await fetchGuide(name, 'l1');
8586
8606
  }
8587
- }, [expandedProject, analysisStatuses, guideContent, fetchGuide]);
8607
+ }, [expandedProject, guideContent, fetchGuide]);
8588
8608
 
8589
8609
  const stopPolling = useCallback((name) => {
8590
8610
  if (pollTimersRef.current[name]) {
@@ -8831,7 +8851,7 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
8831
8851
  data-testid="codebase-summary-panel">
8832
8852
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
8833
8853
  <strong style="font-size:13px;">Codebase summary</strong>
8834
- ${pillState === 'complete' && guide.l1 ? html`
8854
+ ${guide.l1 ? html`
8835
8855
  <button class="btn btn-sm btn-secondary"
8836
8856
  onClick=${() => toggleFullGuide(proj.name)}
8837
8857
  data-testid="project-show-full-guide">
@@ -8840,44 +8860,45 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
8840
8860
  ` : null}
8841
8861
  </div>
8842
8862
  ${guideErr ? html`<div class="error-msg" style="margin-bottom:8px;">${guideErr}</div>` : null}
8843
- ${pillState === 'complete'
8844
- ? (guide.l1 == null
8845
- ? html`<div style="color:var(--text-secondary);font-size:12px;">Loading guide\u2026</div>`
8846
- : html`
8847
- <pre style="white-space:pre-wrap;font-family:inherit;font-size:13px;line-height:1.5;
8848
- background:var(--bg-subtle);padding:10px;border-radius:6px;margin:0;"
8849
- data-testid="codebase-guide-l1">${guide.l1}</pre>
8850
- ${showFullGuide[proj.name]
8851
- ? (guide.l2 == null
8852
- ? html`<div style="margin-top:8px;color:var(--text-secondary);font-size:12px;">Loading full guide\u2026</div>`
8853
- : html`
8854
- <pre style="white-space:pre-wrap;font-family:inherit;font-size:12px;line-height:1.5;
8855
- background:var(--bg-subtle);padding:10px;border-radius:6px;margin-top:8px;"
8856
- data-testid="codebase-guide-l2">${guide.l2}</pre>
8857
- `)
8858
- : null}
8859
- `)
8860
- : pillState === 'running'
8861
- ? html`<div style="display:flex;align-items:center;gap:8px;color:var(--text-secondary);">
8862
- <span class="spinner" style="display:inline-block;width:12px;height:12px;border:2px solid var(--border);
8863
- border-top-color:var(--accent);border-radius:50%;
8864
- animation:spin 0.8s linear infinite;"></span>
8865
- Analyzing codebase\u2026
8866
- </div>`
8867
- : pillState === 'failed'
8868
- ? html`<div>
8869
- <div class="error-msg" style="margin-bottom:8px;">${st.error || 'Analysis failed'}</div>
8870
- <button class="btn btn-sm btn-primary"
8871
- onClick=${() => reanalyze(proj.name)}
8872
- data-testid="project-retry-btn">Retry</button>
8863
+ ${guide.l1 === undefined
8864
+ ? html`<div style="color:var(--text-secondary);font-size:12px;">Loading guide\u2026</div>`
8865
+ : guide.l1
8866
+ ? html`
8867
+ ${isBusy ? html`<div style="font-size:11px;color:var(--text-secondary);margin-bottom:6px;">Re-analysis in progress\u2026</div>` : null}
8868
+ <pre style="white-space:pre-wrap;font-family:inherit;font-size:13px;line-height:1.5;
8869
+ background:var(--bg-subtle);padding:10px;border-radius:6px;margin:0;"
8870
+ data-testid="codebase-guide-l1">${guide.l1}</pre>
8871
+ ${showFullGuide[proj.name]
8872
+ ? (guide.l2 === undefined
8873
+ ? html`<div style="margin-top:8px;color:var(--text-secondary);font-size:12px;">Loading full guide\u2026</div>`
8874
+ : html`
8875
+ <pre style="white-space:pre-wrap;font-family:inherit;font-size:12px;line-height:1.5;
8876
+ background:var(--bg-subtle);padding:10px;border-radius:6px;margin-top:8px;"
8877
+ data-testid="codebase-guide-l2">${guide.l2}</pre>
8878
+ `)
8879
+ : null}
8880
+ `
8881
+ : pillState === 'running'
8882
+ ? html`<div style="display:flex;align-items:center;gap:8px;color:var(--text-secondary);">
8883
+ <span class="spinner" style="display:inline-block;width:12px;height:12px;border:2px solid var(--border);
8884
+ border-top-color:var(--accent);border-radius:50%;
8885
+ animation:spin 0.8s linear infinite;"></span>
8886
+ Analyzing codebase\u2026
8873
8887
  </div>`
8874
- : html`<div style="color:var(--text-secondary);">
8875
- Not analyzed yet \u2014
8876
- <button class="btn btn-sm btn-primary"
8877
- style="margin-left:6px;"
8878
- onClick=${() => reanalyze(proj.name)}
8879
- data-testid="project-run-analysis-btn">Run analysis</button>
8880
- </div>`}
8888
+ : pillState === 'failed'
8889
+ ? html`<div>
8890
+ <div class="error-msg" style="margin-bottom:8px;">${st.error || 'Analysis failed'}</div>
8891
+ <button class="btn btn-sm btn-primary"
8892
+ onClick=${() => reanalyze(proj.name)}
8893
+ data-testid="project-retry-btn">Retry</button>
8894
+ </div>`
8895
+ : html`<div style="color:var(--text-secondary);">
8896
+ Not analyzed yet \u2014
8897
+ <button class="btn btn-sm btn-primary"
8898
+ style="margin-left:6px;"
8899
+ onClick=${() => reanalyze(proj.name)}
8900
+ data-testid="project-run-analysis-btn">Run analysis</button>
8901
+ </div>`}
8881
8902
  </div>
8882
8903
  ` : null}
8883
8904
  <${EnvironmentPanel} projectName=${proj.name} />
@@ -11723,6 +11744,24 @@ function App() {
11723
11744
  setSelectedProjectRaw(value);
11724
11745
  }, []);
11725
11746
 
11747
+ // Auto-select the first registered project when localStorage has no prior
11748
+ // choice. Without this, items and costs both route to the default board
11749
+ // (board.db) which doesn't match project board data — no badges render in
11750
+ // a fresh Playwright or incognito session. Multi-project factories default
11751
+ // to the first registered project; users can override via the sidebar.
11752
+ useEffect(() => {
11753
+ if (localStorage.getItem('beastmode-selected-project')) return;
11754
+ fetch('/api/projects')
11755
+ .then(r => r.ok ? r.json() : null)
11756
+ .then(data => {
11757
+ const projects = data && data.projects;
11758
+ if (Array.isArray(projects) && projects.length > 0) {
11759
+ setSelectedProject(projects[0].name);
11760
+ }
11761
+ })
11762
+ .catch(() => {});
11763
+ }, []);
11764
+
11726
11765
  // If ?view=pipeline or ?view=board is in the URL but no hash route is
11727
11766
  // set, the router would default to Dashboard. Redirect to #/board so
11728
11767
  // the board page renders (and BoardPage can read the ?view= param).
@@ -1 +1 @@
1
- 222163459522e6dc1c977285090302e69b524da2
1
+ eb6aa87c2a0979bee58379926ffeb4c8bb6ffa12
@@ -1 +1 @@
1
- 20260516-124812-2221634
1
+ 20260516-140206-eb6aa87
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beastmode-develeap/beastmode",
3
- "version": "0.1.275",
3
+ "version": "0.1.277",
4
4
  "description": "BeastMode Dark Factory — turn intent into verified software",
5
5
  "type": "module",
6
6
  "bin": {