dctracker 1.0.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.
Files changed (87) hide show
  1. dct/__init__.py +6 -0
  2. dct/cli.py +1659 -0
  3. dct/config.py +150 -0
  4. dct/core/__init__.py +0 -0
  5. dct/core/analytics.py +382 -0
  6. dct/core/changelog.py +112 -0
  7. dct/core/checkpoints.py +205 -0
  8. dct/core/decisions.py +238 -0
  9. dct/core/engine.py +32 -0
  10. dct/core/handoff.py +151 -0
  11. dct/core/ids.py +25 -0
  12. dct/core/items.py +382 -0
  13. dct/core/plans/__init__.py +6 -0
  14. dct/core/plans/classify.py +94 -0
  15. dct/core/plans/crud.py +680 -0
  16. dct/core/plans/ingest.py +408 -0
  17. dct/core/plans/parser.py +312 -0
  18. dct/core/projects.py +298 -0
  19. dct/core/sprint.py +315 -0
  20. dct/core/sprints.py +601 -0
  21. dct/core/version.py +116 -0
  22. dct/db.py +558 -0
  23. dct/export.py +107 -0
  24. dct/hooks/check-changelog-on-stop.sh +49 -0
  25. dct/hooks/check-changelog.sh +143 -0
  26. dct/hooks/dct-plan-autoscan.sh +54 -0
  27. dct/hooks/dct-task-mirror.sh +62 -0
  28. dct/server.py +1812 -0
  29. dct/setup.py +258 -0
  30. dct/skills/clog/SKILL.md +73 -0
  31. dct/skills/commit/SKILL.md +153 -0
  32. dct/skills/dct-import/SKILL.md +329 -0
  33. dct/skills/dct-init/SKILL.md +289 -0
  34. dct/skills/handoff/SKILL.md +136 -0
  35. dct/skills/pickup/SKILL.md +115 -0
  36. dct/skills/plan-resolve-uncertain/SKILL.md +82 -0
  37. dct/skills/plan-status/SKILL.md +79 -0
  38. dct/skills/release/SKILL.md +281 -0
  39. dct/skills/track/SKILL.md +121 -0
  40. dct/skills/track-list/SKILL.md +134 -0
  41. dct/skills/track-resolve/SKILL.md +53 -0
  42. dct/skills/track-update/SKILL.md +109 -0
  43. dct/web/__init__.py +1 -0
  44. dct/web/app.py +83 -0
  45. dct/web/blueprints/__init__.py +5 -0
  46. dct/web/blueprints/analytics.py +34 -0
  47. dct/web/blueprints/changelog.py +40 -0
  48. dct/web/blueprints/decisions.py +23 -0
  49. dct/web/blueprints/events.py +69 -0
  50. dct/web/blueprints/handoffs.py +26 -0
  51. dct/web/blueprints/home.py +15 -0
  52. dct/web/blueprints/items.py +128 -0
  53. dct/web/blueprints/plans.py +53 -0
  54. dct/web/blueprints/projects.py +23 -0
  55. dct/web/blueprints/sprints.py +71 -0
  56. dct/web/changes.py +77 -0
  57. dct/web/serializers.py +109 -0
  58. dct/web/static/app.css +609 -0
  59. dct/web/static/app.js +62 -0
  60. dct/web/static/vendor/SOURCES.txt +10 -0
  61. dct/web/static/vendor/htmx.min.js +1 -0
  62. dct/web/static/vendor/uPlot.iife.min.js +2 -0
  63. dct/web/static/vendor/uPlot.min.css +1 -0
  64. dct/web/templates/analytics.html +63 -0
  65. dct/web/templates/base.html +106 -0
  66. dct/web/templates/changelog/list.html +23 -0
  67. dct/web/templates/decisions/list.html +22 -0
  68. dct/web/templates/handoffs/list.html +45 -0
  69. dct/web/templates/home.html +20 -0
  70. dct/web/templates/item_detail.html +91 -0
  71. dct/web/templates/items_list.html +44 -0
  72. dct/web/templates/partials/_bars.html +15 -0
  73. dct/web/templates/partials/_checkpoint_steps.html +22 -0
  74. dct/web/templates/partials/_items_table.html +23 -0
  75. dct/web/templates/partials/_plan_section.html +24 -0
  76. dct/web/templates/partials/_project_card.html +24 -0
  77. dct/web/templates/plans/detail.html +16 -0
  78. dct/web/templates/plans/list.html +15 -0
  79. dct/web/templates/project_home.html +51 -0
  80. dct/web/templates/sprints/detail.html +37 -0
  81. dct/web/templates/sprints/list.html +35 -0
  82. dctracker-1.0.0.dist-info/METADATA +357 -0
  83. dctracker-1.0.0.dist-info/RECORD +87 -0
  84. dctracker-1.0.0.dist-info/WHEEL +5 -0
  85. dctracker-1.0.0.dist-info/entry_points.txt +2 -0
  86. dctracker-1.0.0.dist-info/licenses/LICENSE +21 -0
  87. dctracker-1.0.0.dist-info/top_level.txt +1 -0
