@felixdotgo/aidlc-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/DEVELOPMENT.md +13 -26
  2. package/README.md +35 -39
  3. package/dist/assets/.agents/aidlc/orchestrator.md +1 -1
  4. package/dist/assets/.agents/aidlc/schemas/state.schema.json +2 -0
  5. package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +94 -48
  6. package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -4
  7. package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +21 -5
  8. package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +52 -78
  9. package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +88 -14
  10. package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
  11. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +20 -5
  12. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +10 -1
  13. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +2 -1
  14. package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +3 -2
  15. package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +9 -3
  16. package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
  17. package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +11 -3
  18. package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +67 -61
  19. package/dist/assets/.agents/aidlc/scripts/lib/loop-leases.mjs +268 -0
  20. package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +78 -17
  21. package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +42 -10
  22. package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +66 -9
  23. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +16 -56
  24. package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +12 -6
  25. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +14 -2
  26. package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-log.mjs +158 -0
  27. package/dist/assets/.agents/aidlc/scripts/lib/repo-head.mjs +22 -0
  28. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +11 -0
  29. package/dist/assets/.agents/aidlc/scripts/lib/task-runtime-migrate.mjs +75 -0
  30. package/dist/assets/.agents/aidlc/scripts/lib/task-runtime.mjs +97 -0
  31. package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +87 -4
  32. package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
  33. package/dist/assets/.agents/aidlc/scripts/loop.sh +71 -0
  34. package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +10 -4
  35. package/dist/assets/.agents/aidlc/scripts/state.mjs +16 -8
  36. package/dist/assets/ui/changes.js +83 -0
  37. package/dist/assets/ui/common.js +664 -0
  38. package/dist/assets/ui/dashboard.css +281 -59
  39. package/dist/assets/ui/dashboard.js +77 -0
  40. package/dist/assets/ui/detail.js +159 -0
  41. package/dist/assets/ui/live-log.js +332 -0
  42. package/dist/services/aidlc-coordination/package.json +1 -1
  43. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +11 -0
  44. package/dist/src/cli/argv.js +10 -4
  45. package/dist/src/cli/commands.js +52 -22
  46. package/dist/src/cli/loop-launcher.d.ts +47 -0
  47. package/dist/src/cli/loop-launcher.js +322 -0
  48. package/dist/src/cli.js +3 -0
  49. package/dist/src/installer.js +4 -2
  50. package/dist/src/model/records.d.ts +4 -0
  51. package/dist/src/state/validate.js +5 -0
  52. package/dist/src/task-ui.d.ts +31 -52
  53. package/dist/src/task-ui.js +25 -1562
  54. package/dist/src/ui/assets.d.ts +14 -0
  55. package/dist/src/ui/assets.js +45 -0
  56. package/dist/src/ui/changes.d.ts +83 -0
  57. package/dist/src/ui/changes.js +498 -0
  58. package/dist/src/ui/containers.d.ts +71 -0
  59. package/dist/src/ui/containers.js +214 -0
  60. package/dist/src/ui/daemon.d.ts +75 -0
  61. package/dist/src/ui/daemon.js +232 -0
  62. package/dist/src/ui/data.d.ts +42 -0
  63. package/dist/src/ui/data.js +89 -0
  64. package/dist/src/ui/docs.d.ts +13 -0
  65. package/dist/src/ui/docs.js +39 -0
  66. package/dist/src/ui/files.d.ts +3 -0
  67. package/dist/src/ui/files.js +20 -0
  68. package/dist/src/ui/http.d.ts +14 -0
  69. package/dist/src/ui/http.js +80 -0
  70. package/dist/src/ui/live.d.ts +5 -0
  71. package/dist/src/ui/live.js +43 -0
  72. package/dist/src/ui/markdown.d.ts +2 -0
  73. package/dist/src/ui/markdown.js +127 -0
  74. package/dist/src/ui/options.d.ts +48 -0
  75. package/dist/src/ui/options.js +81 -0
  76. package/dist/src/ui/security.d.ts +34 -0
  77. package/dist/src/ui/security.js +38 -0
  78. package/dist/src/ui/server.d.ts +39 -0
  79. package/dist/src/ui/server.js +347 -0
  80. package/dist/src/ui/sse.d.ts +14 -0
  81. package/dist/src/ui/sse.js +281 -0
  82. package/dist/src/ui/views/board.d.ts +7 -0
  83. package/dist/src/ui/views/board.js +131 -0
  84. package/dist/src/ui/views/changes.d.ts +19 -0
  85. package/dist/src/ui/views/changes.js +154 -0
  86. package/dist/src/ui/views/components.d.ts +56 -0
  87. package/dist/src/ui/views/components.js +113 -0
  88. package/dist/src/ui/views/docs.d.ts +15 -0
  89. package/dist/src/ui/views/docs.js +64 -0
  90. package/dist/src/ui/views/layout.d.ts +44 -0
  91. package/dist/src/ui/views/layout.js +233 -0
  92. package/dist/src/ui/views/task.d.ts +25 -0
  93. package/dist/src/ui/views/task.js +320 -0
  94. package/dist/src/upgrade.js +5 -2
  95. package/dist/src/workflow.d.ts +4 -0
  96. package/dist/src/workflow.js +33 -1
  97. package/docs/README.md +48 -33
  98. package/docs/architecture.md +101 -38
  99. package/docs/command-reference.md +269 -93
  100. package/docs/configuration.md +62 -42
  101. package/docs/coordination-service.md +85 -36
  102. package/docs/design-decisions.md +35 -31
  103. package/docs/development.md +111 -61
  104. package/docs/loop-local-run.md +135 -402
  105. package/docs/loop-runbook.md +84 -105
  106. package/docs/operating-workflow.md +55 -42
  107. package/docs/testing-and-release.md +70 -51
  108. package/docs/troubleshooting.md +165 -69
  109. package/package.json +1 -1
@@ -4,12 +4,16 @@
4
4
  * - Keeps Bulma's default palette (primary teal, link blue).
