@beastmode-develeap/beastmode 0.1.293 → 0.1.295

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__ = "20260517-082511-56ac2d8";</script>
18
+ <script>window.__BUILD_STAMP__ = "20260517-091536-aa7266d";</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">
@@ -2504,6 +2504,17 @@ input[type="range"]::-webkit-slider-thumb {
2504
2504
  letter-spacing: 0.01em;
2505
2505
  }
2506
2506
 
2507
+ /* Forecast badge on card — shown only when no actual cost exists */
2508
+ .card-badge.badge-forecast {
2509
+ background: var(--bg-secondary, #f3f4f6);
2510
+ color: var(--text-secondary, #6b7280);
2511
+ border: 1px dashed var(--border, #d1d5db);
2512
+ font-family: var(--font-mono);
2513
+ font-size: 10px;
2514
+ font-style: italic;
2515
+ letter-spacing: 0.01em;
2516
+ }
2517
+
2507
2518
  /* Detail sidebar cost section */
2508
2519
  .detail-cost-section {
2509
2520
  padding: 12px 24px;
@@ -6328,6 +6339,7 @@ function PipelineView({
6328
6339
  globalSort,
6329
6340
  sortColumnItems,
6330
6341
  costsByItem,
6342
+ forecastsByItem,
6331
6343
  envVerifyByItem,
6332
6344
  }) {
6333
6345
  const [collapseKey, setCollapseKey] = useState(0);
@@ -6365,7 +6377,16 @@ function PipelineView({
6365
6377
  ${(() => {
6366
6378
  const cost = costsByItem && costsByItem[String(item.id)];
6367
6379
  const label = cost ? formatCost(cost.total_cost_usd) : null;
6368
- return label ? html`<span class="card-badge-wrap"><span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span></span>` : null;
6380
+ if (label) {
6381
+ return html`<span class="card-badge-wrap"><span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span></span>`;
6382
+ }
6383
+ const fc = forecastsByItem && forecastsByItem[String(item.id)];
6384
+ if (fc && typeof fc.median === 'number') {
6385
+ const flabel = '~' + formatCost(fc.median);
6386
+ const title = 'Forecast: ' + formatCost(fc.low) + ' – ' + formatCost(fc.high) + ' (' + fc.confidence + ')';
6387
+ return html`<span class="card-badge-wrap"><span class="card-badge badge-forecast" title=${title}>${flabel}</span></span>`;
6388
+ }
6389
+ return null;
6369
6390
  })()}
6370
6391
  ${(() => {
6371
6392
  const env = item.extra && item.extra.current_env;
@@ -6712,6 +6733,7 @@ function BoardPage({ selectedProject }) {
6712
6733
  const [sortOpen, setSortOpen] = useState(false);
6713
6734
  const [epicCollapseKey, setEpicCollapseKey] = useState(0);
6714
6735
  const [costsByItem, setCostsByItem] = useState({});
6736
+ const [forecastsByItem, setForecastsByItem] = useState({});
6715
6737
  const [envVerifyByItem, setEnvVerifyByItem] = useState({});
6716
6738
  const [viewMode, setViewMode] = useState(() => {
6717
6739
  try {
@@ -6789,6 +6811,18 @@ function BoardPage({ selectedProject }) {
6789
6811
  .catch(() => {});
6790
6812
  }, [selectedProject]);
6791
6813
 
6814
+ // Forecast badges: cards without actual cost yet get a predicted
6815
+ // cost range based on historical k-NN over Done items. The endpoint
6816
+ // returns null for items with insufficient neighbor data; those
6817
+ // cards show no forecast badge at all.
6818
+ const fetchForecasts = useCallback(() => {
6819
+ const boardParam = (selectedProject && selectedProject !== 'all') ? '?board=' + encodeURIComponent(selectedProject) : '';
6820
+ fetch('/api/costs/forecasts' + boardParam)
6821
+ .then(r => r.ok ? r.json() : { forecasts: {} })
6822
+ .then(data => setForecastsByItem((data && data.forecasts) || {}))
6823
+ .catch(() => {});
6824
+ }, [selectedProject]);
6825
+
6792
6826
  // Story 7: batch-fetch env verify status for items that have a current_env.
6793
6827
  // The endpoint is per-item, so we issue parallel requests with allSettled
6794
6828
  // — failed individual fetches don't block the others, and the badge falls
@@ -6826,12 +6860,14 @@ function BoardPage({ selectedProject }) {
6826
6860
  useEffect(() => {
6827
6861
  fetchItems();
6828
6862
  fetchCosts();
6863
+ fetchForecasts();
6829
6864
  const interval = setInterval(() => {
6830
6865
  // Silent refresh — don't show loading spinner on polls
6831
6866
  api('GET', '/api/board/items')
6832
6867
  .then(data => setItems(data.items || []))
6833
6868
  .catch(() => {});
6834
6869
  fetchCosts();
6870
+ fetchForecasts();
6835
6871
  }, 10000);
6836
6872
  return () => clearInterval(interval);
6837
6873
  }, [selectedProject]);
@@ -7499,6 +7535,7 @@ function BoardPage({ selectedProject }) {
7499
7535
  globalSort=${globalSort}
7500
7536
  sortColumnItems=${sortColumnItems}
7501
7537
  costsByItem=${costsByItem}
7538
+ forecastsByItem=${forecastsByItem}
7502
7539
  envVerifyByItem=${envVerifyByItem}
7503
7540
  />`
7504
7541
  : html`
@@ -7576,7 +7613,16 @@ function BoardPage({ selectedProject }) {
7576
7613
  ${(() => {
7577
7614
  const cost = costsByItem && costsByItem[String(item.id)];
7578
7615
  const label = cost ? formatCost(cost.total_cost_usd) : null;
7579
- return label ? html`<span class="card-badge-wrap"><span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span></span>` : null;
7616
+ if (label) {
7617
+ return html`<span class="card-badge-wrap"><span class="card-badge badge-cost" title=${'Total cost: ' + label}>${label}</span></span>`;
7618
+ }
7619
+ const fc = forecastsByItem && forecastsByItem[String(item.id)];
7620
+ if (fc && typeof fc.median === 'number') {
7621
+ const flabel = '~' + formatCost(fc.median);
7622
+ const title = 'Forecast: ' + formatCost(fc.low) + ' – ' + formatCost(fc.high) + ' (' + fc.confidence + ')';
7623
+ return html`<span class="card-badge-wrap"><span class="card-badge badge-forecast" title=${title}>${flabel}</span></span>`;
7624
+ }
7625
+ return null;
7580
7626
  })()}
7581
7627
  ${(() => {
7582
7628
  const env = item.extra && item.extra.current_env;
@@ -1 +1 @@
1
- 56ac2d85d5fbac9e6b78b3effba5f4793a52036d
1
+ aa7266d306c7cabb43469ad8d5c077e26d4471d2
@@ -1 +1 @@
1
- 20260517-082511-56ac2d8
1
+ 20260517-091536-aa7266d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beastmode-develeap/beastmode",
3
- "version": "0.1.293",
3
+ "version": "0.1.295",
4
4
  "description": "BeastMode Dark Factory — turn intent into verified software",
5
5
  "type": "module",
6
6
  "bin": {