dct/web/static/app.css ADDED
@@ -0,0 +1,609 @@
1
+ /* dct web viewer — "The Ledger" design system.
2
+ dct is an append-only ledger (checkpoints, changelog, soft-deletes). The look
3
+ leans into that: a calm slate reading surface, editorial serif titles, every
4
+ datum in monospace, and a status-colored spine in the margin of each row/card.
5
+ Color is reserved for signal (priority / status), never decoration.
6
+ Zero build step: system fonts only, no web-font fetch. */
7
+
8
+ :root {
9
+ --font-display: "Charter", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
10
+ --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
11
+ --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
12
+
13
+ --bg: #eef1f6;
14
+ --surface: #ffffff;
15
+ --surface-2: #f4f6fa;
16
+ --ink: #1a2233;
17
+ --ink-soft: #3a4862;
18
+ --muted: #647089;
19
+ --faint: #97a2b8;
20
+ --line: #e0e5ee;
21
+ --line-strong: #cbd3e1;
22
+
23
+ --accent: #3b5bdb;
24
+ --accent-ink: #2b44b4;
25
+ --accent-wash: #eaeefc;
26
+
27
+ /* signal colors — priority, status, type */
28
+ --p1: #e03131; --p2: #1c7ed6; --p3: #5c6b85; --future: #97a2b8;
29
+ --st-open: #1c7ed6; --st-in_progress: #e8870c; --st-resolved: #2f9e44; --st-wont_fix: #97a2b8;
30
+ --ty-issue: #e03131; --ty-feature: #3b5bdb; --ty-todo: #5c6b85; --ty-idea: #9c36b5; --ty-improvement: #0c8599;
31
+ --gate: #e8590c;
32
+ --warn: #e8870c;
33
+
34
+ --radius: 12px;
35
+ --radius-sm: 7px;
36
+ --shadow: 0 1px 2px rgba(20, 30, 55, .05), 0 4px 14px rgba(20, 30, 55, .05);
37
+ --shadow-hover: 0 2px 6px rgba(20, 30, 55, .07), 0 10px 26px rgba(20, 30, 55, .10);
38
+ }
39
+
40
+ * { box-sizing: border-box; }
41
+
42
+ html { -webkit-text-size-adjust: 100%; }
43
+
44
+ body {
45
+ margin: 0;
46
+ min-height: 100vh;
47
+ display: grid;
48
+ grid-template-columns: 248px 1fr;
49
+ font-family: var(--font-body);
50
+ font-size: 14px;
51
+ line-height: 1.55;
52
+ color: var(--ink);
53
+ background: var(--bg);
54
+ -webkit-font-smoothing: antialiased;
55
+ }
56
+
57
+ a { color: var(--accent); text-decoration: none; }
58
+ a:hover { color: var(--accent-ink); text-decoration: underline; }
59
+
60
+ h1 {
61
+ font-family: var(--font-display);
62
+ font-weight: 600;
63
+ font-size: 1.85rem;
64
+ letter-spacing: -.01em;
65
+ line-height: 1.15;
66
+ margin: 0 0 .9rem;
67
+ color: var(--ink);
68
+ }
69
+ h2 {
70
+ font-size: 1.02rem;
71
+ font-weight: 680;
72
+ letter-spacing: -.005em;
73
+ margin: 2rem 0 .8rem;
74
+ padding-bottom: .35rem;
75
+ border-bottom: 1px solid var(--line);
76
+ color: var(--ink);
77
+ }
78
+ h4 {
79
+ margin: 0 0 .6rem;
80
+ font-size: .72rem;
81
+ font-weight: 700;
82
+ letter-spacing: .07em;
83
+ text-transform: uppercase;
84
+ color: var(--muted);
85
+ }
86
+
87
+ code, .mono { font-family: var(--font-mono); }
88
+ code {
89
+ font-size: .86em;
90
+ background: var(--surface-2);
91
+ border: 1px solid var(--line);
92
+ border-radius: 4px;
93
+ padding: .05em .35em;
94
+ }
95
+ .muted { color: var(--muted); font-weight: 500; }
96
+ .empty { color: var(--muted); font-style: italic; padding: .6rem 0; }
97
+
98
+ /* ── sidebar ───────────────────────────────────────────────────────────── */
99
+ .sidebar {
100
+ background: var(--surface-2);
101
+ border-right: 1px solid var(--line);
102
+ padding: 1.1rem .85rem 2rem;
103
+ position: sticky;
104
+ top: 0;
105
+ align-self: start;
106
+ height: 100vh;
107
+ overflow-y: auto;
108
+ }
109
+ .brand {
110
+ font-family: var(--font-display);
111
+ font-size: 1.5rem;
112
+ font-weight: 600;
113
+ margin: 0 .15rem;
114
+ letter-spacing: -.02em;
115
+ }
116
+ .brand a { color: var(--ink); display: inline-flex; align-items: center; gap: .15rem; }
117
+ .brand a:hover { text-decoration: none; color: var(--ink); }
118
+ .brand-mark { color: var(--accent); }
119
+ .brand-sub {
120
+ margin: 0 0 1.2rem .2rem;
121
+ font-size: .68rem;
122
+ letter-spacing: .08em;
123
+ text-transform: uppercase;
124
+ color: var(--faint);
125
+ }
126
+
127
+ .projects { display: flex; flex-direction: column; gap: .12rem; }
128
+ .proj {
129
+ display: flex;
130
+ align-items: baseline;
131
+ justify-content: space-between;
132
+ gap: .4rem;
133
+ padding: .34rem .55rem;
134
+ border-radius: var(--radius-sm);
135
+ color: var(--ink-soft);
136
+ border-left: 2px solid transparent;
137
+ }
138
+ .proj:hover { background: var(--surface); color: var(--ink); text-decoration: none; }
139
+ .proj-slug { font-weight: 600; }
140
+ .proj-ver { font-family: var(--font-mono); font-size: .7rem; color: var(--faint); }
141
+ .proj.is-active {
142
+ background: var(--accent-wash);
143
+ color: var(--accent-ink);
144
+ border-left-color: var(--accent);
145
+ }
146
+ .proj.is-active .proj-ver { color: var(--accent); }
147
+ .proj-all { font-weight: 600; }
148
+
149
+ .section-nav {
150
+ display: flex;
151
+ flex-direction: column;
152
+ margin: .15rem 0 .5rem .4rem;
153
+ padding-left: .55rem;
154
+ border-left: 1px solid var(--line-strong);
155
+ }
156
+ .section-link {
157
+ padding: .2rem .5rem;
158
+ border-radius: 5px;
159
+ color: var(--muted);
160
+ font-size: .86rem;
161
+ }
162
+ .section-link:hover { background: var(--surface); color: var(--ink); text-decoration: none; }
163
+ .section-link.is-active { color: var(--accent-ink); font-weight: 650; }
164
+
165
+ /* ── main shell + topbar ───────────────────────────────────────────────── */
166
+ #main { display: flex; flex-direction: column; min-width: 0; }
167
+ .topbar {
168
+ display: flex;
169
+ align-items: center;
170
+ justify-content: space-between;
171
+ gap: 1rem;
172
+ height: 46px;
173
+ padding: 0 1.6rem;
174
+ background: rgba(255, 255, 255, .82);
175
+ backdrop-filter: saturate(1.4) blur(6px);
176
+ border-bottom: 1px solid var(--line);
177
+ position: sticky;
178
+ top: 0;
179
+ z-index: 5;
180
+ }
181
+ .crumbs { font-family: var(--font-mono); font-size: .8rem; color: var(--muted); }
182
+ .crumbs a { color: var(--muted); }
183
+ .crumbs a:hover { color: var(--accent); }
184
+ .crumbs .sep { margin: 0 .4rem; color: var(--faint); }
185
+ .crumb-here { color: var(--ink); }
186
+
187
+ .live {
188
+ display: inline-flex; align-items: center; gap: .4rem;
189
+ font-size: .72rem; letter-spacing: .06em; text-transform: uppercase;
190
+ color: var(--st-resolved); font-weight: 650;
191
+ }
192
+ .live-dot {
193
+ width: 8px; height: 8px; border-radius: 50%;
194
+ background: var(--st-resolved);
195
+ box-shadow: 0 0 0 0 rgba(47, 158, 68, .5);
196
+ animation: live-pulse 2.4s ease-out infinite;
197
+ }
198
+ @keyframes live-pulse {
199
+ 0% { box-shadow: 0 0 0 0 rgba(47, 158, 68, .45); }
200
+ 70% { box-shadow: 0 0 0 7px rgba(47, 158, 68, 0); }
201
+ 100% { box-shadow: 0 0 0 0 rgba(47, 158, 68, 0); }
202
+ }
203
+ body.just-refreshed .live-dot { background: var(--accent); box-shadow: 0 0 0 7px var(--accent-wash); }
204
+
205
+ .content { padding: 1.5rem 1.6rem 4rem; max-width: 1180px; }
206
+
207
+ /* ── badges + tags (the data tokens) ───────────────────────────────────── */
208
+ .badge {
209
+ display: inline-block;
210
+ font-family: var(--font-mono);
211
+ font-size: .72rem;
212
+ font-weight: 600;
213
+ line-height: 1.5;
214
+ padding: .05rem .42rem;
215
+ border-radius: 5px;
216
+ border: 1px solid var(--line-strong);
217
+ color: var(--ink-soft);
218
+ background: var(--surface);
219
+ white-space: nowrap;
220
+ }
221
+ /* priority */
222
+ .pri-P1 { color: #fff; background: var(--p1); border-color: var(--p1); }
223
+ .pri-P2 { color: var(--p2); background: color-mix(in srgb, var(--p2) 12%, var(--surface)); border-color: color-mix(in srgb, var(--p2) 35%, var(--surface)); }
224
+ .pri-P3 { color: var(--p3); background: var(--surface-2); border-color: var(--line-strong); }
225
+ .pri-future { color: var(--future); background: var(--surface-2); border-color: var(--line); }
226
+ /* status */
227
+ .status-open { color: var(--st-open); background: color-mix(in srgb, var(--st-open) 12%, var(--surface)); border-color: color-mix(in srgb, var(--st-open) 32%, var(--surface)); }
228
+ .status-in_progress { color: var(--st-in_progress); background: color-mix(in srgb, var(--st-in_progress) 14%, var(--surface)); border-color: color-mix(in srgb, var(--st-in_progress) 35%, var(--surface)); }
229
+ .status-resolved { color: var(--st-resolved); background: color-mix(in srgb, var(--st-resolved) 13%, var(--surface)); border-color: color-mix(in srgb, var(--st-resolved) 33%, var(--surface)); }
230
+ .status-wont_fix { color: var(--st-wont_fix); background: var(--surface-2); border-color: var(--line); text-decoration: line-through; }
231
+ /* type */
232
+ .type-issue { color: var(--ty-issue); } .type-feature { color: var(--ty-feature); }
233
+ .type-todo { color: var(--ty-todo); } .type-idea { color: var(--ty-idea); }
234
+ .type-improvement { color: var(--ty-improvement); }
235
+ .badge.gate, .gate-dogfood, .gate-security, .gate-approval, .gate-review {
236
+ color: #fff; background: var(--gate); border-color: var(--gate);
237
+ }
238
+ .badge.component { color: var(--muted); }
239
+
240
+ .tag {
241
+ display: inline-block;
242
+ font-family: var(--font-mono);
243
+ font-size: .7rem;
244
+ color: var(--muted);
245
+ background: var(--surface-2);
246
+ border: 1px solid var(--line);
247
+ border-radius: 999px;
248
+ padding: .02rem .45rem;
249
+ margin: 0 .25rem .25rem 0;
250
+ }
251
+
252
+ /* ── Radar (home) ──────────────────────────────────────────────────────── */
253
+ .radar-totals {
254
+ display: flex; flex-wrap: wrap; gap: .5rem 1.6rem;
255
+ padding: .7rem 1rem; margin-bottom: 1.4rem;
256
+ background: var(--surface); border: 1px solid var(--line);
257
+ border-radius: var(--radius); box-shadow: var(--shadow);
258
+ font-family: var(--font-mono); font-size: .8rem; color: var(--muted);
259
+ }
260
+ .radar-totals span { color: var(--ink); }
261
+ .radar-grid {
262
+ display: grid;
263
+ grid-template-columns: repeat(auto-fill, minmax(252px, 1fr));
264
+ gap: .9rem;
265
+ }
266
+ .project-card {
267
+ position: relative;
268
+ background: var(--surface);
269
+ border: 1px solid var(--line);
270
+ border-left: 3px solid var(--accent);
271
+ border-radius: var(--radius);
272
+ padding: .9rem 1rem;
273
+ box-shadow: var(--shadow);
274
+ transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
275
+ }
276
+ .project-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }
277
+ .project-card:has(.warn) { border-left-color: var(--warn); }
278
+ .project-card header { display: flex; align-items: baseline; gap: .5rem; margin-bottom: .6rem; }
279
+ .project-card header a {
280
+ font-family: var(--font-display); font-size: 1.15rem; font-weight: 600;
281
+ color: var(--ink); letter-spacing: -.01em;
282
+ }
283
+ .project-card header a::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius); }
284
+ .project-card:hover header a { color: var(--accent-ink); text-decoration: none; }
285
+ .project-card .name { color: var(--faint); font-size: .76rem; }
286
+ .project-card .counts {
287
+ display: flex; flex-wrap: wrap; gap: .35rem .7rem;
288
+ font-family: var(--font-mono); font-size: .76rem; color: var(--muted);
289
+ padding: .15rem 0;
290
+ }
291
+ .project-card .counts .open { color: var(--ink); font-weight: 600; }
292
+ .project-card .counts .warn { color: var(--warn); font-weight: 600; }
293
+
294
+ /* ── tables (items, sprint review) ─────────────────────────────────────── */
295
+ table.items {
296
+ width: 100%;
297
+ border-collapse: collapse;
298
+ background: var(--surface);
299
+ border: 1px solid var(--line);
300
+ border-radius: var(--radius);
301
+ overflow: hidden;
302
+ box-shadow: var(--shadow);
303
+ font-size: .86rem;
304
+ }
305
+ table.items thead th {
306
+ text-align: left;
307
+ font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
308
+ color: var(--muted);
309
+ padding: .55rem .7rem;
310
+ background: var(--surface-2);
311
+ border-bottom: 1px solid var(--line-strong);
312
+ }
313
+ table.items tbody td { padding: .5rem .7rem; border-bottom: 1px solid var(--line); vertical-align: top; }
314
+ table.items tbody tr:last-child td { border-bottom: 0; }
315
+ table.items tbody tr { transition: background .1s ease; }
316
+ table.items tbody tr:hover { background: var(--accent-wash); }
317
+ table.items tbody tr td:first-child { box-shadow: inset 3px 0 0 var(--line-strong); }
318
+ table.items tbody tr.pri-P1 td:first-child { box-shadow: inset 3px 0 0 var(--p1); }
319
+ table.items tbody tr.pri-P2 td:first-child { box-shadow: inset 3px 0 0 var(--p2); }
320
+ table.items tbody tr.status-resolved { color: var(--muted); }
321
+ table.items tbody tr.status-resolved td:last-child a { color: var(--muted); }
322
+ .c-id { font-family: var(--font-mono); color: var(--faint); }
323
+ .c-component { font-family: var(--font-mono); font-size: .78rem; color: var(--muted); }
324
+ table.items td:last-child a { color: var(--ink); font-weight: 500; }
325
+ table.items td:last-child a:hover { color: var(--accent-ink); }
326
+
327
+ td.cp { white-space: nowrap; }
328
+ .cp-num { font-family: var(--font-mono); font-size: .76rem; color: var(--muted); }
329
+ .cp-mini {
330
+ display: inline-block; width: 42px; height: 5px; border-radius: 3px;
331
+ background: var(--line); overflow: hidden; margin-right: .4rem; vertical-align: middle;
332
+ }
333
+ .cp-mini-fill { display: block; height: 100%; background: var(--st-resolved); }
334
+
335
+ /* sprint review summary cards (project home) */
336
+ .summary { display: flex; flex-wrap: wrap; gap: .8rem; margin-bottom: .6rem; }
337
+ .summary .stat {
338
+ flex: 1 1 150px;
339
+ background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
340
+ padding: .7rem .9rem; box-shadow: var(--shadow);
341
+ }
342
+ .summary .stat .label { display: block; font-size: .68rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
343
+ .summary .stat .value { font-family: var(--font-mono); font-size: 1.5rem; font-weight: 600; color: var(--ink); }
344
+ .slug { font-family: var(--font-mono); font-size: .85rem; color: var(--faint); font-weight: 400; }
345
+ .stale { list-style: none; padding: 0; margin: 0; }
346
+ .stale li { font-family: var(--font-mono); font-size: .84rem; padding: .2rem 0; border-bottom: 1px solid var(--line); }
347
+
348
+ /* ── item detail (document) ────────────────────────────────────────────── */
349
+ .item-detail { max-width: 760px; }
350
+ .back { font-family: var(--font-mono); font-size: .8rem; color: var(--muted); }
351
+ .item-detail .meta { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: -.3rem 0 1.1rem; }
352
+ .item-detail .source { font-family: var(--font-mono); font-size: .8rem; color: var(--muted); }
353
+ .description, .resolution, .note-body { line-height: 1.65; }
354
+ .description :is(h1, h2, h3) { font-family: var(--font-body); border: 0; padding: 0; }
355
+ .description h2 { font-size: 1.05rem; margin: 1.3rem 0 .5rem; }
356
+ .description h3 { font-size: .95rem; margin: 1.1rem 0 .4rem; }
357
+ .description p, .resolution p { margin: .6rem 0; }
358
+ .description pre, .resolution pre {
359
+ background: var(--ink); color: #e8edf6; font-family: var(--font-mono); font-size: .82rem;
360
+ padding: .8rem 1rem; border-radius: var(--radius-sm); overflow-x: auto; line-height: 1.5;
361
+ }
362
+ .description pre code, .resolution pre code { background: none; border: 0; padding: 0; color: inherit; font-size: 1em; }
363
+ .description ul, .description ol { padding-left: 1.3rem; }
364
+ .description li { margin: .2rem 0; }
365
+ .description table { border-collapse: collapse; font-size: .85rem; margin: .6rem 0; }
366
+ .description th, .description td { border: 1px solid var(--line-strong); padding: .3rem .55rem; text-align: left; }
367
+ .description blockquote { margin: .6rem 0; padding-left: .9rem; border-left: 3px solid var(--line-strong); color: var(--muted); }
368
+
369
+ /* checkpoints */
370
+ .cp-progress { height: 7px; border-radius: 4px; background: var(--line); overflow: hidden; margin-bottom: .9rem; max-width: 320px; }
371
+ .cp-progress-fill { display: block; height: 100%; background: linear-gradient(90deg, var(--st-resolved), #51cf66); }
372
+ .cp-list, .cp-steps { list-style: none; padding: 0; margin: 0; }
373
+ .cp-list .cp, .cp-steps .cp {
374
+ display: flex; align-items: flex-start; gap: .55rem;
375
+ padding: .4rem .2rem .4rem .7rem;
376
+ border-bottom: 1px solid var(--line);
377
+ border-left: 2px solid var(--line-strong);
378
+ }
379
+ .cp-list .cp:last-child, .cp-steps .cp:last-child { border-bottom: 0; }
380
+ .cp.cp-done { border-left-color: var(--st-resolved); }
381
+ .cp.cp-rejected { border-left-color: var(--st-wont_fix); }
382
+ .cp.cp-pending { border-left-color: var(--st-in_progress); }
383
+ .cp-status {
384
+ flex: none; font-family: var(--font-mono); font-size: .64rem; font-weight: 700;
385
+ letter-spacing: .04em; text-transform: uppercase; padding: .08rem .35rem; border-radius: 4px;
386
+ min-width: 4.6rem; text-align: center;
387
+ }
388
+ .cp-done .cp-status { color: var(--st-resolved); background: color-mix(in srgb, var(--st-resolved) 14%, var(--surface)); }
389
+ .cp-rejected .cp-status { color: var(--st-wont_fix); background: var(--surface-2); }
390
+ .cp-pending .cp-status { color: var(--st-in_progress); background: color-mix(in srgb, var(--st-in_progress) 14%, var(--surface)); }
391
+ .cp-box { flex: none; font-family: var(--font-mono); color: var(--muted); }
392
+ .cp-done .cp-box { color: var(--st-resolved); }
393
+ .cp-text { flex: 1; }
394
+ .cp-rejected .cp-text { color: var(--muted); text-decoration: line-through; }
395
+
396
+ /* notes timeline */
397
+ .timeline { list-style: none; padding: 0; margin: 0; border-left: 2px solid var(--line); }
398
+ .timeline .note { position: relative; padding: 0 0 1rem 1.1rem; }
399
+ .timeline .note::before {
400
+ content: ""; position: absolute; left: -5px; top: .45rem;
401
+ width: 8px; height: 8px; border-radius: 50%; background: var(--accent); border: 2px solid var(--surface);
402
+ }
403
+ .timeline time { font-family: var(--font-mono); font-size: .74rem; color: var(--faint); }
404
+
405
+ /* ── analytics ─────────────────────────────────────────────────────────── */
406
+ .tabs { display: flex; gap: .4rem; margin-bottom: 1.2rem; border-bottom: 1px solid var(--line); }
407
+ .tab {
408
+ padding: .4rem .8rem; font-weight: 600; font-size: .9rem; color: var(--muted);
409
+ border-bottom: 2px solid transparent; margin-bottom: -1px;
410
+ }
411
+ .tab:hover { color: var(--accent); text-decoration: none; }
412
+ .tab-panel { scroll-margin-top: 60px; }
413
+ .tab-panel h2:first-child { margin-top: .4rem; }
414
+ .dist-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
415
+ .bars {
416
+ background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
417
+ padding: .9rem 1rem; box-shadow: var(--shadow);
418
+ }
419
+ .bar-row { display: grid; grid-template-columns: 7rem 1fr 2.2rem; align-items: center; gap: .6rem; padding: .18rem 0; }
420
+ .bar-label { font-family: var(--font-mono); font-size: .78rem; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
421
+ .bar-track { height: 9px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 5px; overflow: hidden; }
422
+ .bar-fill { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), #5c7cfa); border-radius: 5px; min-width: 2px; }
423
+ .bar-count { font-family: var(--font-mono); font-size: .78rem; color: var(--muted); text-align: right; }
424
+
425
+ .cp-stats, .hotspots {
426
+ background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
427
+ padding: .8rem 1rem; box-shadow: var(--shadow); margin-top: 1rem;
428
+ }
429
+ .cp-stats p { font-family: var(--font-mono); font-size: .85rem; color: var(--ink-soft); margin: 0; }
430
+ .hotspots ul { margin: 0; padding-left: 1.1rem; }
431
+ .hotspots li { font-family: var(--font-mono); font-size: .82rem; color: var(--ink-soft); padding: .12rem 0; }
432
+ .uplot-mount { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem .6rem .4rem; box-shadow: var(--shadow); }
433
+
434
+ /* ── generic lists used by structural views (sprints/plans/changelog/...) ─ */
435
+ .content > ul:not([class]), .content section > ul:not([class]) { padding-left: 1.2rem; }
436
+ .content > ul:not([class]) > li, section > ul:not([class]) > li { margin: .2rem 0; }
437
+
438
+ /* plan section tree */
439
+ .plan-section { border-left: 2px solid var(--line); padding-left: .9rem; margin: .6rem 0; }
440
+ .plan-section > h3, .plan-section > h4 { margin: .3rem 0; }
441
+
442
+ /* ── filters bar + sort ────────────────────────────────────────────────── */
443
+ .filters { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: 1rem; }
444
+ .filters select, .filters input {
445
+ font: inherit; font-size: .85rem; padding: .32rem .55rem;
446
+ border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
447
+ background: var(--surface); color: var(--ink);
448
+ }
449
+ .filters input { min-width: 9rem; }
450
+ .filters input:focus, .filters select:focus { border-color: var(--accent); outline: none; }
451
+ .sort-select { margin-left: auto; color: var(--muted); }
452
+
453
+ /* ── prose measure — readable line length for spec / notes ─────────────── */
454
+ .description, .resolution, .note-body { max-width: 70ch; line-height: 1.7; }
455
+ .description p, .note-body p, .resolution p { margin: 0 0 .8rem; }
456
+ .description p:last-child, .note-body p:last-child { margin-bottom: 0; }
457
+
458
+ /* ── item prev/next pager ──────────────────────────────────────────────── */
459
+ .item-pager { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
460
+ .item-pager-sides { display: inline-flex; gap: .8rem; }
461
+ .item-pager-sides a, .pager-end { font-family: var(--font-mono); font-size: .8rem; color: var(--muted); }
462
+ .item-pager-sides a:hover { color: var(--accent-ink); }
463
+ .pager-end { color: var(--faint); }
464
+
465
+ /* ── dark theme ────────────────────────────────────────────────────────── */
466
+ html[data-theme="dark"] {
467
+ --bg: #0f141c;
468
+ --surface: #171e29;
469
+ --surface-2: #1e2733;
470
+ --ink: #e6ecf5;
471
+ --ink-soft: #b8c2d4;
472
+ --muted: #8a96ab;
473
+ --faint: #5d6a80;
474
+ --line: #29333f;
475
+ --line-strong: #3a4654;
476
+ --accent: #6f8cf5;
477
+ --accent-ink: #9db2ff;
478
+ --accent-wash: #1d2740;
479
+ --p1: #ff6b6b; --p2: #4dabf7; --p3: #8a96ab; --future: #5d6a80;
480
+ --st-open: #4dabf7; --st-in_progress: #ffa94d; --st-resolved: #51cf66; --st-wont_fix: #6d7a90;
481
+ --ty-issue: #ff6b6b; --ty-feature: #748ffc; --ty-todo: #8a96ab; --ty-idea: #da77f2; --ty-improvement: #3bc9db;
482
+ --gate: #ff922b;
483
+ --warn: #ffa94d;
484
+ --shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 4px 14px rgba(0, 0, 0, .35);
485
+ --shadow-hover: 0 2px 6px rgba(0, 0, 0, .45), 0 12px 28px rgba(0, 0, 0, .5);
486
+ color-scheme: dark;
487
+ }
488
+ html[data-theme="dark"] .topbar { background: rgba(23, 30, 41, .82); }
489
+ html[data-theme="dark"] .pri-P1 { color: #0f141c; }
490
+ html[data-theme="dark"] .badge.gate, html[data-theme="dark"] .gate-dogfood,
491
+ html[data-theme="dark"] .gate-security, html[data-theme="dark"] .gate-approval,
492
+ html[data-theme="dark"] .gate-review { color: #0f141c; }
493
+ html[data-theme="dark"] .description pre, html[data-theme="dark"] .resolution pre { background: #0a0e15; }
494
+
495
+ /* ── theme toggle ──────────────────────────────────────────────────────── */
496
+ .topbar-actions { display: flex; align-items: center; gap: .9rem; }
497
+ .theme-toggle {
498
+ display: inline-grid; place-items: center; width: 30px; height: 30px; padding: 0;
499
+ border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface);
500
+ color: var(--ink-soft); cursor: pointer; font-size: .95rem; line-height: 1;
501
+ }
502
+ .theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
503
+ .theme-sun { display: none; }
504
+ .theme-moon { display: inline; }
505
+ html[data-theme="dark"] .theme-sun { display: inline; }
506
+ html[data-theme="dark"] .theme-moon { display: none; }
507
+
508
+ /* ── overflow + long-token wrapping (no more runaway width) ─────────────── */
509
+ .table-wrap { overflow-x: auto; border-radius: var(--radius); }
510
+ .cp-text, .entry, .member-title, .goal, .section-heading, .decision .title,
511
+ .bar-label, table.items td:last-child { overflow-wrap: anywhere; }
512
+ .prompt {
513
+ font-family: var(--font-mono); font-size: .8rem; line-height: 1.5; margin: 0;
514
+ background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
515
+ padding: .6rem .8rem; white-space: pre-wrap; word-break: break-word;
516
+ }
517
+
518
+ /* ── sprint detail: members + steps ────────────────────────────────────── */
519
+ .sprint-meta, .plan-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; margin: -.3rem 0 .7rem; }
520
+ .sprint-meta .code, .sprint-meta .kind, .plan-meta .kind { font-family: var(--font-mono); font-size: .76rem; color: var(--muted); }
521
+ .goal { font-style: italic; color: var(--ink-soft); margin: .2rem 0 .4rem; }
522
+ .rollup { font-family: var(--font-mono); font-size: .8rem; color: var(--muted); }
523
+ .member-items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .55rem; }
524
+ .member { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: .6rem .85rem; box-shadow: var(--shadow); }
525
+ .member-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem; }
526
+ .member-id { font-family: var(--font-mono); font-size: .8rem; color: var(--faint); }
527
+ .member-title { flex: 1 1 100%; color: var(--ink); font-weight: 500; margin-top: .15rem; }
528
+ .member-title:hover { color: var(--accent-ink); }
529
+ details.steps { margin-top: .45rem; }
530
+ details.steps > summary, .handoff-prompt > summary {
531
+ cursor: pointer; list-style: none; color: var(--muted);
532
+ font-size: .72rem; letter-spacing: .04em; text-transform: uppercase; padding: .15rem 0;
533
+ }
534
+ details.steps > summary::-webkit-details-marker, .handoff-prompt > summary::-webkit-details-marker { display: none; }
535
+ details.steps > summary::before { content: "▸ "; }
536
+ details.steps[open] > summary::before { content: "▾ "; }
537
+
538
+ /* ── sprint / plan / item lists ────────────────────────────────────────── */
539
+ .sprint-model { margin-bottom: 1.3rem; }
540
+ .sprint-list, .plan-list, .item-list { list-style: none; padding: 0; margin: 0; }
541
+ .sprint-list li, .plan-list li {
542
+ display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem;
543
+ padding: .5rem .2rem; border-bottom: 1px solid var(--line);
544
+ }
545
+ .sprint-list li a, .plan-list li a { font-weight: 600; color: var(--ink); }
546
+ .sprint-list li a:hover, .plan-list li a:hover { color: var(--accent-ink); }
547
+ .sprint-list .code, .sprint-list .kind, .plan-list .kind { font-family: var(--font-mono); font-size: .74rem; color: var(--faint); }
548
+ .item-list li { padding: .35rem 0; border-bottom: 1px solid var(--line); }
549
+
550
+ /* ── handoffs ──────────────────────────────────────────────────────────── */
551
+ .handoff-group { margin-bottom: 1.6rem; }
552
+ .handoff-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .7rem; }
553
+ .handoff {
554
+ background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--st-in_progress);
555
+ border-radius: var(--radius); padding: .7rem .9rem; box-shadow: var(--shadow);
556
+ }
557
+ .handoff.is-consumed { border-left-color: var(--line-strong); }
558
+ .handoff-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
559
+ .handoff-head + .prompt { margin-top: .5rem; }
560
+ .handoff-by { font-weight: 600; color: var(--ink-soft); }
561
+ .handoff-at { font-family: var(--font-mono); font-size: .74rem; color: var(--faint); }
562
+ .badge.scope { color: var(--accent-ink); background: var(--accent-wash); border-color: transparent; }
563
+
564
+ /* ── decisions ─────────────────────────────────────────────────────────── */
565
+ .decision-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .5rem; }
566
+ .decision {
567
+ background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--accent);
568
+ border-radius: var(--radius); padding: .55rem .85rem; box-shadow: var(--shadow);
569
+ display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem;
570
+ }
571
+ .decision .ref { font-family: var(--font-mono); font-size: .76rem; color: var(--accent-ink); font-weight: 600; }
572
+ .decision .kind { font-family: var(--font-mono); font-size: .74rem; color: var(--faint); }
573
+ .decision .title { color: var(--ink); font-weight: 500; }
574
+ .decision .supersede { flex-basis: 100%; font-size: .8rem; color: var(--muted); }
575
+
576
+ /* ── changelog ─────────────────────────────────────────────────────────── */
577
+ .clog-version { margin-bottom: 1.2rem; }
578
+ .clog-entries { list-style: none; padding: 0; margin: 0; }
579
+ .clog-entry { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; padding: .42rem 0; border-bottom: 1px solid var(--line); }
580
+ .clog-entry .entry { flex: 1 1 55%; }
581
+ .clog-entry .component { font-family: var(--font-mono); font-size: .74rem; color: var(--muted); }
582
+ .clog-entry .rel { font-family: var(--font-mono); font-size: .76rem; }
583
+ .badge.cat { text-transform: uppercase; }
584
+ .cat-added { color: var(--st-resolved); } .cat-fixed { color: var(--st-open); }
585
+ .cat-changed { color: var(--warn); } .cat-removed { color: var(--p1); }
586
+
587
+ /* ── plan section tree ─────────────────────────────────────────────────── */
588
+ .plan-tree, .section-children { list-style: none; padding: 0; margin: 0; }
589
+ .section-children { margin: .15rem 0 .15rem 1rem; padding-left: .9rem; border-left: 1px solid var(--line); }
590
+ .plan-section { margin: .3rem 0; }
591
+ .section-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem; padding: .12rem 0; }
592
+ .section-type { font-family: var(--font-mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .05em; color: var(--faint); }
593
+ .section-heading { font-weight: 600; color: var(--ink); }
594
+ .plan-section.depth-1 > .section-head .section-heading { font-size: 1.02rem; }
595
+ .badge.warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, var(--surface)); border-color: color-mix(in srgb, var(--warn) 32%, var(--surface)); }
596
+
597
+ /* ── responsive ────────────────────────────────────────────────────────── */
598
+ @media (max-width: 820px) {
599
+ body { grid-template-columns: 1fr; }
600
+ .sidebar { position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--line); }
601
+ table.items thead th { top: 0; }
602
+ .content { padding: 1.1rem; }
603
+ }
604
+
605
+ /* ── a11y floor ────────────────────────────────────────────────────────── */
606
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
607
+ @media (prefers-reduced-motion: reduce) {
608
+ * { animation: none !important; transition: none !important; }
609
+ }
dct/web/static/app.js ADDED
@@ -0,0 +1,62 @@
1
+ // dct web viewer — client glue.
2
+ // v1: intentionally empty; the SSE refresh wiring lives inline in base.html.
3
+ // Task 8 appends the uPlot chart initialization below.
4
+
5
+ // --- Velocity charts (uPlot) -------------------------------------------------
6
+ function dctInitVelocity() {
7
+ var mount = document.getElementById("velocity-chart");
8
+ var dataEl = document.getElementById("velocity-data");
9
+ if (!mount || !dataEl || typeof uPlot === "undefined") return;
10
+ if (mount.dataset.rendered === "1") return;
11
+
12
+ var v;
13
+ try {
14
+ v = JSON.parse(dataEl.textContent);
15
+ } catch (e) {
16
+ return;
17
+ }
18
+
19
+ // Merge created/resolved per-week series onto a shared, sorted week axis.
20
+ var byWeek = {};
21
+ (v.created_per_week || []).forEach(function (r) {
22
+ (byWeek[r.week] = byWeek[r.week] || {}).created = r.count;
23
+ });
24
+ (v.resolved_per_week || []).forEach(function (r) {
25
+ (byWeek[r.week] = byWeek[r.week] || {}).resolved = r.count;
26
+ });
27
+
28
+ var weeks = Object.keys(byWeek).sort();
29
+ if (!weeks.length) {
30
+ mount.dataset.rendered = "1";
31
+ return;
32
+ }
33
+
34
+ var xs = weeks.map(function (_, i) { return i; });
35
+ var ysCreated = weeks.map(function (w) { return byWeek[w].created || 0; });
36
+ var ysResolved = weeks.map(function (w) { return byWeek[w].resolved || 0; });
37
+
38
+ var opts = {
39
+ width: mount.clientWidth || 720,
40
+ height: 260,
41
+ scales: { x: { time: false } },
42
+ axes: [
43
+ {
44
+ values: function (u, vals) {
45
+ return vals.map(function (i) { return weeks[i] || ""; });
46
+ }
47
+ },
48
+ {}
49
+ ],
50
+ series: [
51
+ {},
52
+ { label: "created", stroke: "#d97706" },
53
+ { label: "resolved", stroke: "#2563eb" }
54
+ ]
55
+ };
56
+
57
+ new uPlot(opts, [xs, ysCreated, ysResolved], mount);
58
+ mount.dataset.rendered = "1";
59
+ }
60
+
61
+ document.addEventListener("DOMContentLoaded", dctInitVelocity);
62
+ document.addEventListener("htmx:afterSwap", dctInitVelocity);
@@ -0,0 +1,10 @@
1
+ Vendored third-party assets (no build step — committed as-is).
2
+
3
+ htmx 1.9.12 — BSD 2-Clause License — https://github.com/bigskysoftware/htmx
4
+ htmx.min.js <- dist/htmx.min.js @ tag v1.9.12
5
+ source: https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js
6
+
7
+ uPlot 1.6.31 — MIT License — https://github.com/leeoniya/uPlot
8
+ uPlot.iife.min.js <- dist/uPlot.iife.min.js @ tag 1.6.31
9
+ uPlot.min.css <- dist/uPlot.min.css @ tag 1.6.31
10
+ source: https://raw.githubusercontent.com/leeoniya/uPlot/1.6.31/dist/