@beastmode-develeap/beastmode 0.1.35 → 0.1.36

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.
@@ -2268,7 +2268,8 @@ async function api(method, path, body) {
2268
2268
  // Each project has its own SQLite DB on the server — without this
2269
2269
  // scoping every project would share one global board.
2270
2270
  const isBoardCall = path.startsWith('/api/board/items') ||
2271
- path.startsWith('/api/board/updates');
2271
+ path.startsWith('/api/board/updates') ||
2272
+ path.startsWith('/api/board/attachments');
2272
2273
  if (isBoardCall) {
2273
2274
  const proj = localStorage.getItem('beastmode-selected-project') || '';
2274
2275
  if (proj && proj !== 'all' && !path.includes('board=')) {
@@ -3205,6 +3206,11 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
3205
3206
  const [updates, setUpdates] = useState([]);
3206
3207
  const [loadingUpdates, setLoadingUpdates] = useState(true);
3207
3208
  const [sortNewest, setSortNewest] = useState(true);
3209
+ // Attachments (Gap 8a — 2026-04-15): list every attachment the
3210
+ // board service has for this item, render image types as thumbnails
3211
+ // that open in a new tab when clicked.
3212
+ const [attachments, setAttachments] = useState([]);
3213
+ const [loadingAttachments, setLoadingAttachments] = useState(true);
3208
3214
  const sidebarRef = useRef(null);
3209
3215
  const topCommentRef = useRef(null);
3210
3216
 
@@ -3218,6 +3224,16 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
3218
3224
  .catch(() => setUpdates([]));
3219
3225
  }, [item && item.id]);
3220
3226
 
3227
+ const refreshAttachments = useCallback(() => {
3228
+ if (!item) return;
3229
+ api('GET', '/api/board/items/' + item.id + '/attachments')
3230
+ .then(data => {
3231
+ const list = Array.isArray(data) ? data : (data.attachments || data.data || []);
3232
+ setAttachments(list);
3233
+ })
3234
+ .catch(() => setAttachments([]));
3235
+ }, [item && item.id]);
3236
+
3221
3237
  useEffect(() => {
3222
3238
  if (!item) return;
3223
3239
  setLoadingUpdates(true);
@@ -3228,7 +3244,18 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
3228
3244
  })
3229
3245
  .catch(() => setUpdates([]))
3230
3246
  .finally(() => setLoadingUpdates(false));
3231
- const interval = setInterval(refreshUpdates, 10000);
3247
+ setLoadingAttachments(true);
3248
+ api('GET', '/api/board/items/' + item.id + '/attachments')
3249
+ .then(data => {
3250
+ const list = Array.isArray(data) ? data : (data.attachments || data.data || []);
3251
+ setAttachments(list);
3252
+ })
3253
+ .catch(() => setAttachments([]))
3254
+ .finally(() => setLoadingAttachments(false));
3255
+ const interval = setInterval(() => {
3256
+ refreshUpdates();
3257
+ refreshAttachments();
3258
+ }, 10000);
3232
3259
  return () => clearInterval(interval);
3233
3260
  }, [item && item.id]);
3234
3261
 
@@ -3357,6 +3384,41 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
3357
3384
  <label>Parent Epic</label>
3358
3385
  <div class="detail-value">${item.parent_epic ? '#' + item.parent_epic : '\u2014'}</div>
3359
3386
  </div>
3387
+ ${(loadingAttachments || attachments.length > 0) && html`
3388
+ <div style="padding:12px 24px 0;">
3389
+ <h4 style="margin:0 0 8px 0;font-size:13px;font-weight:600;">
3390
+ Attachments${attachments.length > 0 ? ' (' + attachments.length + ')' : ''}
3391
+ </h4>
3392
+ ${loadingAttachments ? html`<div class="loading-text">Loading attachments...</div>` : html`
3393
+ <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;">
3394
+ ${attachments.map((att) => {
3395
+ const isImage = (att.content_type || '').startsWith('image/');
3396
+ const proj = localStorage.getItem('beastmode-selected-project') || '';
3397
+ const sep = '?';
3398
+ const boardQuery = (proj && proj !== 'all') ? sep + 'board=' + encodeURIComponent(proj) : '';
3399
+ const downloadUrl = '/api/board/attachments/' + att.id + '/download' + boardQuery;
3400
+ const sizeLabel = att.file_size > 1024 * 1024
3401
+ ? (att.file_size / (1024 * 1024)).toFixed(1) + ' MB'
3402
+ : Math.round(att.file_size / 1024) + ' KB';
3403
+ if (isImage) {
3404
+ return html`
3405
+ <a key=${att.id} href=${downloadUrl} target="_blank" rel="noopener" title=${att.original_name + ' (' + sizeLabel + ')'} style="display:block;border:1px solid var(--border);border-radius:4px;overflow:hidden;background:var(--bg-input);">
3406
+ <img src=${downloadUrl} alt=${att.original_name} style="display:block;width:100%;height:80px;object-fit:cover;background:var(--bg-input);" />
3407
+ <div style="padding:4px 6px;font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${att.original_name}</div>
3408
+ </a>
3409
+ `;
3410
+ }
3411
+ return html`
3412
+ <a key=${att.id} href=${downloadUrl} target="_blank" rel="noopener" title=${att.original_name + ' (' + sizeLabel + ')'} style="display:block;border:1px solid var(--border);border-radius:4px;padding:8px;background:var(--bg-input);text-decoration:none;color:inherit;">
3413
+ <div style="font-size:11px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">📎 ${att.original_name}</div>
3414
+ <div style="font-size:10px;color:var(--text-muted);margin-top:2px;">${sizeLabel}</div>
3415
+ </a>
3416
+ `;
3417
+ })}
3418
+ </div>
3419
+ `}
3420
+ </div>
3421
+ `}
3360
3422
  <div class="detail-updates">
3361
3423
  <${UpdatesHeader} count=${updates.length} sortNewest=${sortNewest} onToggle=${() => setSortNewest(!sortNewest)} itemId=${item.id} onPosted=${refreshUpdates} />
3362
3424
  ${loadingUpdates ? html`<div class="loading-text">Loading updates...</div>` :
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beastmode-develeap/beastmode",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "BeastMode Dark Factory — turn intent into verified software",
5
5
  "type": "module",
6
6
  "bin": {