htmlgraph 0.24.2__py3-none-any.whl → 0.26.1__py3-none-any.whl

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 (112) hide show
  1. htmlgraph/__init__.py +20 -1
  2. htmlgraph/agent_detection.py +26 -10
  3. htmlgraph/analytics/cross_session.py +4 -3
  4. htmlgraph/analytics/work_type.py +52 -16
  5. htmlgraph/analytics_index.py +51 -19
  6. htmlgraph/api/__init__.py +3 -0
  7. htmlgraph/api/main.py +2263 -0
  8. htmlgraph/api/static/htmx.min.js +1 -0
  9. htmlgraph/api/static/style-redesign.css +1344 -0
  10. htmlgraph/api/static/style.css +1079 -0
  11. htmlgraph/api/templates/dashboard-redesign.html +812 -0
  12. htmlgraph/api/templates/dashboard.html +794 -0
  13. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  14. htmlgraph/api/templates/partials/activity-feed.html +1020 -0
  15. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  16. htmlgraph/api/templates/partials/agents.html +317 -0
  17. htmlgraph/api/templates/partials/event-traces.html +373 -0
  18. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  19. htmlgraph/api/templates/partials/features.html +509 -0
  20. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  21. htmlgraph/api/templates/partials/metrics.html +346 -0
  22. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  23. htmlgraph/api/templates/partials/orchestration.html +163 -0
  24. htmlgraph/api/templates/partials/spawners.html +375 -0
  25. htmlgraph/atomic_ops.py +560 -0
  26. htmlgraph/builders/base.py +55 -1
  27. htmlgraph/builders/bug.py +17 -2
  28. htmlgraph/builders/chore.py +17 -2
  29. htmlgraph/builders/epic.py +17 -2
  30. htmlgraph/builders/feature.py +25 -2
  31. htmlgraph/builders/phase.py +17 -2
  32. htmlgraph/builders/spike.py +27 -2
  33. htmlgraph/builders/track.py +14 -0
  34. htmlgraph/cigs/__init__.py +4 -0
  35. htmlgraph/cigs/reporter.py +818 -0
  36. htmlgraph/cli.py +1427 -401
  37. htmlgraph/cli_commands/__init__.py +1 -0
  38. htmlgraph/cli_commands/feature.py +195 -0
  39. htmlgraph/cli_framework.py +115 -0
  40. htmlgraph/collections/__init__.py +2 -0
  41. htmlgraph/collections/base.py +21 -0
  42. htmlgraph/collections/session.py +189 -0
  43. htmlgraph/collections/spike.py +7 -1
  44. htmlgraph/collections/task_delegation.py +236 -0
  45. htmlgraph/collections/traces.py +482 -0
  46. htmlgraph/config.py +113 -0
  47. htmlgraph/converter.py +41 -0
  48. htmlgraph/cost_analysis/__init__.py +5 -0
  49. htmlgraph/cost_analysis/analyzer.py +438 -0
  50. htmlgraph/dashboard.html +3356 -492
  51. htmlgraph-0.24.2.data/data/htmlgraph/dashboard.html → htmlgraph/dashboard.html.backup +2246 -248
  52. htmlgraph/dashboard.html.bak +7181 -0
  53. htmlgraph/dashboard.html.bak2 +7231 -0
  54. htmlgraph/dashboard.html.bak3 +7232 -0
  55. htmlgraph/db/__init__.py +38 -0
  56. htmlgraph/db/queries.py +790 -0
  57. htmlgraph/db/schema.py +1584 -0
  58. htmlgraph/deploy.py +26 -27
  59. htmlgraph/docs/API_REFERENCE.md +841 -0
  60. htmlgraph/docs/HTTP_API.md +750 -0
  61. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  62. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +710 -0
  63. htmlgraph/docs/README.md +533 -0
  64. htmlgraph/docs/version_check.py +3 -1
  65. htmlgraph/error_handler.py +544 -0
  66. htmlgraph/event_log.py +2 -0
  67. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  68. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  69. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  70. htmlgraph/hooks/__init__.py +8 -0
  71. htmlgraph/hooks/bootstrap.py +169 -0
  72. htmlgraph/hooks/cigs_pretool_enforcer.py +2 -2
  73. htmlgraph/hooks/concurrent_sessions.py +208 -0
  74. htmlgraph/hooks/context.py +318 -0
  75. htmlgraph/hooks/drift_handler.py +525 -0
  76. htmlgraph/hooks/event_tracker.py +496 -79
  77. htmlgraph/hooks/orchestrator.py +6 -4
  78. htmlgraph/hooks/orchestrator_reflector.py +4 -4
  79. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  80. htmlgraph/hooks/pretooluse.py +473 -6
  81. htmlgraph/hooks/prompt_analyzer.py +637 -0
  82. htmlgraph/hooks/session_handler.py +637 -0
  83. htmlgraph/hooks/state_manager.py +504 -0
  84. htmlgraph/hooks/subagent_stop.py +309 -0
  85. htmlgraph/hooks/task_enforcer.py +39 -0
  86. htmlgraph/hooks/validator.py +15 -11
  87. htmlgraph/models.py +111 -15
  88. htmlgraph/operations/fastapi_server.py +230 -0
  89. htmlgraph/orchestration/headless_spawner.py +344 -29
  90. htmlgraph/orchestration/live_events.py +377 -0
  91. htmlgraph/pydantic_models.py +476 -0
  92. htmlgraph/quality_gates.py +350 -0
  93. htmlgraph/repo_hash.py +511 -0
  94. htmlgraph/sdk.py +348 -10
  95. htmlgraph/server.py +194 -0
  96. htmlgraph/session_hooks.py +300 -0
  97. htmlgraph/session_manager.py +131 -1
  98. htmlgraph/session_registry.py +587 -0
  99. htmlgraph/session_state.py +436 -0
  100. htmlgraph/system_prompts.py +449 -0
  101. htmlgraph/templates/orchestration-view.html +350 -0
  102. htmlgraph/track_builder.py +19 -0
  103. htmlgraph/validation.py +115 -0
  104. htmlgraph-0.26.1.data/data/htmlgraph/dashboard.html +7458 -0
  105. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/METADATA +91 -64
  106. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/RECORD +112 -46
  107. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/styles.css +0 -0
  108. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  109. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  110. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  111. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/WHEEL +0 -0
  112. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,509 @@
