htmlgraph 0.26.24__py3-none-any.whl → 0.27.0__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.
- htmlgraph/__init__.py +23 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/cli.py +3 -3
- htmlgraph/analytics/cost_analyzer.py +5 -1
- htmlgraph/analytics/cross_session.py +13 -9
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/work_type.py +15 -11
- htmlgraph/analytics_index.py +2 -1
- htmlgraph/api/main.py +114 -51
- htmlgraph/api/templates/dashboard-redesign.html +3 -3
- htmlgraph/api/templates/dashboard.html +3 -3
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/builders/base.py +2 -1
- htmlgraph/builders/bug.py +2 -1
- htmlgraph/builders/chore.py +2 -1
- htmlgraph/builders/epic.py +2 -1
- htmlgraph/builders/feature.py +2 -1
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +2 -1
- htmlgraph/builders/spike.py +2 -1
- htmlgraph/builders/track.py +28 -1
- htmlgraph/cli/analytics.py +2 -1
- htmlgraph/cli/base.py +33 -8
- htmlgraph/cli/core.py +2 -1
- htmlgraph/cli/main.py +2 -1
- htmlgraph/cli/models.py +2 -1
- htmlgraph/cli/templates/cost_dashboard.py +2 -1
- htmlgraph/cli/work/__init__.py +76 -1
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +2 -1
- htmlgraph/cli/work/orchestration.py +2 -1
- htmlgraph/cli/work/report.py +2 -1
- htmlgraph/cli/work/sessions.py +2 -1
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +2 -1
- htmlgraph/collections/base.py +43 -4
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +12 -7
- htmlgraph/collections/spike.py +6 -1
- htmlgraph/collections/task_delegation.py +7 -2
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +15 -10
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +11 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/edge_index.py +2 -1
- htmlgraph/event_log.py +81 -66
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +6 -2
- htmlgraph/hooks/cigs_pretool_enforcer.py +5 -1
- htmlgraph/hooks/drift_handler.py +3 -3
- htmlgraph/hooks/event_tracker.py +40 -61
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +92 -14
- htmlgraph/hooks/orchestrator_reflector.py +4 -0
- htmlgraph/hooks/post_tool_use_failure.py +7 -3
- htmlgraph/hooks/posttooluse.py +4 -0
- htmlgraph/hooks/prompt_analyzer.py +5 -5
- htmlgraph/hooks/session_handler.py +5 -2
- htmlgraph/hooks/session_summary.py +6 -2
- htmlgraph/hooks/validator.py +8 -4
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +2 -1
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +18 -1
- htmlgraph/operations/analytics.py +2 -1
- htmlgraph/operations/bootstrap.py +2 -1
- htmlgraph/operations/events.py +2 -1
- htmlgraph/operations/fastapi_server.py +2 -1
- htmlgraph/operations/hooks.py +2 -1
- htmlgraph/operations/initialization.py +2 -1
- htmlgraph/operations/server.py +2 -1
- htmlgraph/orchestration/__init__.py +4 -0
- htmlgraph/orchestration/claude_launcher.py +23 -20
- htmlgraph/orchestration/command_builder.py +2 -1
- htmlgraph/orchestration/headless_spawner.py +6 -2
- htmlgraph/orchestration/model_selection.py +7 -3
- htmlgraph/orchestration/plugin_manager.py +25 -21
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/claude.py +5 -2
- htmlgraph/orchestration/spawners/codex.py +12 -19
- htmlgraph/orchestration/spawners/copilot.py +13 -18
- htmlgraph/orchestration/spawners/gemini.py +12 -19
- htmlgraph/orchestration/subprocess_runner.py +6 -3
- htmlgraph/orchestration/task_coordination.py +16 -8
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/parallel.py +2 -1
- htmlgraph/query_builder.py +2 -1
- htmlgraph/reflection.py +2 -1
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +2 -1
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/server.py +21 -17
- htmlgraph/session_manager.py +1 -7
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/handoff.py +10 -3
- htmlgraph/system_prompts.py +2 -1
- htmlgraph/track_builder.py +14 -1
- htmlgraph/transcript.py +2 -1
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/METADATA +15 -1
- {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/RECORD +154 -117
- htmlgraph/sdk.py +0 -3430
- {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
<div class="view-container work-items-view">
|
|
2
|
+
<div class="view-header">
|
|
3
|
+
<h2>Work Items - Smart Kanban</h2>
|
|
4
|
+
<div class="view-description">
|
|
5
|
+
All work items (features, bugs, spikes, tasks) organized by status. Drag cards between visible columns.
|
|
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/work-items"
|
|
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">{{ work_items_by_status.todo|length }}</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="column-cards">
|
|
35
|
+
{% for item in work_items_by_status.todo %}
|
|
36
|
+
<div class="work-item-card todo-card" draggable="true" data-work-item-id="{{ item.id }}">
|
|
37
|
+
<div class="card-header">
|
|
38
|
+
<span class="work-item-type-badge type-{{ item.type }}">{{ item.type }}</span>
|
|
39
|
+
<span class="priority-badge priority-{{ item.priority }}">{{ item.priority }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<h4 class="card-title">{{ item.title }}</h4>
|
|
42
|
+
{% if item.description %}
|
|
43
|
+
<p class="card-description">{{ item.description[:100] }}{% if item.description|length > 100 %}...{% endif %}</p>
|
|
44
|
+
{% endif %}
|
|
45
|
+
<div class="card-footer">
|
|
46
|
+
<span class="work-item-id" title="{{ item.id }}">{{ item.id[:8] }}</span>
|
|
47
|
+
<div class="contributors">
|
|
48
|
+
{% if item.contributors %}
|
|
49
|
+
{% for agent in item.contributors %}
|
|
50
|
+
<span class="contributor-tag agent-{{ agent|lower|replace(' ', '-') }}" title="{{ agent }}">
|
|
51
|
+
{{ agent[:1]|upper }}
|
|
52
|
+
</span>
|
|
53
|
+
{% endfor %}
|
|
54
|
+
{% endif %}
|
|
55
|
+
{% if item.assigned_to %}
|
|
56
|
+
<span class="assigned-to" title="Assigned to {{ item.assigned_to }}">👤 {{ item.assigned_to[:8] }}</span>
|
|
57
|
+
{% endif %}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
{% endfor %}
|
|
62
|
+
{% if not work_items_by_status.todo %}
|
|
63
|
+
<div class="empty-column">No tasks</div>
|
|
64
|
+
{% endif %}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- IN PROGRESS Column -->
|
|
69
|
+
<div class="kanban-column in-progress-column" data-status="in_progress">
|
|
70
|
+
<div class="column-header">
|
|
71
|
+
<div>
|
|
72
|
+
<h3><span class="column-collapse-indicator">▼</span> In Progress</h3>
|
|
73
|
+
</div>
|
|
74
|
+
<span class="column-count">{{ work_items_by_status.in_progress|length }}</span>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="column-cards">
|
|
77
|
+
{% for item in work_items_by_status.in_progress %}
|
|
78
|
+
<div class="work-item-card in-progress-card" draggable="true" data-work-item-id="{{ item.id }}">
|
|
79
|
+
<div class="card-header">
|
|
80
|
+
<span class="work-item-type-badge type-{{ item.type }}">{{ item.type }}</span>
|
|
81
|
+
<span class="priority-badge priority-{{ item.priority }}">{{ item.priority }}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<h4 class="card-title">{{ item.title }}</h4>
|
|
84
|
+
{% if item.description %}
|
|
85
|
+
<p class="card-description">{{ item.description[:100] }}{% if item.description|length > 100 %}...{% endif %}</p>
|
|
86
|
+
{% endif %}
|
|
87
|
+
<div class="card-footer">
|
|
88
|
+
<span class="work-item-id" title="{{ item.id }}">{{ item.id[:8] }}</span>
|
|
89
|
+
<div class="contributors">
|
|
90
|
+
{% if item.contributors %}
|
|
91
|
+
{% for agent in item.contributors %}
|
|
92
|
+
<span class="contributor-tag agent-{{ agent|lower|replace(' ', '-') }}" title="{{ agent }}">
|
|
93
|
+
{{ agent[:1]|upper }}
|
|
94
|
+
</span>
|
|
95
|
+
{% endfor %}
|
|
96
|
+
{% endif %}
|
|
97
|
+
{% if item.assigned_to %}
|
|
98
|
+
<span class="assigned-to" title="Assigned to {{ item.assigned_to }}">👤 {{ item.assigned_to[:8] }}</span>
|
|
99
|
+
{% endif %}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
{% endfor %}
|
|
104
|
+
{% if not work_items_by_status.in_progress %}
|
|
105
|
+
<div class="empty-column">No tasks</div>
|
|
106
|
+
{% endif %}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- BLOCKED Column (Collapsed by default) -->
|
|
111
|
+
<div class="kanban-column blocked-column collapsed" data-status="blocked">
|
|
112
|
+
<div class="column-header">
|
|
113
|
+
<div>
|
|
114
|
+
<h3><span class="column-collapse-indicator">▶</span> Blocked</h3>
|
|
115
|
+
</div>
|
|
116
|
+
<span class="column-count">{{ work_items_by_status.blocked|length }}</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="column-cards">
|
|
119
|
+
{% for item in work_items_by_status.blocked %}
|
|
120
|
+
<div class="work-item-card blocked-card" draggable="true" data-work-item-id="{{ item.id }}">
|
|
121
|
+
<div class="card-header">
|
|
122
|
+
<span class="work-item-type-badge type-{{ item.type }}">{{ item.type }}</span>
|
|
123
|
+
<span class="priority-badge priority-{{ item.priority }}">{{ item.priority }}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<h4 class="card-title">{{ item.title }}</h4>
|
|
126
|
+
{% if item.description %}
|
|
127
|
+
<p class="card-description">{{ item.description[:100] }}{% if item.description|length > 100 %}...{% endif %}</p>
|
|
128
|
+
{% endif %}
|
|
129
|
+
<div class="card-footer">
|
|
130
|
+
<span class="work-item-id" title="{{ item.id }}">{{ item.id[:8] }}</span>
|
|
131
|
+
<div class="contributors">
|
|
132
|
+
{% if item.contributors %}
|
|
133
|
+
{% for agent in item.contributors %}
|
|
134
|
+
<span class="contributor-tag agent-{{ agent|lower|replace(' ', '-') }}" title="{{ agent }}">
|
|
135
|
+
{{ agent[:1]|upper }}
|
|
136
|
+
</span>
|
|
137
|
+
{% endfor %}
|
|
138
|
+
{% endif %}
|
|
139
|
+
{% if item.assigned_to %}
|
|
140
|
+
<span class="assigned-to" title="Assigned to {{ item.assigned_to }}">👤 {{ item.assigned_to[:8] }}</span>
|
|
141
|
+
{% endif %}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
{% endfor %}
|
|
146
|
+
{% if not work_items_by_status.blocked %}
|
|
147
|
+
<div class="empty-column">No tasks</div>
|
|
148
|
+
{% endif %}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<!-- DONE Column (Collapsed by default) -->
|
|
153
|
+
<div class="kanban-column done-column collapsed" data-status="done">
|
|
154
|
+
<div class="column-header">
|
|
155
|
+
<div>
|
|
156
|
+
<h3><span class="column-collapse-indicator">▶</span> Done</h3>
|
|
157
|
+
</div>
|
|
158
|
+
<span class="column-count">{{ work_items_by_status.done|length }}</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="column-cards">
|
|
161
|
+
{% for item in work_items_by_status.done %}
|
|
162
|
+
<div class="work-item-card done-card" draggable="true" data-work-item-id="{{ item.id }}">
|
|
163
|
+
<div class="card-header">
|
|
164
|
+
<span class="work-item-type-badge type-{{ item.type }}">{{ item.type }}</span>
|
|
165
|
+
<span class="priority-badge priority-{{ item.priority }}">{{ item.priority }}</span>
|
|
166
|
+
</div>
|
|
167
|
+
<h4 class="card-title">{{ item.title }}</h4>
|
|
168
|
+
{% if item.description %}
|
|
169
|
+
<p class="card-description">{{ item.description[:100] }}{% if item.description|length > 100 %}...{% endif %}</p>
|
|
170
|
+
{% endif %}
|
|
171
|
+
<div class="card-footer">
|
|
172
|
+
<span class="work-item-id" title="{{ item.id }}">{{ item.id[:8] }}</span>
|
|
173
|
+
<div class="contributors">
|
|
174
|
+
{% if item.contributors %}
|
|
175
|
+
{% for agent in item.contributors %}
|
|
176
|
+
<span class="contributor-tag agent-{{ agent|lower|replace(' ', '-') }}" title="{{ agent }}">
|
|
177
|
+
{{ agent[:1]|upper }}
|
|
178
|
+
</span>
|
|
179
|
+
{% endfor %}
|
|
180
|
+
{% endif %}
|
|
181
|
+
{% if item.assigned_to %}
|
|
182
|
+
<span class="assigned-to" title="Assigned to {{ item.assigned_to }}">👤 {{ item.assigned_to[:8] }}</span>
|
|
183
|
+
{% endif %}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
{% endfor %}
|
|
188
|
+
{% if not work_items_by_status.done %}
|
|
189
|
+
<div class="empty-column">No tasks</div>
|
|
190
|
+
{% endif %}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<script>
|
|
197
|
+
// Smart Kanban Column Management
|
|
198
|
+
class SmartKanban {
|
|
199
|
+
constructor() {
|
|
200
|
+
this.visibleColumns = ['todo', 'in_progress']; // Default visible columns
|
|
201
|
+
this.openOrder = []; // Track which column was opened first
|
|
202
|
+
this.init();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
init() {
|
|
206
|
+
// Load saved state from localStorage
|
|
207
|
+
const saved = localStorage.getItem('kanban-visible-columns');
|
|
208
|
+
if (saved) {
|
|
209
|
+
this.visibleColumns = JSON.parse(saved);
|
|
210
|
+
this.renderColumns();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Setup column header click handlers
|
|
214
|
+
document.querySelectorAll('.column-header').forEach(header => {
|
|
215
|
+
header.addEventListener('click', (e) => {
|
|
216
|
+
const column = header.closest('.kanban-column');
|
|
217
|
+
const status = column.dataset.status;
|
|
218
|
+
this.toggleColumn(status);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Setup drag and drop
|
|
223
|
+
this.setupDragDrop();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
toggleColumn(status) {
|
|
227
|
+
if (this.visibleColumns.includes(status)) {
|
|
228
|
+
// Column is visible, hide it
|
|
229
|
+
this.visibleColumns = this.visibleColumns.filter(s => s !== status);
|
|
230
|
+
this.openOrder = this.openOrder.filter(s => s !== status);
|
|
231
|
+
} else {
|
|
232
|
+
// Column is hidden, show it
|
|
233
|
+
this.visibleColumns.push(status);
|
|
234
|
+
this.openOrder.push(status);
|
|
235
|
+
|
|
236
|
+
// Keep only 2 visible columns
|
|
237
|
+
if (this.visibleColumns.length > 2) {
|
|
238
|
+
// Collapse oldest opened column (if not 'done' or 'blocked')
|
|
239
|
+
const toCollapse = this.openOrder.shift();
|
|
240
|
+
if (toCollapse && toCollapse !== 'done' && toCollapse !== 'blocked') {
|
|
241
|
+
this.visibleColumns = this.visibleColumns.filter(s => s !== toCollapse);
|
|
242
|
+
} else if (toCollapse) {
|
|
243
|
+
// If we need to collapse 'done' or 'blocked', find next oldest
|
|
244
|
+
for (let col of this.openOrder) {
|
|
245
|
+
if (col !== 'done' && col !== 'blocked') {
|
|
246
|
+
this.visibleColumns = this.visibleColumns.filter(s => s !== col);
|
|
247
|
+
this.openOrder = this.openOrder.filter(s => s !== col);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.saveState();
|
|
256
|
+
this.renderColumns();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
renderColumns() {
|
|
260
|
+
document.querySelectorAll('.kanban-column').forEach(column => {
|
|
261
|
+
const status = column.dataset.status;
|
|
262
|
+
const isVisible = this.visibleColumns.includes(status);
|
|
263
|
+
|
|
264
|
+
column.classList.toggle('collapsed', !isVisible);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
saveState() {
|
|
269
|
+
localStorage.setItem('kanban-visible-columns', JSON.stringify(this.visibleColumns));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
setupDragDrop() {
|
|
273
|
+
document.addEventListener('dragstart', (e) => {
|
|
274
|
+
if (e.target.classList.contains('work-item-card')) {
|
|
275
|
+
e.target.classList.add('dragging');
|
|
276
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
277
|
+
e.dataTransfer.setData('card-id', e.target.dataset.workItemId);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
document.addEventListener('dragend', (e) => {
|
|
282
|
+
document.querySelectorAll('.work-item-card').forEach(card => {
|
|
283
|
+
card.classList.remove('dragging');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
document.addEventListener('dragover', (e) => {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
e.dataTransfer.dropEffect = 'move';
|
|
290
|
+
|
|
291
|
+
// Only allow drops in visible columns
|
|
292
|
+
const cardsList = e.target.closest('.column-cards');
|
|
293
|
+
if (cardsList) {
|
|
294
|
+
const column = cardsList.closest('.kanban-column');
|
|
295
|
+
if (!column.classList.contains('collapsed')) {
|
|
296
|
+
cardsList.style.background = 'rgba(205, 255, 0, 0.1)';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
document.addEventListener('dragleave', (e) => {
|
|
302
|
+
const cardsList = e.target.closest('.column-cards');
|
|
303
|
+
if (cardsList) {
|
|
304
|
+
cardsList.style.background = '';
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
document.addEventListener('drop', (e) => {
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
const cardsList = e.target.closest('.column-cards');
|
|
311
|
+
if (cardsList) {
|
|
312
|
+
const column = cardsList.closest('.kanban-column');
|
|
313
|
+
const cardId = e.dataTransfer.getData('card-id');
|
|
314
|
+
const card = document.querySelector(`[data-work-item-id="${cardId}"]`);
|
|
315
|
+
|
|
316
|
+
if (card && !column.classList.contains('collapsed')) {
|
|
317
|
+
cardsList.appendChild(card);
|
|
318
|
+
cardsList.style.background = '';
|
|
319
|
+
|
|
320
|
+
// TODO: Send update to server
|
|
321
|
+
const newStatus = column.dataset.status;
|
|
322
|
+
console.log(`Move feature ${cardId} to ${newStatus}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Initialize when DOM is ready
|
|
330
|
+
document.addEventListener('htmx:afterSettle', function(evt) {
|
|
331
|
+
if (evt.detail.target.id === 'content-area' &&
|
|
332
|
+
document.querySelector('.work-items-view')) {
|
|
333
|
+
window.kanban = new SmartKanban();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Also initialize on page load if work items are already visible
|
|
338
|
+
if (document.querySelector('.work-items-view')) {
|
|
339
|
+
window.kanban = new SmartKanban();
|
|
340
|
+
}
|
|
341
|
+
</script>
|
|
342
|
+
|
|
343
|
+
<style>
|
|
344
|
+
/* Kanban specific styles */
|
|
345
|
+
.kanban-board {
|
|
346
|
+
display: grid;
|
|
347
|
+
grid-template-columns: repeat(4, 1fr);
|
|
348
|
+
gap: var(--spacing-lg);
|
|
349
|
+
height: calc(100% - 100px);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.kanban-column {
|
|
353
|
+
display: flex;
|
|
354
|
+
flex-direction: column;
|
|
355
|
+
background: var(--bg-card);
|
|
356
|
+
border: 1px solid var(--border-subtle);
|
|
357
|
+
border-radius: 2px;
|
|
358
|
+
overflow: hidden;
|
|
359
|
+
transition: all var(--transition-base);
|
|
360
|
+
min-height: 100px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.kanban-column.collapsed {
|
|
364
|
+
background: var(--bg-darker);
|
|
365
|
+
max-height: 60px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.column-header {
|
|
369
|
+
display: flex;
|
|
370
|
+
justify-content: space-between;
|
|
371
|
+
align-items: center;
|
|
372
|
+
padding: var(--spacing-lg);
|
|
373
|
+
background: var(--bg-darker);
|
|
374
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
375
|
+
cursor: pointer;
|
|
376
|
+
transition: all var(--transition-base);
|
|
377
|
+
user-select: none;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.column-header:hover {
|
|
381
|
+
background: var(--bg-hover);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.column-header h3 {
|
|
385
|
+
margin: 0;
|
|
386
|
+
font-size: 1rem;
|
|
387
|
+
color: var(--accent-lime);
|
|
388
|
+
text-transform: uppercase;
|
|
389
|
+
letter-spacing: 0.05em;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.column-count {
|
|
393
|
+
background: var(--accent-lime);
|
|
394
|
+
color: var(--bg-dark);
|
|
395
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
396
|
+
border-radius: 2px;
|
|
397
|
+
font-weight: 700;
|
|
398
|
+
font-size: 0.8rem;
|
|
399
|
+
min-width: 32px;
|
|
400
|
+
text-align: center;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.column-cards {
|
|
404
|
+
flex: 1;
|
|
405
|
+
overflow-y: auto;
|
|
406
|
+
padding: var(--spacing-md);
|
|
407
|
+
display: flex;
|
|
408
|
+
flex-direction: column;
|
|
409
|
+
gap: var(--spacing-md);
|
|
410
|
+
transition: background-color var(--transition-base);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.kanban-column.collapsed .column-cards {
|
|
414
|
+
display: none;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.work-item-card {
|
|
418
|
+
background: var(--bg-darker);
|
|
419
|
+
border: 1px solid var(--border-subtle);
|
|
420
|
+
border-radius: 2px;
|
|
421
|
+
padding: var(--spacing-lg);
|
|
422
|
+
cursor: grab;
|
|
423
|
+
transition: all var(--transition-base);
|
|
424
|
+
flex-shrink: 0;
|
|
425
|
+
user-select: none;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.work-item-card:hover {
|
|
429
|
+
border-color: var(--accent-lime);
|
|
430
|
+
box-shadow: var(--shadow-md);
|
|
431
|
+
transform: translateY(-2px);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.work-item-card.dragging {
|
|
435
|
+
opacity: 0.5;
|
|
436
|
+
cursor: grabbing;
|
|
437
|
+
transform: rotate(-5deg) scale(0.95);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.card-header {
|
|
441
|
+
display: flex;
|
|
442
|
+
gap: var(--spacing-md);
|
|
443
|
+
margin-bottom: var(--spacing-md);
|
|
444
|
+
flex-wrap: wrap;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.work-item-type-badge {
|
|
448
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
449
|
+
border-radius: 2px;
|
|
450
|
+
font-size: 0.7rem;
|
|
451
|
+
font-weight: 600;
|
|
452
|
+
text-transform: uppercase;
|
|
453
|
+
letter-spacing: 0.05em;
|
|
454
|
+
border: 1px solid;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Type-specific colors */
|
|
458
|
+
.work-item-type-badge.type-feature {
|
|
459
|
+
background: rgba(205, 255, 0, 0.15);
|
|
460
|
+
color: var(--accent-lime);
|
|
461
|
+
border-color: var(--accent-lime);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.work-item-type-badge.type-bug {
|
|
465
|
+
background: rgba(239, 68, 68, 0.15);
|
|
466
|
+
color: #ef4444;
|
|
467
|
+
border-color: #ef4444;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.work-item-type-badge.type-spike {
|
|
471
|
+
background: rgba(251, 191, 36, 0.15);
|
|
472
|
+
color: #fbbf24;
|
|
473
|
+
border-color: #fbbf24;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.work-item-type-badge.type-task {
|
|
477
|
+
background: rgba(139, 92, 246, 0.15);
|
|
478
|
+
color: #8b5cf6;
|
|
479
|
+
border-color: #8b5cf6;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.work-item-type-badge.type-chore {
|
|
483
|
+
background: rgba(107, 114, 128, 0.15);
|
|
484
|
+
color: #9ca3af;
|
|
485
|
+
border-color: #9ca3af;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.work-item-type-badge.type-epic {
|
|
489
|
+
background: rgba(34, 197, 94, 0.15);
|
|
490
|
+
color: #22c55e;
|
|
491
|
+
border-color: #22c55e;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.priority-badge {
|
|
495
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
496
|
+
border-radius: 2px;
|
|
497
|
+
font-size: 0.7rem;
|
|
498
|
+
font-weight: 600;
|
|
499
|
+
text-transform: uppercase;
|
|
500
|
+
letter-spacing: 0.05em;
|
|
501
|
+
border: 1px solid;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.priority-badge.high {
|
|
505
|
+
background: rgba(239, 68, 68, 0.15);
|
|
506
|
+
color: var(--status-blocked);
|
|
507
|
+
border-color: var(--status-blocked);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.priority-badge.medium {
|
|
511
|
+
background: rgba(251, 191, 36, 0.15);
|
|
512
|
+
color: #FBBF24;
|
|
513
|
+
border-color: #FBBF24;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.priority-badge.low {
|
|
517
|
+
background: rgba(34, 197, 94, 0.15);
|
|
518
|
+
color: var(--status-success);
|
|
519
|
+
border-color: var(--status-success);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.card-title {
|
|
523
|
+
color: var(--text-primary);
|
|
524
|
+
font-size: 0.95rem;
|
|
525
|
+
margin: 0;
|
|
526
|
+
line-height: 1.4;
|
|
527
|
+
font-weight: 600;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.card-description {
|
|
531
|
+
color: var(--text-secondary);
|
|
532
|
+
font-size: 0.85rem;
|
|
533
|
+
margin: var(--spacing-md) 0 0 0;
|
|
534
|
+
line-height: 1.5;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.card-footer {
|
|
538
|
+
display: flex;
|
|
539
|
+
justify-content: space-between;
|
|
540
|
+
align-items: center;
|
|
541
|
+
margin-top: var(--spacing-md);
|
|
542
|
+
padding-top: var(--spacing-md);
|
|
543
|
+
border-top: 1px solid var(--border-subtle);
|
|
544
|
+
font-size: 0.8rem;
|
|
545
|
+
color: var(--text-secondary);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.work-item-id {
|
|
549
|
+
font-family: 'JetBrains Mono', monospace;
|
|
550
|
+
color: var(--accent-lime);
|
|
551
|
+
font-weight: 600;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.contributors {
|
|
555
|
+
display: flex;
|
|
556
|
+
gap: 4px;
|
|
557
|
+
align-items: center;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.contributor-tag {
|
|
561
|
+
display: inline-flex;
|
|
562
|
+
align-items: center;
|
|
563
|
+
justify-content: center;
|
|
564
|
+
width: 18px;
|
|
565
|
+
height: 18px;
|
|
566
|
+
border-radius: 50%;
|
|
567
|
+
background: rgba(255, 255, 255, 0.1);
|
|
568
|
+
color: var(--text-primary);
|
|
569
|
+
font-size: 0.6rem;
|
|
570
|
+
font-weight: 700;
|
|
571
|
+
border: 1px solid var(--border-subtle);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.contributor-tag.agent-claude-code, .contributor-tag.agent-claude {
|
|
575
|
+
background: rgba(200, 255, 0, 0.2);
|
|
576
|
+
color: #c8ff00;
|
|
577
|
+
border-color: rgba(200, 255, 0, 0.4);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.contributor-tag.agent-gemini {
|
|
581
|
+
background: rgba(74, 222, 128, 0.2);
|
|
582
|
+
color: #4ade80;
|
|
583
|
+
border-color: rgba(74, 222, 128, 0.4);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.assigned-to {
|
|
587
|
+
color: var(--text-muted);
|
|
588
|
+
margin-left: 4px;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.empty-column {
|
|
592
|
+
display: flex;
|
|
593
|
+
align-items: center;
|
|
594
|
+
justify-content: center;
|
|
595
|
+
height: 100px;
|
|
596
|
+
color: var(--text-muted);
|
|
597
|
+
font-size: 0.85rem;
|
|
598
|
+
text-align: center;
|
|
599
|
+
padding: var(--spacing-lg);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
@media (max-width: 1024px) {
|
|
603
|
+
.kanban-board {
|
|
604
|
+
grid-template-columns: repeat(2, 1fr);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
@media (max-width: 768px) {
|
|
609
|
+
.kanban-board {
|
|
610
|
+
grid-template-columns: 1fr;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
</style>
|
htmlgraph/attribute_index.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Attribute Index for O(1) attribute-based lookups.
|
|
3
5
|
|
|
@@ -11,7 +13,6 @@ Without this index, finding nodes by attribute requires scanning
|
|
|
11
13
|
all nodes in the graph - O(n) complexity.
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
|
-
from __future__ import annotations
|
|
15
16
|
|
|
16
17
|
from collections import defaultdict
|
|
17
18
|
from dataclasses import dataclass, field
|
htmlgraph/builders/base.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Base builder class for fluent node creation.
|
|
3
5
|
|
|
4
6
|
Provides common builder patterns shared across all node types.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
|
-
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
htmlgraph/builders/bug.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Bug builder for creating bug report nodes.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ Extends BaseBuilder with bug-specific methods like
|
|
|
5
7
|
severity and reproduction steps.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
11
12
|
|
htmlgraph/builders/chore.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Chore builder for creating maintenance task nodes.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ Extends BaseBuilder with chore-specific methods like
|
|
|
5
7
|
chore type and recurrence.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
11
12
|
|
htmlgraph/builders/epic.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Epic builder for creating large body of work nodes.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ Extends BaseBuilder with epic-specific methods like
|
|
|
5
7
|
child features and milestones.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
from datetime import date
|
|
11
12
|
from typing import TYPE_CHECKING, Any
|
htmlgraph/builders/feature.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Feature builder for creating feature nodes.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ Extends BaseBuilder with feature-specific methods like
|
|
|
5
7
|
capability management.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
11
12
|
|