5
5
  * - Flat, enterprise look: white canvas, 1px hairline borders, small radii,
6
6
  * no elevation except overlays (drawer, modal, toast).
7
- * - Dark mode follows the system automatically: Bulma switches its scheme
8
- * variables inside `@media (prefers-color-scheme: dark)`, and every colour
9
- * below is expressed through `--bulma-*` variables so it follows along.
10
- * Never override scheme lightness (`--bulma-*-l`) on a bare `:root` here:
11
- * it has the same specificity as Bulma's media-query block and would pin
12
- * one scheme.
7
+ * - Dark mode: `html[data-theme="dark"|"light"]` (set before first paint from
8
+ * Settings → Appearance, see layout.ts) pins a scheme through Bulma's own
9
+ * `[data-theme]` blocks; without it (`system`) Bulma follows
10
+ * `@media (prefers-color-scheme: dark)`. Every colour below is expressed
11
+ * through `--bulma-*` variables so it follows along, and the few dashboard
12
+ * variables that differ per scheme are declared twice: once for the explicit
13
+ * dark theme and once for the system preference when no light theme is
14
+ * pinned. Never override scheme lightness (`--bulma-*-l`) on a bare `:root`
15
+ * here: it has the same specificity as Bulma's media-query block and would
16
+ * pin one scheme.
13
17
  * - Custom components use the `ad-` prefix to avoid clashing with Bulma.
14
18
  */
15
19
 
@@ -36,8 +40,16 @@
36
40
  --ad-transition: 160ms ease;
37
41
  }
38
42
 