1
+ <div class="view-container features-view">
2
+ <div class="view-header">
3
+ <h2>Feature Tracker - Smart Kanban</h2>
4
+ <div class="view-description">
5
+ Drag cards between visible columns. Columns auto-collapse when others are opened.
6
+ </div>
7
+ <div class="view-filters">
8
+ <div class="filter-group">
9
+ <label>Quick Filter:</label>
10
+ <select class="filter-select"
11
+ hx-get="/views/features"
12
+ hx-target="#content-area"
13
+ hx-trigger="change"
14
+ name="status">
15
+ <option value="all">All Status</option>
16
+ <option value="todo">To Do Only</option>
17
+ <option value="in_progress">In Progress Only</option>
18
+ <option value="blocked">Blocked Only</option>
19
+ <option value="done">Done Only</option>
20
+ </select>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="kanban-board">
26
+ <!-- TO DO Column -->
27
+ <div class="kanban-column todo-column" data-status="todo">
28
+ <div class="column-header">
29
+ <div>
30
+ <h3><span class="column-collapse-indicator">▼</span> To Do</h3>
31
+ </div>
32
+ <span class="column-count">{{ features_by_status.todo|length }}</span>
33
+ </div>
34
+ <div class="column-cards">
35
+ {% for feature in features_by_status.todo %}
36
+ <div class="feature-card todo-card" draggable="true" data-feature-id="{{ feature.id }}">
37
+ <div class="card-header">
38
+ <span class="feature-type-badge type-{{ feature.type }}">{{ feature.type }}</span>
39
+ <span class="priority-badge priority-{{ feature.priority }}">{{ feature.priority }}</span>
40
+ </div>
41
+ <h4 class="card-title">{{ feature.title }}</h4>
42
+ {% if feature.description %}
43
+ <p class="card-description">{{ feature.description[:100] }}{% if feature.description|length > 100 %}...{% endif %}</p>
44
+ {% endif %}
45
+ <div class="card-footer">
46
+ <span class="feature-id" title="{{ feature.id }}">{{ feature.id[:8] }}</span>
47
+ {% if feature.assigned_to %}
48
+ <span class="assigned-to">👤 {{ feature.assigned_to }}</span>
49
+ {% endif %}
50
+ </div>
51
+ </div>
52
+ {% endfor %}
53
+ {% if not features_by_status.todo %}
54
+ <div class="empty-column">No tasks</div>
55
+ {% endif %}
56
+ </div>
57
+ </div>
58
+
59
+ <!-- IN PROGRESS Column -->
60
+ <div class="kanban-column in-progress-column" data-status="in_progress">
61
+ <div class="column-header">
62
+ <div>
63
+ <h3><span class="column-collapse-indicator">▼</span> In Progress</h3>
64
+ </div>
65
+ <span class="column-count">{{ features_by_status.in_progress|length }}</span>
66
+ </div>
67
+ <div class="column-cards">
68
+ {% for feature in features_by_status.in_progress %}
69
+ <div class="feature-card in-progress-card" draggable="true" data-feature-id="{{ feature.id }}">
70
+ <div class="card-header">
71
+ <span class="feature-type-badge type-{{ feature.type }}">{{ feature.type }}</span>
72
+ <span class="priority-badge priority-{{ feature.priority }}">{{ feature.priority }}</span>
73
+ </div>
74
+ <h4 class="card-title">{{ feature.title }}</h4>
75
+ {% if feature.description %}
76
+ <p class="card-description">{{ feature.description[:100] }}{% if feature.description|length > 100 %}...{% endif %}</p>
77
+ {% endif %}
78
+ <div class="card-footer">
79
+ <span class="feature-id" title="{{ feature.id }}">{{ feature.id[:8] }}</span>
80
+ {% if feature.assigned_to %}
81
+ <span class="assigned-to">👤 {{ feature.assigned_to }}</span>
82
+ {% endif %}
83
+ </div>
84
+ </div>
85
+ {% endfor %}
86
+ {% if not features_by_status.in_progress %}
87
+ <div class="empty-column">No tasks</div>
88
+ {% endif %}
89
+ </div>
90
+ </div>
91
+
92
+ <!-- BLOCKED Column (Collapsed by default) -->
93
+ <div class="kanban-column blocked-column collapsed" data-status="blocked">
94
+ <div class="column-header">
95
+ <div>
96
+ <h3><span class="column-collapse-indicator">▶</span> Blocked</h3>
97
+ </div>
98
+ <span class="column-count">{{ features_by_status.blocked|length }}</span>
99
+ </div>
100
+ <div class="column-cards">
101
+ {% for feature in features_by_status.blocked %}
102
+ <div class="feature-card blocked-card" draggable="true" data-feature-id="{{ feature.id }}">
103
+ <div class="card-header">
104
+ <span class="feature-type-badge type-{{ feature.type }}">{{ feature.type }}</span>
105
+ <span class="priority-badge priority-{{ feature.priority }}">{{ feature.priority }}</span>
106
+ </div>
107
+ <h4 class="card-title">{{ feature.title }}</h4>
108
+ {% if feature.description %}
109
+ <p class="card-description">{{ feature.description[:100] }}{% if feature.description|length > 100 %}...{% endif %}</p>
110
+ {% endif %}
111
+ <div class="card-footer">
112
+ <span class="feature-id" title="{{ feature.id }}">{{ feature.id[:8] }}</span>
113
+ {% if feature.assigned_to %}
114
+ <span class="assigned-to">👤 {{ feature.assigned_to }}</span>
115
+ {% endif %}
116
+ </div>
117
+ </div>
118
+ {% endfor %}
119
+ {% if not features_by_status.blocked %}
120
+ <div class="empty-column">No tasks</div>
121
+ {% endif %}
122
+ </div>
123
+ </div>
124
+
125
+ <!-- DONE Column (Collapsed by default) -->
126
+ <div class="kanban-column done-column collapsed" data-status="done">
127
+ <div class="column-header">
128
+ <div>
129
+ <h3><span class="column-collapse-indicator">▶</span> Done</h3>
130
+ </div>
131
+ <span class="column-count">{{ features_by_status.done|length }}</span>
132
+ </div>
133
+ <div class="column-cards">
134
+ {% for feature in features_by_status.done %}
135
+ <div class="feature-card done-card" draggable="true" data-feature-id="{{ feature.id }}">
136
+ <div class="card-header">
137
+ <span class="feature-type-badge type-{{ feature.type }}">{{ feature.type }}</span>
138
+ <span class="priority-badge priority-{{ feature.priority }}">{{ feature.priority }}</span>
139
+ </div>
140
+ <h4 class="card-title">{{ feature.title }}</h4>
141
+ {% if feature.description %}
142
+ <p class="card-description">{{ feature.description[:100] }}{% if feature.description|length > 100 %}...{% endif %}</p>
143
+ {% endif %}
144
+ <div class="card-footer">
145
+ <span class="feature-id" title="{{ feature.id }}">{{ feature.id[:8] }}</span>
146
+ {% if feature.assigned_to %}
147
+ <span class="assigned-to">👤 {{ feature.assigned_to }}</span>
148
+ {% endif %}
149
+ </div>
150
+ </div>
151
+ {% endfor %}
152
+ {% if not features_by_status.done %}
153
+ <div class="empty-column">No tasks</div>
154
+ {% endif %}
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <script>
161
+ // Smart Kanban Column Management
162
+ class SmartKanban {
163
+ constructor() {
164
+ this.visibleColumns = ['todo', 'in_progress']; // Default visible columns
165
+ this.openOrder = []; // Track which column was opened first
166
+ this.init();
167
+ }
168
+
169
+ init() {
170
+ // Load saved state from localStorage
171
+ const saved = localStorage.getItem('kanban-visible-columns');
172
+ if (saved) {
173
+ this.visibleColumns = JSON.parse(saved);
174
+ this.renderColumns();
175
+ }
176
+
177
+ // Setup column header click handlers
178
+ document.querySelectorAll('.column-header').forEach(header => {
179
+ header.addEventListener('click', (e) => {
180
+ const column = header.closest('.kanban-column');
181
+ const status = column.dataset.status;
182
+ this.toggleColumn(status);
183
+ });
184
+ });
185
+
186
+ // Setup drag and drop
187
+ this.setupDragDrop();
188
+ }
189
+
190
+ toggleColumn(status) {
191
+ if (this.visibleColumns.includes(status)) {
192
+ // Column is visible, hide it
193
+ this.visibleColumns = this.visibleColumns.filter(s => s !== status);
194
+ this.openOrder = this.openOrder.filter(s => s !== status);
195
+ } else {
196
+ // Column is hidden, show it
197
+ this.visibleColumns.push(status);
198
+ this.openOrder.push(status);
199
+
200
+ // Keep only 2 visible columns
201
+ if (this.visibleColumns.length > 2) {
202
+ // Collapse oldest opened column (if not 'done' or 'blocked')
203
+ const toCollapse = this.openOrder.shift();
204
+ if (toCollapse && toCollapse !== 'done' && toCollapse !== 'blocked') {
205
+ this.visibleColumns = this.visibleColumns.filter(s => s !== toCollapse);
206
+ } else if (toCollapse) {
207
+ // If we need to collapse 'done' or 'blocked', find next oldest
208
+ for (let col of this.openOrder) {
209
+ if (col !== 'done' && col !== 'blocked') {
210
+ this.visibleColumns = this.visibleColumns.filter(s => s !== col);
211
+ this.openOrder = this.openOrder.filter(s => s !== col);
212
+ break;
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ this.saveState();
220
+ this.renderColumns();
221
+ }
222
+
223
+ renderColumns() {
224
+ document.querySelectorAll('.kanban-column').forEach(column => {
225
+ const status = column.dataset.status;
226
+ const isVisible = this.visibleColumns.includes(status);
227
+
228
+ column.classList.toggle('collapsed', !isVisible);
229
+ });
230
+ }
231
+
232
+ saveState() {
233
+ localStorage.setItem('kanban-visible-columns', JSON.stringify(this.visibleColumns));
234
+ }
235
+
236
+ setupDragDrop() {
237
+ document.addEventListener('dragstart', (e) => {
238
+ if (e.target.classList.contains('feature-card')) {
239
+ e.target.classList.add('dragging');
240
+ e.dataTransfer.effectAllowed = 'move';
241
+ e.dataTransfer.setData('card-id', e.target.dataset.featureId);
242
+ }
243
+ });
244
+
245
+ document.addEventListener('dragend', (e) => {
246
+ document.querySelectorAll('.feature-card').forEach(card => {
247
+ card.classList.remove('dragging');
248
+ });
249
+ });
250
+
251
+ document.addEventListener('dragover', (e) => {
252
+ e.preventDefault();
253
+ e.dataTransfer.dropEffect = 'move';
254
+
255
+ // Only allow drops in visible columns
256
+ const cardsList = e.target.closest('.column-cards');
257
+ if (cardsList) {
258
+ const column = cardsList.closest('.kanban-column');
259
+ if (!column.classList.contains('collapsed')) {
260
+ cardsList.style.background = 'rgba(205, 255, 0, 0.1)';
261
+ }
262
+ }
263
+ });
264
+
265
+ document.addEventListener('dragleave', (e) => {
266
+ const cardsList = e.target.closest('.column-cards');
267
+ if (cardsList) {
268
+ cardsList.style.background = '';
269
+ }
270
+ });
271
+
272
+ document.addEventListener('drop', (e) => {
273
+ e.preventDefault();
274
+ const cardsList = e.target.closest('.column-cards');
275
+ if (cardsList) {
276
+ const column = cardsList.closest('.kanban-column');
277
+ const cardId = e.dataTransfer.getData('card-id');
278
+ const card = document.querySelector(`[data-feature-id="${cardId}"]`);
279
+
280
+ if (card && !column.classList.contains('collapsed')) {
281
+ cardsList.appendChild(card);
282
+ cardsList.style.background = '';
283
+
284
+ // TODO: Send update to server
285
+ const newStatus = column.dataset.status;
286
+ console.log(`Move feature ${cardId} to ${newStatus}`);
287
+ }
288
+ }
289
+ });
290
+ }
291
+ }
292
+
293
+ // Initialize when DOM is ready
294
+ document.addEventListener('htmx:afterSettle', function(evt) {
295
+ if (evt.detail.target.id === 'content-area' &&
296
+ document.querySelector('.features-view')) {
297
+ window.kanban = new SmartKanban();
298
+ }
299
+ });
300
+
301
+ // Also initialize on page load if features are already visible
302
+ if (document.querySelector('.features-view')) {
303
+ window.kanban = new SmartKanban();
304
+ }
305
+ </script>
306
+
307
+ <style>
308
+ /* Kanban specific styles */
309
+ .kanban-board {
310
+ display: grid;
311
+ grid-template-columns: repeat(4, 1fr);
312
+ gap: var(--spacing-lg);
313
+ height: calc(100% - 100px);
314
+ }
315
+
316
+ .kanban-column {
317
+ display: flex;
318
+ flex-direction: column;
319
+ background: var(--bg-card);
320
+ border: 1px solid var(--border-subtle);
321
+ border-radius: 2px;
322
+ overflow: hidden;
323
+ transition: all var(--transition-base);
324
+ min-height: 100px;
325
+ }
326
+
327
+ .kanban-column.collapsed {
328
+ background: var(--bg-darker);
329
+ max-height: 60px;
330
+ }
331
+
332
+ .column-header {
333
+ display: flex;
334
+ justify-content: space-between;
335
+ align-items: center;
336
+ padding: var(--spacing-lg);
337
+ background: var(--bg-darker);
338
+ border-bottom: 1px solid var(--border-subtle);
339
+ cursor: pointer;
340
+ transition: all var(--transition-base);
341
+ user-select: none;
342
+ }
343
+
344
+ .column-header:hover {
345
+ background: var(--bg-hover);
346
+ }
347
+
348
+ .column-header h3 {
349
+ margin: 0;
350
+ font-size: 1rem;
351
+ color: var(--accent-lime);
352
+ text-transform: uppercase;
353
+ letter-spacing: 0.05em;
354
+ }
355
+
356
+ .column-count {
357
+ background: var(--accent-lime);
358
+ color: var(--bg-dark);
359
+ padding: var(--spacing-xs) var(--spacing-sm);
360
+ border-radius: 2px;
361
+ font-weight: 700;
362
+ font-size: 0.8rem;
363
+ min-width: 32px;
364
+ text-align: center;
365
+ }
366
+
367
+ .column-cards {
368
+ flex: 1;
369
+ overflow-y: auto;
370
+ padding: var(--spacing-md);
371
+ display: flex;
372
+ flex-direction: column;
373
+ gap: var(--spacing-md);
374
+ transition: background-color var(--transition-base);
375
+ }
376
+
377
+ .kanban-column.collapsed .column-cards {
378
+ display: none;
379
+ }
380
+
381
+ .feature-card {
382
+ background: var(--bg-darker);
383
+ border: 1px solid var(--border-subtle);
384
+ border-radius: 2px;
385
+ padding: var(--spacing-lg);
386
+ cursor: grab;
387
+ transition: all var(--transition-base);
388
+ flex-shrink: 0;
389
+ user-select: none;
390
+ }
391
+
392
+ .feature-card:hover {
393
+ border-color: var(--accent-lime);
394
+ box-shadow: var(--shadow-md);
395
+ transform: translateY(-2px);
396
+ }
397
+
398
+ .feature-card.dragging {
399
+ opacity: 0.5;
400
+ cursor: grabbing;
401
+ transform: rotate(-5deg) scale(0.95);
402
+ }
403
+
404
+ .card-header {
405
+ display: flex;
406
+ gap: var(--spacing-md);
407
+ margin-bottom: var(--spacing-md);
408
+ flex-wrap: wrap;
409
+ }
410
+
411
+ .feature-type-badge {
412
+ padding: var(--spacing-xs) var(--spacing-sm);
413
+ border-radius: 2px;
414
+ font-size: 0.7rem;
415
+ font-weight: 600;
416
+ text-transform: uppercase;
417
+ letter-spacing: 0.05em;
418
+ background: var(--bg-card);
419
+ color: var(--accent-lime);
420
+ border: 1px solid var(--border-subtle);
421
+ }
422
+
423
+ .priority-badge {
424
+ padding: var(--spacing-xs) var(--spacing-sm);
425
+ border-radius: 2px;
426
+ font-size: 0.7rem;
427
+ font-weight: 600;
428
+ text-transform: uppercase;
429
+ letter-spacing: 0.05em;
430
+ border: 1px solid;
431
+ }
432
+
433
+ .priority-badge.high {
434
+ background: rgba(239, 68, 68, 0.15);
435
+ color: var(--status-blocked);
436
+ border-color: var(--status-blocked);
437
+ }
438
+
439
+ .priority-badge.medium {
440
+ background: rgba(251, 191, 36, 0.15);
441
+ color: #FBBF24;
442
+ border-color: #FBBF24;
443
+ }
444
+
445
+ .priority-badge.low {
446
+ background: rgba(34, 197, 94, 0.15);
447
+ color: var(--status-success);
448
+ border-color: var(--status-success);
449
+ }
450
+
451
+ .card-title {
452
+ color: var(--text-primary);
453
+ font-size: 0.95rem;
454
+ margin: 0;
455
+ line-height: 1.4;
456
+ font-weight: 600;
457
+ }
458
+
459
+ .card-description {
460
+ color: var(--text-secondary);
461
+ font-size: 0.85rem;
462
+ margin: var(--spacing-md) 0 0 0;
463
+ line-height: 1.5;
464
+ }
465
+
466
+ .card-footer {
467
+ display: flex;
468
+ justify-content: space-between;
469
+ align-items: center;
470
+ margin-top: var(--spacing-md);
471
+ padding-top: var(--spacing-md);
472
+ border-top: 1px solid var(--border-subtle);
473
+ font-size: 0.8rem;
474
+ color: var(--text-secondary);
475
+ }
476
+
477
+ .feature-id {
478
+ font-family: 'JetBrains Mono', monospace;
479
+ color: var(--accent-lime);
480
+ font-weight: 600;
481
+ }
482
+
483
+ .assigned-to {
484
+ color: var(--text-muted);
485
+ }
486
+
487
+ .empty-column {
488
+ display: flex;
489
+ align-items: center;
490
+ justify-content: center;
491
+ height: 100px;
492
+ color: var(--text-muted);
493
+ font-size: 0.85rem;
494
+ text-align: center;
495
+ padding: var(--spacing-lg);
496
+ }
497
+
498
+ @media (max-width: 1024px) {
499
+ .kanban-board {
500
+ grid-template-columns: repeat(2, 1fr);
501
+ }
502
+ }
503
+
504
+ @media (max-width: 768px) {
505
+ .kanban-board {
506
+ grid-template-columns: 1fr;
507
+ }
508
+ }
509
+ </style>