total-recall-brain 3.12.1 → 3.13.0

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.
@@ -0,0 +1,614 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { fetchDesignDocs, fetchDesignDocContent } from '../api';
3
+
4
+ // ─── Types ──────────────────────────────────────────────────────────────────────
5
+
6
+ interface DocItem {
7
+ name: string;
8
+ path: string;
9
+ icon: string;
10
+ category: string;
11
+ }
12
+
13
+ interface KanbanCard {
14
+ id: string;
15
+ name: string;
16
+ emoji: string;
17
+ }
18
+
19
+ interface KanbanColumn {
20
+ id: string;
21
+ title: string;
22
+ accent: string;
23
+ cards: KanbanCard[];
24
+ count?: number;
25
+ collapsible?: boolean;
26
+ }
27
+
28
+ // ─── Static Data ────────────────────────────────────────────────────────────────
29
+
30
+ const CORE_DOCS: DocItem[] = [
31
+ { name: 'ARCHITECTURE.md', path: 'docs/ARCHITECTURE.md', icon: '🏗️', category: 'Core Docs' },
32
+ { name: 'SSSS.md', path: 'docs/SSSS.md', icon: '🔐', category: 'Core Docs' },
33
+ { name: 'IDE_INTEGRATION.md', path: 'docs/IDE_INTEGRATION.md', icon: '🔌', category: 'Core Docs' },
34
+ { name: 'CHANGELOG.md', path: 'docs/CHANGELOG.md', icon: '📋', category: 'Core Docs' },
35
+ ];
36
+
37
+ const DEV_GUIDES: DocItem[] = [
38
+ { name: 'README.md', path: 'README.md', icon: '📖', category: 'Developer Guides' },
39
+ ];
40
+
41
+ const KANBAN_COLUMNS: KanbanColumn[] = [
42
+ {
43
+ id: 'in-progress',
44
+ title: 'In Progress',
45
+ accent: '#6c5ce7',
46
+ cards: [
47
+ { id: 'auto-update', name: 'Auto Update Feature', emoji: '🔄' },
48
+ { id: 'compaction-cache', name: 'Compaction Cache Bypass', emoji: '⚡' },
49
+ { id: 'sovereign-policy', name: 'Sovereign Policy Enforcement', emoji: '🛡️' },
50
+ ],
51
+ },
52
+ {
53
+ id: 'planned',
54
+ title: 'Planned',
55
+ accent: '#74b9ff',
56
+ cards: [
57
+ { id: 'system-resilience', name: 'System Resilience', emoji: '🧱' },
58
+ { id: 'gpu-intelligence', name: 'GPU Intelligence Network', emoji: '🧠' },
59
+ { id: 'living-memory', name: 'Living Memory Capsule (UltraChat)', emoji: '💊' },
60
+ { id: 'expo-mobile', name: 'Expo Mobile (stub)', emoji: '📱' },
61
+ ],
62
+ },
63
+ {
64
+ id: 'completed',
65
+ title: 'Completed',
66
+ accent: '#00cec9',
67
+ count: 35,
68
+ collapsible: true,
69
+ cards: [
70
+ { id: 'okf-integration', name: 'OKF Integration', emoji: '✅' },
71
+ { id: 'byom-architecture', name: 'BYOM Architecture', emoji: '✅' },
72
+ { id: 'cli-performance', name: 'CLI Performance Optimization', emoji: '✅' },
73
+ { id: 'ssss-sovereign', name: 'SSSS Sovereign AI OS', emoji: '✅' },
74
+ { id: 'embedding-pipeline', name: 'Embedding Pipeline v2', emoji: '✅' },
75
+ { id: 'surface-compiler', name: 'Surface Compiler', emoji: '✅' },
76
+ ],
77
+ },
78
+ {
79
+ id: 'archived',
80
+ title: 'Archived',
81
+ accent: '#636e72',
82
+ count: 3,
83
+ collapsible: true,
84
+ cards: [
85
+ { id: 'legacy-sync', name: 'Legacy Sync Protocol', emoji: '📦' },
86
+ { id: 'deprecated-cli', name: 'Deprecated CLI v1', emoji: '📦' },
87
+ { id: 'old-auth', name: 'Old Auth Flow', emoji: '📦' },
88
+ ],
89
+ },
90
+ ];
91
+
92
+ // ─── Simple Markdown Renderer ───────────────────────────────────────────────────
93
+
94
+ function renderMarkdown(md: string): JSX.Element {
95
+ if (!md) return <></>;
96
+
97
+ let html = md
98
+ .replace(/&/g, '&amp;')
99
+ .replace(/</g, '&lt;')
100
+ .replace(/>/g, '&gt;');
101
+
102
+ // Tables (basic pipe tables) — process before other inline transforms
103
+ html = html.replace(
104
+ /^(\|.+\|)\n(\|[\s:|-]+\|)\n((?:\|.+\|\n?)+)/gm,
105
+ (_match, headerRow: string, _separator: string, bodyRows: string) => {
106
+ const headers = headerRow.split('|').filter((c: string) => c.trim());
107
+ const rows = bodyRows.trim().split('\n').map((r: string) =>
108
+ r.split('|').filter((c: string) => c.trim())
109
+ );
110
+ const thCells = headers.map((h: string) =>
111
+ `<th style="padding:8px 12px;text-align:left;border-bottom:2px solid var(--border-hover);font-weight:600;">${h.trim()}</th>`
112
+ ).join('');
113
+ const tbodyRows = rows.map((row: string[]) => {
114
+ const cells = row.map((c: string) =>
115
+ `<td style="padding:8px 12px;border-bottom:1px solid var(--border);">${c.trim()}</td>`
116
+ ).join('');
117
+ return `<tr>${cells}</tr>`;
118
+ }).join('');
119
+ return `<table style="width:100%;border-collapse:collapse;margin:16px 0;font-size:13px;"><thead><tr>${thCells}</tr></thead><tbody>${tbodyRows}</tbody></table>`;
120
+ }
121
+ );
122
+
123
+ // Headings
124
+ html = html.replace(/^#### (.*$)/gm, '<h4 style="margin:20px 0 8px;font-size:14px;color:var(--text-primary);">$1</h4>');
125
+ html = html.replace(/^### (.*$)/gm, '<h3 style="margin:24px 0 10px;font-size:16px;color:var(--text-primary);">$1</h3>');
126
+ html = html.replace(/^## (.*$)/gm, '<h2 style="margin:28px 0 12px;font-size:19px;color:var(--text-primary);border-bottom:1px solid var(--border);padding-bottom:8px;">$1</h2>');
127
+ html = html.replace(/^# (.*$)/gm, '<h1 style="margin:0 0 16px;font-size:24px;color:var(--text-primary);">$1</h1>');
128
+
129
+ // Code blocks (fenced)
130
+ html = html.replace(
131
+ /```[\w]*\n([\s\S]*?)```/g,
132
+ '<pre style="background:rgba(0,0,0,0.35);padding:14px 16px;border-radius:var(--radius-sm);border:1px solid var(--border);font-family:var(--font-mono);font-size:12.5px;overflow-x:auto;margin:12px 0;line-height:1.5;"><code>$1</code></pre>'
133
+ );
134
+
135
+ // Bold & Italic
136
+ html = html.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
137
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong style="color:var(--text-primary);">$1</strong>');
138
+ html = html.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
139
+
140
+ // Inline code
141
+ html = html.replace(
142
+ /`([^`]+)`/g,
143
+ '<code style="background:rgba(108,92,231,0.12);color:#b8b0f0;padding:2px 6px;border-radius:4px;font-family:var(--font-mono);font-size:12px;">$1</code>'
144
+ );
145
+
146
+ // Blockquotes
147
+ html = html.replace(
148
+ /^&gt; (.*$)/gm,
149
+ '<blockquote style="border-left:3px solid var(--accent);padding:8px 14px;color:var(--text-secondary);margin:12px 0;background:rgba(108,92,231,0.05);border-radius:0 var(--radius-sm) var(--radius-sm) 0;">$1</blockquote>'
150
+ );
151
+
152
+ // Unordered lists
153
+ html = html.replace(
154
+ /^- (.*$)/gm,
155
+ '<li style="margin-left:20px;list-style-type:disc;margin-bottom:4px;">$1</li>'
156
+ );
157
+
158
+ // Ordered lists
159
+ html = html.replace(
160
+ /^\d+\. (.*$)/gm,
161
+ '<li style="margin-left:20px;list-style-type:decimal;margin-bottom:4px;">$1</li>'
162
+ );
163
+
164
+ // Line breaks
165
+ html = html.replace(/\n/g, '<br />');
166
+
167
+ return <div dangerouslySetInnerHTML={{ __html: html }} className="markdown-preview-block" />;
168
+ }
169
+
170
+ // ─── Component ──────────────────────────────────────────────────────────────────
171
+
172
+ export default function DesignDocsPage() {
173
+ const [activeTab, setActiveTab] = useState<'architecture' | 'board'>('architecture');
174
+
175
+ // Architecture docs state
176
+ const [selectedDoc, setSelectedDoc] = useState<DocItem>(CORE_DOCS[0]);
177
+ const [docContent, setDocContent] = useState<string>('');
178
+ const [docLoading, setDocLoading] = useState(false);
179
+ const [docError, setDocError] = useState<string | null>(null);
180
+ const [, setDocsFromApi] = useState<Array<{ name: string; path: string; size: number; modified: string; category: string }>>([]);
181
+
182
+ // Kanban state
183
+ const [expandedColumns, setExpandedColumns] = useState<Record<string, boolean>>({});
184
+
185
+ // Load docs list on mount
186
+ useEffect(() => {
187
+ async function loadDocsList() {
188
+ try {
189
+ const data = await fetchDesignDocs();
190
+ setDocsFromApi(data);
191
+ } catch {
192
+ // Silently fall back to static doc list
193
+ }
194
+ }
195
+ loadDocsList();
196
+ }, []);
197
+
198
+ // Load selected doc content
199
+ useEffect(() => {
200
+ if (activeTab !== 'architecture' || !selectedDoc) return;
201
+ let cancelled = false;
202
+
203
+ async function loadContent() {
204
+ setDocLoading(true);
205
+ setDocError(null);
206
+ try {
207
+ const data = await fetchDesignDocContent(selectedDoc.path);
208
+ if (!cancelled) setDocContent(data.content);
209
+ } catch {
210
+ if (!cancelled) {
211
+ setDocError(selectedDoc.path);
212
+ setDocContent('');
213
+ }
214
+ } finally {
215
+ if (!cancelled) setDocLoading(false);
216
+ }
217
+ }
218
+ loadContent();
219
+ return () => { cancelled = true; };
220
+ }, [selectedDoc, activeTab]);
221
+
222
+ const toggleColumn = (colId: string) => {
223
+ setExpandedColumns(prev => ({ ...prev, [colId]: !prev[colId] }));
224
+ };
225
+
226
+ // ─── Sidebar Section Renderer ───────────────────────────────────────────────
227
+
228
+ const renderSidebarSection = (title: string, items: DocItem[]) => (
229
+ <div style={{ marginBottom: 20 }}>
230
+ <div
231
+ style={{
232
+ fontSize: 10,
233
+ fontWeight: 700,
234
+ textTransform: 'uppercase',
235
+ letterSpacing: '0.08em',
236
+ color: 'var(--text-tertiary)',
237
+ padding: '0 12px',
238
+ marginBottom: 8,
239
+ }}
240
+ >
241
+ {title}
242
+ </div>
243
+ {items.map(item => {
244
+ const isActive = selectedDoc.path === item.path;
245
+ return (
246
+ <button
247
+ id={`doc-nav-${item.name.replace(/\./g, '-').toLowerCase()}`}
248
+ key={item.path}
249
+ onClick={() => setSelectedDoc(item)}
250
+ style={{
251
+ display: 'flex',
252
+ alignItems: 'center',
253
+ gap: 10,
254
+ width: '100%',
255
+ textAlign: 'left',
256
+ padding: '9px 12px',
257
+ borderRadius: 'var(--radius-sm)',
258
+ background: isActive ? 'var(--accent-muted)' : 'transparent',
259
+ color: isActive ? 'var(--accent-hover)' : 'var(--text-secondary)',
260
+ border: 'none',
261
+ cursor: 'pointer',
262
+ transition: 'var(--transition-normal)',
263
+ fontSize: 13,
264
+ fontWeight: isActive ? 600 : 400,
265
+ }}
266
+ >
267
+ <span style={{ fontSize: 15, flexShrink: 0 }}>{item.icon}</span>
268
+ <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
269
+ {item.name}
270
+ </span>
271
+ </button>
272
+ );
273
+ })}
274
+ </div>
275
+ );
276
+
277
+ // ─── Architecture Tab ───────────────────────────────────────────────────────
278
+
279
+ const renderArchitectureTab = () => (
280
+ <div
281
+ id="architecture-panel"
282
+ style={{ display: 'flex', gap: 24, minHeight: '65vh' }}
283
+ >
284
+ {/* Sidebar */}
285
+ <div
286
+ id="docs-sidebar"
287
+ style={{
288
+ width: 220,
289
+ flexShrink: 0,
290
+ background: 'var(--bg-secondary)',
291
+ border: '1px solid var(--border)',
292
+ borderRadius: 'var(--radius-md)',
293
+ padding: '16px 8px',
294
+ overflowY: 'auto',
295
+ }}
296
+ >
297
+ {renderSidebarSection('Core Docs', CORE_DOCS)}
298
+ {renderSidebarSection('Developer Guides', DEV_GUIDES)}
299
+ </div>
300
+
301
+ {/* Main Viewer */}
302
+ <div
303
+ id="docs-viewer"
304
+ style={{
305
+ flex: 1,
306
+ background: 'var(--bg-secondary)',
307
+ border: '1px solid var(--border)',
308
+ borderRadius: 'var(--radius-md)',
309
+ padding: '24px 32px',
310
+ overflowY: 'auto',
311
+ maxHeight: '75vh',
312
+ }}
313
+ >
314
+ {/* Breadcrumb */}
315
+ <div
316
+ id="docs-breadcrumb"
317
+ style={{
318
+ display: 'flex',
319
+ alignItems: 'center',
320
+ gap: 6,
321
+ fontSize: 12,
322
+ color: 'var(--text-tertiary)',
323
+ marginBottom: 20,
324
+ fontFamily: 'var(--font-mono)',
325
+ }}
326
+ >
327
+ <span>Total Recall</span>
328
+ <span style={{ color: 'var(--text-tertiary)' }}>›</span>
329
+ <span>docs</span>
330
+ <span style={{ color: 'var(--text-tertiary)' }}>›</span>
331
+ <span style={{ color: 'var(--accent)' }}>{selectedDoc.name}</span>
332
+ </div>
333
+
334
+ {/* Content */}
335
+ {docLoading ? (
336
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
337
+ {[200, 120, 280, 80, 160].map((w, i) => (
338
+ <div
339
+ key={i}
340
+ className="skeleton"
341
+ style={{ height: 16, width: `${w}px`, maxWidth: '100%', borderRadius: 4 }}
342
+ />
343
+ ))}
344
+ </div>
345
+ ) : docError ? (
346
+ <div
347
+ id="doc-fallback-message"
348
+ style={{
349
+ background: 'var(--warning-muted)',
350
+ border: '1px solid rgba(253, 203, 110, 0.2)',
351
+ borderRadius: 'var(--radius-sm)',
352
+ padding: '20px 24px',
353
+ color: 'var(--text-secondary)',
354
+ lineHeight: 1.7,
355
+ }}
356
+ >
357
+ <div style={{ marginBottom: 8, color: 'var(--warning)', fontWeight: 600 }}>
358
+ 📄 Document not available via API
359
+ </div>
360
+ <div>
361
+ This document is available in your project at{' '}
362
+ <code
363
+ style={{
364
+ background: 'rgba(108,92,231,0.12)',
365
+ color: '#b8b0f0',
366
+ padding: '2px 6px',
367
+ borderRadius: 4,
368
+ fontFamily: 'var(--font-mono)',
369
+ fontSize: 12,
370
+ }}
371
+ >
372
+ {docError}
373
+ </code>
374
+ . Open it in your editor to view.
375
+ </div>
376
+ </div>
377
+ ) : (
378
+ <div style={{ color: 'var(--text-primary)', lineHeight: 1.75, fontSize: 14 }}>
379
+ {renderMarkdown(docContent)}
380
+ </div>
381
+ )}
382
+ </div>
383
+ </div>
384
+ );
385
+
386
+ // ─── Kanban Board Tab ─────────────────────────────────────────────────────────
387
+
388
+ const renderKanbanCard = (card: KanbanCard) => (
389
+ <div
390
+ id={`kanban-card-${card.id}`}
391
+ key={card.id}
392
+ style={{
393
+ background: 'var(--bg-tertiary)',
394
+ border: '1px solid var(--border)',
395
+ borderRadius: 'var(--radius-sm)',
396
+ padding: '12px 14px',
397
+ cursor: 'default',
398
+ transition: 'var(--transition-normal)',
399
+ display: 'flex',
400
+ alignItems: 'center',
401
+ gap: 10,
402
+ }}
403
+ onMouseEnter={e => {
404
+ (e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)';
405
+ (e.currentTarget as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
406
+ (e.currentTarget as HTMLElement).style.borderColor = 'var(--border-hover)';
407
+ }}
408
+ onMouseLeave={e => {
409
+ (e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
410
+ (e.currentTarget as HTMLElement).style.boxShadow = 'none';
411
+ (e.currentTarget as HTMLElement).style.borderColor = 'var(--border)';
412
+ }}
413
+ >
414
+ <span style={{ fontSize: 16, flexShrink: 0 }}>{card.emoji}</span>
415
+ <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>
416
+ {card.name}
417
+ </span>
418
+ </div>
419
+ );
420
+
421
+ const renderKanbanColumn = (col: KanbanColumn) => {
422
+ const isExpanded = expandedColumns[col.id] ?? !col.collapsible;
423
+ const visibleCards = isExpanded ? col.cards : [];
424
+
425
+ return (
426
+ <div
427
+ id={`kanban-col-${col.id}`}
428
+ key={col.id}
429
+ style={{
430
+ minWidth: 260,
431
+ flex: 1,
432
+ maxWidth: 320,
433
+ background: 'var(--bg-secondary)',
434
+ border: '1px solid var(--border)',
435
+ borderRadius: 'var(--radius-md)',
436
+ borderTop: `4px solid ${col.accent}`,
437
+ display: 'flex',
438
+ flexDirection: 'column',
439
+ overflow: 'hidden',
440
+ }}
441
+ >
442
+ {/* Column Header */}
443
+ <div
444
+ style={{
445
+ padding: '14px 16px 10px',
446
+ display: 'flex',
447
+ alignItems: 'center',
448
+ justifyContent: 'space-between',
449
+ }}
450
+ >
451
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
452
+ <span style={{ fontWeight: 600, fontSize: 14, color: 'var(--text-primary)' }}>
453
+ {col.title}
454
+ </span>
455
+ <span
456
+ className="badge"
457
+ style={{
458
+ background: `${col.accent}22`,
459
+ color: col.accent,
460
+ fontSize: 11,
461
+ padding: '2px 8px',
462
+ borderRadius: 10,
463
+ fontWeight: 600,
464
+ }}
465
+ >
466
+ {col.count ?? col.cards.length}
467
+ </span>
468
+ </div>
469
+ {col.collapsible && (
470
+ <button
471
+ id={`kanban-toggle-${col.id}`}
472
+ className="btn btn-ghost"
473
+ onClick={() => toggleColumn(col.id)}
474
+ style={{
475
+ fontSize: 12,
476
+ padding: '4px 10px',
477
+ color: 'var(--text-secondary)',
478
+ background: 'transparent',
479
+ borderRadius: 'var(--radius-sm)',
480
+ }}
481
+ >
482
+ {isExpanded ? 'Collapse' : 'Expand'}
483
+ </button>
484
+ )}
485
+ </div>
486
+
487
+ {/* Cards */}
488
+ <div
489
+ style={{
490
+ padding: '0 12px 14px',
491
+ display: 'flex',
492
+ flexDirection: 'column',
493
+ gap: 8,
494
+ flex: 1,
495
+ }}
496
+ >
497
+ {col.collapsible && !isExpanded ? (
498
+ <div
499
+ style={{
500
+ color: 'var(--text-tertiary)',
501
+ fontSize: 12,
502
+ textAlign: 'center',
503
+ padding: '12px 0',
504
+ cursor: 'pointer',
505
+ }}
506
+ onClick={() => toggleColumn(col.id)}
507
+ >
508
+ {col.count ?? col.cards.length} projects — click to expand
509
+ </div>
510
+ ) : (
511
+ visibleCards.map(card => renderKanbanCard(card))
512
+ )}
513
+ </div>
514
+ </div>
515
+ );
516
+ };
517
+
518
+ const renderBoardTab = () => (
519
+ <div id="board-panel">
520
+ <div
521
+ id="kanban-board"
522
+ style={{
523
+ display: 'flex',
524
+ gap: 16,
525
+ overflowX: 'auto',
526
+ paddingBottom: 16,
527
+ }}
528
+ >
529
+ {KANBAN_COLUMNS.map(col => renderKanbanColumn(col))}
530
+ </div>
531
+ </div>
532
+ );
533
+
534
+ // ─── Main Render ──────────────────────────────────────────────────────────────
535
+
536
+ const tabs: { key: typeof activeTab; label: string; icon: string }[] = [
537
+ { key: 'architecture', label: 'Architecture', icon: '📐' },
538
+ { key: 'board', label: 'Project Board', icon: '📋' },
539
+ ];
540
+
541
+ return (
542
+ <div className="page" style={{ padding: '24px 28px', maxWidth: 1200, margin: '0 auto' }}>
543
+ {/* Page Header */}
544
+ <div className="page-header" style={{ marginBottom: 24 }}>
545
+ <h1 style={{ fontSize: 22, fontWeight: 700 }}>Design Docs & Projects</h1>
546
+ <p style={{ color: 'var(--text-secondary)', fontSize: 13, marginTop: 4 }}>
547
+ Architecture documentation, specifications, and project tracking board.
548
+ </p>
549
+ </div>
550
+
551
+ {/* Tab Navigation */}
552
+ <div
553
+ id="design-docs-tabs"
554
+ style={{
555
+ display: 'inline-flex',
556
+ gap: 0,
557
+ marginBottom: 24,
558
+ borderBottom: '1px solid var(--border)',
559
+ }}
560
+ >
561
+ {tabs.map(tab => {
562
+ const isActive = activeTab === tab.key;
563
+ return (
564
+ <button
565
+ id={`tab-${tab.key}`}
566
+ key={tab.key}
567
+ onClick={() => setActiveTab(tab.key)}
568
+ style={{
569
+ display: 'flex',
570
+ alignItems: 'center',
571
+ gap: 7,
572
+ padding: '10px 20px',
573
+ background: 'transparent',
574
+ color: isActive ? 'var(--accent)' : 'var(--text-secondary)',
575
+ fontSize: 14,
576
+ fontWeight: isActive ? 600 : 400,
577
+ borderBottom: isActive ? '2px solid var(--accent)' : '2px solid transparent',
578
+ transition: 'var(--transition-normal)',
579
+ cursor: 'pointer',
580
+ marginBottom: -1,
581
+ }}
582
+ >
583
+ <span>{tab.icon}</span>
584
+ {tab.label}
585
+ </button>
586
+ );
587
+ })}
588
+ </div>
589
+
590
+ {/* Tab Content */}
591
+ {activeTab === 'architecture' ? renderArchitectureTab() : renderBoardTab()}
592
+
593
+ {/* ─── Responsive Styles ─────────────────────────────────────────────────── */}
594
+ <style>{`
595
+ @media (max-width: 768px) {
596
+ #architecture-panel {
597
+ flex-direction: column !important;
598
+ }
599
+ #docs-sidebar {
600
+ width: 100% !important;
601
+ max-height: 200px;
602
+ }
603
+ #kanban-board {
604
+ flex-direction: column !important;
605
+ }
606
+ #kanban-board > div {
607
+ max-width: 100% !important;
608
+ min-width: 100% !important;
609
+ }
610
+ }
611
+ `}</style>
612
+ </div>
613
+ );
614
+ }