43
+ /* Dark-scheme variables: explicit dark theme, or the OS preference unless light is pinned. */
44
+ html[data-theme="dark"] {
45
+ --ad-shadow-lg: 0 12px 32px -6px hsla(0, 0%, 0%, 0.7), 0 0 0 1px hsla(0, 0%, 100%, 0.06);
46
+ --ad-surface: var(--bulma-scheme-main);
47
+ --ad-canvas: var(--bulma-scheme-main);
48
+ --ad-subtle: var(--bulma-scheme-main-bis);
49
+ --ad-log-bg: hsl(222, 25%, 6%);
50
+ }
39
51
  @media (prefers-color-scheme: dark) {
40
- :root {
52
+ html:not([data-theme="light"]) {
41
53
  --ad-shadow-lg: 0 12px 32px -6px hsla(0, 0%, 0%, 0.7), 0 0 0 1px hsla(0, 0%, 100%, 0.06);
42
54
  --ad-surface: var(--bulma-scheme-main);
43
55
  --ad-canvas: var(--bulma-scheme-main);
@@ -45,6 +57,8 @@
45
57
  --ad-log-bg: hsl(222, 25%, 6%);
46
58
  }
47
59
  }
60
+ html[data-theme="dark"] { color-scheme: dark; }
61
+ html[data-theme="light"] { color-scheme: light; }
48
62
 
49
63
  html { background-color: var(--ad-canvas); scroll-padding-top: 5rem; }
50
64
  body { min-height: 100vh; font-feature-settings: "cv11", "ss01"; -webkit-font-smoothing: antialiased; }
@@ -99,41 +113,54 @@ kbd, .ad-kbd {
99
113
  .button.ad-soft.is-link { background: var(--bulma-link-soft); color: var(--bulma-link-bold); }
100
114
  .button.ad-soft.is-danger { background: var(--bulma-danger-soft); color: var(--bulma-danger-bold); }
101
115
  .button.ad-soft:hover { filter: brightness(0.96); }
102
- @media (prefers-color-scheme: dark) { .button.ad-soft:hover { filter: brightness(1.2); } }
103
-
104
- /* ---------- Stat cards ---------- */
105
- .ad-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: 0.875rem; margin-bottom: 1.5rem; }
106
- .ad-stat {
107
- display: flex; flex-direction: column; gap: 0.25rem; padding: 0.9rem 1rem; border-radius: var(--ad-radius);
108
- background: var(--ad-surface); box-shadow: var(--ad-ring), var(--ad-shadow-sm); position: relative; overflow: hidden;
109
- color: inherit; text-align: left; border: 0; font: inherit; cursor: pointer; transition: box-shadow var(--ad-transition), background var(--ad-transition);
110
- }
111
- .ad-stat:hover { box-shadow: var(--ad-ring-strong); background: var(--ad-subtle); }
112
- .ad-stat[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--ad-accent, var(--bulma-link)); }
113
- .ad-stat::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--ad-accent, var(--bulma-border)); }
114
- .ad-stat-label { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ad-muted); display: flex; align-items: center; gap: 0.4rem; }
115
- .ad-stat-label .ad-dot { color: var(--ad-accent, var(--ad-muted)); }
116
- .ad-stat-value { font-size: 1.6rem; font-weight: 700; line-height: 1.1; color: var(--bulma-text-strong); font-variant-numeric: tabular-nums; }
117
- .ad-stat.is-info { --ad-accent: var(--bulma-info); }
118
- .ad-stat.is-warning { --ad-accent: var(--bulma-warning); }
119
- .ad-stat.is-success { --ad-accent: var(--bulma-success); }
120
- .ad-stat.is-danger { --ad-accent: var(--bulma-danger); }
121
- .ad-stat.is-primary { --ad-accent: var(--bulma-primary); }
122
-
123
- /* ---------- Needs your review ---------- */
124
- .ad-attention {
125
- margin-bottom: 1.5rem; border-radius: var(--ad-radius); overflow: hidden;
126
- background: var(--ad-surface); box-shadow: 0 0 0 1px var(--bulma-warning), var(--ad-shadow-sm);
127
- }
128
- .ad-attention-head {
129
- display: flex; align-items: center; gap: 0.6rem; padding: 0.7rem 1rem;
130
- background: var(--bulma-warning-soft); color: var(--bulma-warning-bold); font-weight: 700; font-size: 0.9rem;
131
- }
132
- .ad-attention-head .ad-count { margin-left: auto; }
133
- .ad-attention-list { list-style: none; margin: 0; padding: 0; }
134
- .ad-attention-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 1rem; border-top: 1px solid var(--bulma-border-weak); flex-wrap: wrap; }
135
- .ad-attention-item .ad-title { font-weight: 600; color: var(--bulma-text-strong); flex: 1 1 14rem; min-width: 0; }
136
- .ad-attention-item .ad-actions { margin-left: auto; }
116
+ html[data-theme="dark"] .button.ad-soft:hover { filter: brightness(1.2); }
117
+ @media (prefers-color-scheme: dark) { html:not([data-theme="light"]) .button.ad-soft:hover { filter: brightness(1.2); } }
118
+
119
+ /* ---------- Live regions (morphed in place; never a layout box) ---------- */
120
+ .ad-live { display: contents; }
121
+ #live-status { animation: ad-pulse 1.8s ease-in-out infinite; }
122
+ a.ad-pill:hover { filter: brightness(0.96); }
123
+
124
+ /* ---------- Kanban / list view (html[data-view] is set before first paint) ---------- */
125
+ #list-view { display: none; }
126
+ html[data-view="list"] #list-view { display: block; }
127
+ html[data-view="list"] #kanban-view { display: none; }
128
+
129
+ /* ---------- Header notifications (tasks waiting at a gate) ---------- */
130
+ .ad-notify { position: relative; }
131
+ .ad-notify-toggle { position: relative; }
132
+ .ad-notify-toggle.has-reviews { color: var(--bulma-warning-bold); background: var(--bulma-warning-soft); border-color: transparent; }
133
+ .ad-notify-badge {
134
+ position: absolute; top: -0.4rem; right: -0.4rem; min-width: 1.1rem; height: 1.1rem; padding: 0 0.3rem;
135
+ display: inline-grid; place-items: center; border-radius: 999px; font-size: 0.65rem; font-weight: 700; line-height: 1;
136
+ background: var(--bulma-warning); color: var(--bulma-warning-invert); box-shadow: 0 0 0 2px var(--ad-surface);
137
+ font-variant-numeric: tabular-nums;
138
+ }
139
+ .ad-notify-panel {
140
+ position: absolute; right: 0; top: calc(100% + 0.5rem); z-index: 40; width: min(24rem, calc(100vw - 2rem));
141
+ max-height: min(28rem, 70vh); overflow-y: auto; border-radius: var(--ad-radius);
142
+ background: var(--ad-surface); box-shadow: var(--ad-ring), var(--ad-shadow-lg);
143
+ }
144
+ .ad-notify-head {
145
+ position: sticky; top: 0; display: flex; align-items: center; gap: 0.5rem; padding: 0.65rem 0.9rem;
146
+ background: var(--ad-surface); border-bottom: 1px solid var(--bulma-border-weak); font-size: 0.85rem; color: var(--bulma-text-strong);
147
+ }
148
+ .ad-notify-head .ad-count { margin-left: auto; }
149
+ .ad-notify-list { list-style: none; margin: 0; padding: 0; }
150
+ .ad-notify-list li + li { border-top: 1px solid var(--bulma-border-weak); }
151
+ .ad-notify-item { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.65rem 0.9rem; color: inherit; }
152
+ .ad-notify-item:hover, .ad-notify-item:focus-visible { background: var(--ad-subtle); color: inherit; }
153
+ .ad-notify-item[aria-current="page"] { box-shadow: inset 3px 0 0 var(--bulma-warning); }
154
+ .ad-notify-row { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
155
+ .ad-notify-time { margin-left: auto; font-size: 0.72rem; color: var(--ad-muted); }
156
+ .ad-notify-title { font-weight: 600; color: var(--bulma-text-strong); font-size: 0.875rem; overflow-wrap: anywhere; }
157
+ .ad-notify-id { font-size: 0.72rem; color: var(--ad-muted); background: transparent; padding: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
158
+ .ad-notify-more {
159
+ display: block; width: 100%; padding: 0.6rem 0.9rem; border: 0; border-top: 1px solid var(--bulma-border-weak);
160
+ background: transparent; color: var(--bulma-link); font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer; text-align: center;
161
+ }
162
+ .ad-notify-more:hover { background: var(--ad-subtle); }
163
+ .ad-notify-empty { padding: 1rem 0.9rem; font-size: 0.85rem; color: var(--ad-muted); }
137
164
 
138
165
  /* ---------- Toolbar ---------- */
139
166
  .ad-toolbar { display: flex; align-items: center; gap: 0.625rem; margin-bottom: 1rem; flex-wrap: wrap; }
@@ -176,12 +203,9 @@ kbd, .ad-kbd {
176
203
  }
177
204
  .ad-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; border-radius: var(--ad-radius) 0 0 var(--ad-radius); background: var(--ad-status-color, var(--bulma-border)); }
178
205
  .ad-card:hover { box-shadow: var(--ad-ring-strong); background: var(--ad-subtle); }
206
+ .ad-card[data-card-href] { cursor: pointer; }
179
207
  .ad-card.is-attention { box-shadow: 0 0 0 1px var(--bulma-warning); }
180
- .ad-card-top { display: flex; align-items: center; gap: 0.5rem; }
181
- .ad-card-id { font-family: var(--bulma-family-code); font-size: 0.72rem; color: var(--ad-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
182
- .ad-card-id:hover { color: var(--bulma-link); }
183
- .ad-card-top .ad-pill { margin-left: auto; }
184
- .ad-card-title { font-weight: 600; font-size: 0.92rem; line-height: 1.35; color: var(--bulma-text-strong); word-break: break-word; }
208
+ .ad-card-title { font-weight: 600; font-size: 0.95rem; line-height: 1.4; color: var(--bulma-text-strong); word-break: break-word; }
185
209
  .ad-card-title a { color: inherit; }
186
210
  .ad-card-title a:hover { color: var(--bulma-link); }
187
211
  .ad-card-meta { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
@@ -213,7 +237,7 @@ kbd, .ad-kbd {
213
237
  .ad-stepper li:last-child::after { display: none; }
214
238
  .ad-stepper li.is-complete::after { background: var(--bulma-primary); }
215
239
  .ad-stepper-marker {
216
- flex: none; display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 999px;
240
+ position: relative; flex: none; display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 999px;
217
241
  font-size: 0.72rem; font-weight: 700; background: var(--ad-surface); color: var(--ad-muted); box-shadow: inset 0 0 0 2px var(--bulma-border);
218
242
  }
219
243
  .ad-stepper li.is-complete .ad-stepper-marker { background: var(--bulma-primary); color: var(--bulma-primary-invert); box-shadow: none; }
@@ -336,6 +360,55 @@ kbd, .ad-kbd {
336
360
  .ad-log-line.is-agent .ad-log-type { color: hsl(171, 80%, 55%); }
337
361
  .ad-log-muted { color: hsl(220, 10%, 50%); font-style: italic; }
338
362
 
363
+ /* Readable live log (live-log.js): `time · actor · sentence`, grouped by turn.
364
+ * The terminal stays dark in both schemes, so tones are fixed light hues. */
365
+ .ad-log-line.is-unparsed { display: block; color: hsl(220, 10%, 60%); }
366
+ .ad-log-line.is-error .ad-log-text { color: hsl(0, 85%, 72%); }
367
+ .ad-log-line.is-warn .ad-log-text { color: hsl(40, 90%, 66%); }
368
+ .ad-log-line.is-ok .ad-log-text { color: hsl(140, 50%, 66%); }
369
+ .ad-log-line.is-muted .ad-log-text { color: hsl(220, 10%, 58%); }
370
+ .ad-log-actor {
371
+ align-self: start; max-width: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
372
+ padding: 0 0.4rem; border-radius: var(--bulma-radius-small); font-size: 0.7rem; line-height: 1.5rem;
373
+ color: hsl(220, 14%, 88%); background: hsla(0, 0%, 100%, 0.08);
374
+ }
375
+ .ad-log-line .ad-log-actor { line-height: 1.2rem; margin-top: 0.1rem; }
376
+ .ad-log-actor.is-lead { color: hsl(171, 80%, 62%); background: hsla(171, 80%, 45%, 0.16); }
377
+ .ad-log-actor.is-loop { color: hsl(220, 10%, 64%); background: hsla(220, 10%, 60%, 0.12); }
378
+ .ad-log-actor.is-worker-0 { color: hsl(217, 90%, 74%); background: hsla(217, 90%, 60%, 0.18); }
379
+ .ad-log-actor.is-worker-1 { color: hsl(271, 80%, 78%); background: hsla(271, 80%, 60%, 0.18); }
380
+ .ad-log-actor.is-worker-2 { color: hsl(28, 90%, 70%); background: hsla(28, 90%, 55%, 0.18); }
381
+ .ad-log-actor.is-worker-3 { color: hsl(330, 75%, 76%); background: hsla(330, 75%, 60%, 0.18); }
382
+ .ad-log-code { background: hsla(0, 0%, 100%, 0.07); color: inherit; padding: 0 0.3em; font-size: inherit; }
383
+ .ad-log-link { color: hsl(200, 85%, 70%); text-decoration: underline; }
384
+ .ad-log-link:hover { color: hsl(200, 85%, 80%); }
385
+ .ad-log-text.is-json { color: hsl(220, 10%, 72%); }
386
+ .ad-log-more > summary { cursor: pointer; list-style: none; }
387
+ .ad-log-more > summary::-webkit-details-marker { display: none; }
388
+ .ad-log-more > summary::after { content: " ▸ more"; color: hsl(220, 10%, 52%); font-size: 0.9em; }
389
+ .ad-log-more[open] > summary::after { content: " ▾ less"; }
390
+ .ad-log-full { margin-top: 0.25rem; padding-left: 0.6rem; border-left: 2px solid hsla(0, 0%, 100%, 0.12); color: hsl(220, 12%, 78%); }
391
+ .ad-log-turn { margin: 0.6rem 0 0.35rem; }
392
+ .ad-log-turn:first-child { margin-top: 0; }
393
+ .ad-log-turn-head {
394
+ display: grid; grid-template-columns: auto auto 1fr; gap: 0.75rem; align-items: center;
395
+ padding: 0.15rem 0; border-top: 1px solid hsla(0, 0%, 100%, 0.08); color: hsl(220, 14%, 92%); font-weight: 600;
396
+ }
397
+ .ad-log-turn-body { padding-left: 1.1rem; border-left: 2px solid hsla(0, 0%, 100%, 0.08); margin-left: 0.3rem; }
398
+ .ad-log-switch { display: inline-flex; }
399
+ .ad-log-switch button:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
400
+ .ad-log-switch button:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-width: 0; }
401
+ .ad-terminal-tools a { font-size: 0.72rem; color: hsl(200, 85%, 70%); }
402
+ .ad-terminal-tools a:hover { color: hsl(200, 85%, 80%); }
403
+ .ad-terminal-head .ad-log-state { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
404
+ .ad-live-log.is-compact { margin: 0 0 1rem; }
405
+ .log-panel.is-compact { height: auto; max-height: 15rem; font-size: 0.74rem; }
406
+ @media screen and (max-width: 768px) {
407
+ .ad-live-log .ad-terminal-head { flex-wrap: wrap; }
408
+ .ad-log-line, .ad-log-turn-head { grid-template-columns: auto 1fr; }
409
+ .ad-log-line .ad-log-time, .ad-log-turn-head .ad-log-time { display: none; }
410
+ }
411
+
339
412
  /* ---------- Toasts ---------- */
340
413
  .ad-toasts { position: fixed; right: 1rem; bottom: 1rem; z-index: 100; display: flex; flex-direction: column; gap: 0.5rem; max-width: min(26rem, calc(100vw - 2rem)); pointer-events: none; }
341
414
  .ad-toast {
@@ -362,21 +435,28 @@ kbd, .ad-kbd {
362
435
  .modal-card-body { background: var(--ad-surface); }
363
436
  .modal-card-foot { justify-content: flex-end; gap: 0.5rem; }
364
437
  .modal-card-foot .ad-foot-start { margin-right: auto; font-size: 0.8rem; color: var(--ad-muted); }
365
- .modal.is-active .modal-card { animation: ad-modal-in 140ms ease-out; }
366
- @keyframes ad-modal-in { from { opacity: 0; transform: translateY(0.5rem); } to { opacity: 1; transform: none; } }
438
+ .modal.is-active .modal-background { animation: ad-fade-in 180ms ease-out; }
439
+ .modal.is-active .modal-card { animation: ad-modal-in 200ms cubic-bezier(0.22, 1, 0.36, 1); }
440
+ .modal.is-closing .modal-background { animation: ad-fade-out 160ms ease-in forwards; }
441
+ .modal.is-closing .modal-card { animation: ad-modal-out 160ms ease-in forwards; }
442
+ @keyframes ad-modal-in { from { opacity: 0; transform: translateY(0.75rem) scale(0.97); } to { opacity: 1; transform: none; } }
443
+ @keyframes ad-modal-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(0.5rem) scale(0.98); } }
444
+ @keyframes ad-fade-in { from { opacity: 0; } to { opacity: 1; } }
445
+ @keyframes ad-fade-out { from { opacity: 1; } to { opacity: 0; } }
367
446
  .ad-field-hint { display: flex; justify-content: space-between; gap: 1rem; margin-top: 0.4rem; font-size: 0.75rem; color: var(--ad-muted); }
368
447
  .ad-field-hint .is-over { color: var(--bulma-danger); font-weight: 600; }
369
448
 
370
449
  /* ---------- Task side panel (drawer, JIRA-style) ---------- */
371
450
  .ad-drawer { position: fixed; inset: 0; z-index: 40; }
372
- .ad-drawer-backdrop { position: absolute; inset: 0; background: hsla(221, 39%, 8%, 0.35); opacity: 0; transition: opacity 200ms ease; }
451
+ .ad-drawer-backdrop { position: absolute; inset: 0; background: hsla(221, 39%, 8%, 0.35); opacity: 0; transition: opacity 240ms ease; }
373
452
  .ad-drawer-panel {
374
453
  position: absolute; top: 0; right: 0; height: 100%; width: clamp(40rem, 55vw, 72rem); max-width: 100vw;
375
454
  display: flex; flex-direction: column; background: var(--ad-surface); border-left: 1px solid var(--bulma-border-weak);
376
- box-shadow: var(--ad-shadow-lg); transform: translateX(100%); transition: transform 200ms ease; outline: none;
455
+ box-shadow: var(--ad-shadow-lg); transform: translateX(100%); opacity: 0.6; outline: none;
456
+ transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1), opacity 240ms ease;
377
457
  }
378
458
  .ad-drawer.is-open .ad-drawer-backdrop { opacity: 1; }
379
- .ad-drawer.is-open .ad-drawer-panel { transform: none; }
459
+ .ad-drawer.is-open .ad-drawer-panel { transform: none; opacity: 1; }
380
460
  .ad-drawer-head {
381
461
  flex: none; display: flex; align-items: center; gap: 0.75rem; padding: 0.7rem 1.25rem;
382
462
  border-bottom: 1px solid var(--bulma-border-weak); background: var(--ad-surface);
@@ -390,10 +470,6 @@ kbd, .ad-kbd {
390
470
  .ad-drawer-body .ad-sticky { position: static; }
391
471
  .ad-drawer-body .ad-page-title { font-size: 1.3rem; }
392
472
 
393
- /* ---------- Footer help ---------- */
394
- .ad-help { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--bulma-border-weak); font-size: 0.8rem; color: var(--ad-muted); line-height: 1.6; }
395
- .ad-help code { font-size: 0.78rem; }
396
-
397
473
  /* ---------- Responsive ---------- */
398
474
  @media screen and (max-width: 1023px) {
399
475
  .ad-board { grid-template-columns: repeat(5, minmax(16rem, 80vw)); scroll-snap-type: x mandatory; }
@@ -409,6 +485,152 @@ kbd, .ad-kbd {
409
485
  .ad-drawer-body { padding: 1rem; }
410
486
  }
411
487
 
412
- @media (prefers-reduced-motion: reduce) {
413
- *, *::before, *::after { animation: none !important; transition: none !important; }
488
+ /* ---------- Collapsed kanban column ---------- */
489
+ .ad-column-more {
490
+ display: block; width: 100%; margin-top: 0.25rem; padding: 0.45rem; border: 1px dashed var(--bulma-border); border-radius: var(--ad-radius);
491
+ background: transparent; color: var(--ad-muted); font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer;
414
492
  }
493
+ .ad-column-more:hover { color: var(--bulma-link); border-color: var(--bulma-link); }
494
+
495
+ /* ---------- Side-panel call to action (open the full page for gate actions) ---------- */
496
+ .ad-drawer-cta { margin: 0 0 1rem; align-items: center; flex-wrap: wrap; }
497
+ .ad-drawer-cta .button { margin-left: auto; }
498
+ .ad-callout.is-danger { background: var(--bulma-danger-soft); color: var(--bulma-danger-bold); }
499
+
500
+ /* ---------- Task changes page (server-rendered unified diff) ---------- */
501
+ .ad-changes-toolbar { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1rem; }
502
+ .ad-changes-banner { margin: 0 0 1rem; }
503
+ .ad-changes-item .ad-changes-banner { margin: 0.75rem 1.1rem 0; }
504
+ .ad-callout.is-info { background: var(--bulma-info-soft); color: var(--bulma-info-bold); }
505
+ .ad-changes-commit { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.8rem; color: var(--ad-muted); }
506
+ /* .ad-panel clips with overflow: hidden, which turns it into the sticky file list's
507
+ * scroll container and disables sticking; clip keeps the rounded corners without that. */
508
+ .ad-panel.ad-changes-item { overflow: visible; overflow: clip; }
509
+ .ad-changes { display: grid; grid-template-columns: minmax(14rem, 22rem) minmax(0, 1fr); align-items: start; }
510
+ .ad-changes-files { position: sticky; top: 4.5rem; max-height: calc(100vh - 6rem); overflow-y: auto; border-right: 1px solid var(--bulma-border-weak); padding: 0.5rem 0; }
511
+ .ad-changes-files ul { margin: 0; list-style: none; }
512
+ .ad-changes-files a { display: flex; align-items: center; gap: 0.45rem; padding: 0.3rem 0.9rem; font-size: 0.8rem; color: var(--bulma-text); }
513
+ .ad-changes-files a:hover, .ad-changes-files a:focus-visible { background: var(--ad-subtle); color: var(--bulma-link); }
514
+ .ad-diff-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; font-family: var(--bulma-family-code); }
515
+ .ad-diff-stat { margin-left: auto; font-family: var(--bulma-family-code); font-size: 0.75rem; color: var(--ad-muted); white-space: nowrap; }
516
+ .ad-diff-plus { color: var(--bulma-success-bold, var(--bulma-success)); }
517
+ .ad-diff-minus { color: var(--bulma-danger-bold, var(--bulma-danger)); }
518
+ .ad-diff-status { font-size: 0.65rem; padding: 0.05rem 0.4rem; text-transform: uppercase; letter-spacing: 0.03em; flex: none; }
519
+ .ad-changes-diff { min-width: 0; padding: 0.75rem; }
520
+ .ad-diff-file { margin-bottom: 1rem; border-radius: var(--ad-radius); box-shadow: var(--ad-ring); overflow: hidden; scroll-margin-top: 5rem; }
521
+ .ad-diff-file.is-target { box-shadow: 0 0 0 2px var(--bulma-link); }
522
+ .ad-diff-file-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.45rem 0.75rem; background: var(--ad-subtle); border-bottom: 1px solid var(--bulma-border-weak); }
523
+ .ad-diff-file-head code { background: transparent; padding: 0; color: var(--bulma-text-strong); white-space: normal; word-break: break-all; }
524
+ .ad-diff { overflow-x: auto; font-family: var(--bulma-family-code); font-size: 0.78rem; line-height: 1.45; }
525
+ .ad-diff-line { white-space: pre; min-height: 1.45em; padding: 0 0.75rem; min-width: max-content; }
526
+ .ad-diff-add { background: var(--bulma-success-soft); color: var(--bulma-success-bold, inherit); }
527
+ .ad-diff-del { background: var(--bulma-danger-soft); color: var(--bulma-danger-bold, inherit); }
528
+ .ad-diff-hunk { background: var(--bulma-info-soft); color: var(--bulma-info-bold, var(--ad-muted)); }
529
+ .ad-diff-meta { color: var(--ad-muted); }
530
+ .ad-diff-note { padding: 0.6rem 0.75rem; font-size: 0.85rem; color: var(--ad-muted); }
531
+ @media (max-width: 1023px) {
532
+ .ad-changes { grid-template-columns: minmax(0, 1fr); }
533
+ .ad-changes-files { position: static; max-height: 16rem; border-right: 0; border-bottom: 1px solid var(--bulma-border-weak); }
534
+ }
535
+
536
+ /* ---------- Copy-to-clipboard id ---------- */
537
+ .ad-copy {
538
+ display: inline-flex; align-items: center; gap: 0.35rem; min-width: 0; padding: 0.1rem 0.4rem; border: 0; border-radius: var(--ad-radius);
539
+ background: transparent; color: var(--ad-muted); font: inherit; cursor: pointer; transition: background var(--ad-transition), color var(--ad-transition);
540
+ }
541
+ .ad-copy code { background: transparent; padding: 0; color: inherit; font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
542
+ .ad-copy svg { flex: none; opacity: 0.7; }
543
+ .ad-copy:hover, .ad-copy:focus-visible { background: var(--ad-subtle); color: var(--bulma-link); }
544
+ .ad-copy.is-copied { color: var(--bulma-success-bold, var(--bulma-success)); }
545
+ /* The side panel shows the id (with copy) in its header; skip the body's copy. */
546
+ .ad-drawer-body .ad-page-sub .ad-copy { display: none; }
547
+
548
+ /* ---------- "Radioactive" pulse on the current step ---------- */
549
+ .ad-pulse { position: absolute; inset: 0; border-radius: 999px; pointer-events: none; color: var(--bulma-primary); }
550
+ .ad-pulse.is-waiting { color: var(--bulma-warning); }
551
+ .ad-pulse::before, .ad-pulse::after {
552
+ content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid currentColor;
553
+ animation: ad-pulse-ring 1.8s cubic-bezier(0.25, 0.6, 0.35, 1) infinite;
554
+ }
555
+ .ad-pulse::after { animation-delay: 0.9s; }
556
+ /* Named apart from the `ad-pulse` opacity blink above: two keyframes with one name would
557
+ * make the last declaration win for both the live dot and this ring. */
558
+ @keyframes ad-pulse-ring { 0% { transform: scale(1); opacity: 0.75; } 100% { transform: scale(2.1); opacity: 0; } }
559
+ .ad-stepper li.is-current .ad-stepper-marker { z-index: 0; }
560
+
561
+ /* ---------- New task panel + Markdown editor ---------- */
562
+ .ad-drawer.is-compose .ad-drawer-panel { width: clamp(34rem, 44vw, 52rem); }
563
+ .ad-drawer-heading { margin-right: auto; font-size: 1rem; font-weight: 700; color: var(--bulma-text-strong); }
564
+ .ad-drawer-foot {
565
+ flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem; padding: 0.75rem 1.25rem;
566
+ border-top: 1px solid var(--bulma-border-weak); background: var(--ad-surface);
567
+ }
568
+ .ad-drawer-foot .ad-foot-start { margin-right: auto; font-size: 0.8rem; color: var(--ad-muted); }
569
+ .ad-compose-intro { margin-bottom: 0.75rem; font-size: 0.85rem; color: var(--ad-muted); }
570
+ .ad-editor { display: flex; flex-direction: column; border-radius: var(--ad-radius); box-shadow: var(--ad-ring); background: var(--ad-surface); transition: box-shadow var(--ad-transition); }
571
+ .ad-editor:focus-within { box-shadow: 0 0 0 1px var(--bulma-link), 0 0 0 4px var(--bulma-link-soft, transparent); }
572
+ .ad-editor-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; padding: 0.35rem 0.5rem; border-bottom: 1px solid var(--bulma-border-weak); background: var(--ad-subtle); border-radius: var(--ad-radius) var(--ad-radius) 0 0; }
573
+ .ad-editor-tabs { display: flex; gap: 0.15rem; }
574
+ .ad-editor-tabs button, .ad-editor-tool {
575
+ border: 0; border-radius: var(--ad-radius); background: transparent; color: var(--ad-muted); font: inherit; font-size: 0.8rem; cursor: pointer;
576
+ padding: 0.25rem 0.6rem; transition: background var(--ad-transition), color var(--ad-transition);
577
+ }
578
+ .ad-editor-tabs button[aria-selected="true"] { background: var(--ad-surface); color: var(--bulma-text-strong); font-weight: 600; box-shadow: var(--ad-ring); }
579
+ .ad-editor-tools { display: flex; flex-wrap: wrap; gap: 0.1rem; margin-left: auto; }
580
+ .ad-editor-tool { min-width: 1.9rem; padding: 0.25rem 0.45rem; }
581
+ .ad-editor-tool:hover:not(:disabled), .ad-editor-tabs button:hover { background: var(--ad-surface); color: var(--bulma-link); }
582
+ .ad-editor-tool:disabled { opacity: 0.4; cursor: default; }
583
+ .ad-editor-input {
584
+ display: block; width: 100%; min-height: 18rem; resize: vertical; border: 0; outline: none; padding: 0.85rem 1rem; background: transparent;
585
+ color: var(--bulma-text); font-family: var(--bulma-family-code); font-size: 0.85rem; line-height: 1.6; border-radius: 0 0 var(--ad-radius) var(--ad-radius);
586
+ }
587
+ .ad-editor-preview { min-height: 18rem; padding: 0.85rem 1rem; overflow: auto; }
588
+
589
+ /* ---------- Settings dialog ---------- */
590
+ .ad-settings { display: flex; flex-direction: column; gap: 1.1rem; }
591
+ .ad-settings-group { margin: 0; padding: 0 0 1rem; border: 0; border-bottom: 1px solid var(--bulma-border-weak); min-inline-size: 0; }
592
+ .ad-settings-group:last-child { padding-bottom: 0; border-bottom: 0; }
593
+ .ad-settings-group > legend { padding: 0; margin-bottom: 0.6rem; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ad-muted); }
594
+ .ad-settings .field:last-child { margin-bottom: 0; }
595
+ .ad-settings .label.is-small { margin-bottom: 0.3rem; font-weight: 600; }
596
+ .ad-settings-radios { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; }
597
+ .ad-settings .radio input, .ad-settings .checkbox input { margin-right: 0.4rem; }
598
+ .ad-settings .help.is-danger { color: var(--bulma-danger-bold); }
599
+
600
+ /* ---------- Docs pages ---------- */
601
+ .ad-docs { display: grid; grid-template-columns: minmax(13rem, 16rem) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
602
+ .ad-docs-nav { position: sticky; top: 4.5rem; max-height: calc(100vh - 6rem); overflow-y: auto; font-size: 0.85rem; }
603
+ .ad-docs-nav-title { margin: 0 0 0.4rem; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ad-muted); }
604
+ .ad-docs-pages, .ad-docs-toc { margin: 0 0 1.25rem; list-style: none; }
605
+ .ad-docs-nav a { display: block; padding: 0.3rem 0.6rem; border-radius: var(--ad-radius); color: var(--bulma-text); }
606
+ .ad-docs-nav a:hover { background: var(--ad-subtle); color: var(--bulma-link); }
607
+ .ad-docs-nav a.is-active { background: var(--bulma-primary-soft); color: var(--bulma-primary-bold); font-weight: 600; }
608
+ .ad-docs-toc .is-level-3 a { padding-left: 1.4rem; font-size: 0.8rem; color: var(--ad-muted); }
609
+ .ad-docs-body .artifact { padding: 1.5rem 2rem 2rem; max-height: none; overflow: visible; }
610
+ .ad-docs-body h1, .ad-docs-body h2, .ad-docs-body h3, .ad-docs-body h4 { position: relative; scroll-margin-top: 4.5rem; }
611
+ .ad-doc-anchor { position: absolute; left: -1.1rem; opacity: 0; color: var(--ad-muted); font-weight: 400; text-decoration: none; }
612
+ .ad-docs-body h1:hover .ad-doc-anchor, .ad-docs-body h2:hover .ad-doc-anchor, .ad-docs-body h3:hover .ad-doc-anchor, .ad-docs-body h4:hover .ad-doc-anchor { opacity: 1; }
613
+ .ad-doc-callout { border-left: 3px solid var(--bulma-info); background: var(--bulma-info-soft); color: var(--bulma-text); }
614
+ .ad-doc-callout.is-warning, .ad-doc-callout.is-caution { border-left-color: var(--bulma-warning); background: var(--bulma-warning-soft); }
615
+ .ad-doc-callout.is-important { border-left-color: var(--bulma-link); background: var(--bulma-link-soft); }
616
+ .ad-doc-callout.is-tip { border-left-color: var(--bulma-success); background: var(--bulma-success-soft); }
617
+ .ad-doc-callout-title { display: block; margin-bottom: 0.2rem; }
618
+ @media screen and (max-width: 1023px) {
619
+ .ad-docs { grid-template-columns: 1fr; }
620
+ .ad-docs-nav { position: static; max-height: none; }
621
+ .ad-drawer.is-compose .ad-drawer-panel { width: 100vw; }
622
+ }
623
+
624
+ /* Containers */
625
+ /* The container log viewer sits inside the Containers panel, under the table. */
626
+ .ad-panel .ad-container-log { margin: 0; border-radius: 0; border-top: 1px solid var(--bulma-border-weak); }
627
+ .ad-container-log[hidden] { display: none; }
628
+ .ad-container-log .ad-terminal-head { flex-wrap: wrap; }
629
+ .ad-container-log .log-panel { height: 20rem; }
630
+ .ad-container-log [data-container-log-name] { font-family: var(--bulma-family-code); }
631
+
632
+ /* ---------- Code changes page: wrap toggle and toolbar options ---------- */
633
+ .ad-changes-diff.is-wrapped .ad-diff-line { white-space: pre-wrap; word-break: break-word; min-width: 0; }
634
+ .ad-changes-form { display: inline-flex; align-items: center; gap: 0.6rem; }
635
+ .ad-changes-options { display: inline-flex; align-items: center; gap: 0.9rem; margin-left: auto; font-size: 0.8rem; }
636
+ .ad-changes-options label { display: inline-flex; align-items: center; gap: 0.3rem; cursor: pointer; }
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Loop dashboard page: kanban/list view toggle, client-side filters, and the
3
+ * collapsible done column. Loaded after common.js (uses its `live` hooks).
4
+ */
5
+ /* ---- Dashboard view state (re-applied after every live update) ---- */
6
+ const viewKey = "aidlc-view";
7
+ const applyView = (view) => {
8
+ document.documentElement.setAttribute("data-view", view === "list" ? "list" : "kanban");
9
+ document.querySelectorAll("[data-view-toggle]").forEach((el) => {
10
+ const active = el.getAttribute("data-view-toggle") === view;
11
+ el.classList.toggle("is-active", active);
12
+ el.setAttribute("aria-pressed", String(active));
13
+ });
14
+ };
15
+ const currentView = () => (localStorage.getItem(viewKey) === "list" ? "list" : "kanban");
16
+ const expandedColumns = new Set();
17
+ // Client-side filtering over server-rendered cards and rows (no extra API calls).
18
+ // Columns with data-collapse show that many cards unless expanded or filtering.
19
+ const applyFilter = () => {
20
+ const search = document.getElementById("task-search");
21
+ const statusFilter = document.getElementById("filter-status");
22
+ const riskFilter = document.getElementById("filter-risk");
23
+ const resultCount = document.getElementById("result-count");
24
+ const terms = (search ? search.value : "").toLowerCase().split(/\s+/).filter(Boolean);
25
+ const status = statusFilter ? statusFilter.value : "";
26
+ const risk = riskFilter ? riskFilter.value : "";
27
+ const filtered = !!(terms.length || status || risk);
28
+ const matches = (el) => (!status || el.getAttribute("data-status") === status) && (!risk || el.getAttribute("data-risk") === risk) && terms.every((term) => (el.getAttribute("data-search") || "").includes(term));
29
+ let visible = 0;
30
+ document.querySelectorAll("#kanban-view [data-task-item]").forEach((el) => { const show = matches(el); el.hidden = !show; if (show) visible += 1; });
31
+ document.querySelectorAll("#list-view [data-task-item]").forEach((el) => { el.hidden = !matches(el); });
32
+ document.querySelectorAll("[data-phase-column]").forEach((column) => {
33
+ const shown = [...column.querySelectorAll("[data-task-item]")].filter((el) => !el.hidden);
34
+ const limit = Number(column.getAttribute("data-collapse") || 0);
35
+ const expanded = expandedColumns.has(column.getAttribute("data-phase-column"));
36
+ const extra = limit && !filtered ? Math.max(0, shown.length - limit) : 0;
37
+ if (extra && !expanded) shown.slice(limit).forEach((el) => { el.hidden = true; });
38
+ const more = column.querySelector("[data-column-more]");
39
+ if (more) { more.hidden = extra === 0; more.textContent = expanded ? "Show less" : "Show " + extra + " more"; more.setAttribute("aria-expanded", String(expanded)); }
40
+ const badge = column.querySelector("[data-column-count]");
41
+ if (badge) badge.textContent = String(shown.length);
42
+ const empty = column.querySelector("[data-column-empty]");
43
+ if (empty) empty.hidden = shown.length > 0;
44
+ });
45
+ const total = document.querySelectorAll("#kanban-view [data-task-item]").length;
46
+ if (resultCount) resultCount.textContent = filtered ? visible + " of " + total + " tasks" : total + (total === 1 ? " task" : " tasks");
47
+ document.querySelectorAll("[data-no-match]").forEach((el) => el.classList.toggle("is-visible", filtered && visible === 0));
48
+ };
49
+ const clearFilters = () => {
50
+ ["task-search", "filter-status", "filter-risk"].forEach((id) => { const el = document.getElementById(id); if (el) el.value = ""; });
51
+ applyFilter();
52
+ };
53
+ live.hooks.push(() => { applyView(currentView()); applyFilter(); });
54
+
55
+ document.addEventListener("click", (event) => {
56
+ const target = event.target instanceof Element ? event.target : null;
57
+ if (!target) return;
58
+ const viewToggle = target.closest("[data-view-toggle]");
59
+ if (viewToggle) { const view = viewToggle.getAttribute("data-view-toggle"); localStorage.setItem(viewKey, view); applyView(view); return; }
60
+ const columnMore = target.closest("[data-column-more]");
61
+ if (columnMore) {
62
+ const column = columnMore.closest("[data-phase-column]");
63
+ const name = column ? column.getAttribute("data-phase-column") : "";
64
+ if (expandedColumns.has(name)) expandedColumns.delete(name); else expandedColumns.add(name);
65
+ applyFilter();
66
+ return;
67
+ }
68
+ if (target.closest("#filter-clear")) { clearFilters(); return; }
69
+ });
70
+ document.addEventListener("input", (event) => {
71
+ const target = event.target instanceof Element ? event.target : null;
72
+ if (target && target.matches("#task-search, #filter-status, #filter-risk")) applyFilter();
73
+ });
74
+ document.addEventListener("DOMContentLoaded", () => {
75
+ applyView(currentView());
76
+ applyFilter();
77
+ });