@polderlabs/openkan 0.4.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 (114) hide show
  1. package/CHANGELOG.md +226 -0
  2. package/LICENSE +21 -0
  3. package/README.md +318 -0
  4. package/agents/openkan.md +254 -0
  5. package/bin/install-agent.mjs +63 -0
  6. package/bin/ok.mjs +17 -0
  7. package/bin/openkan.mjs +10 -0
  8. package/dist/.claude/skills/ok-planning/SKILL.md +285 -0
  9. package/dist/.claude/skills/ok-planning/references/integration.md +153 -0
  10. package/dist/.claude/skills/ok-planning/references/schemas.md +270 -0
  11. package/dist/.claude/skills/ok-planning/references/workflows.md +185 -0
  12. package/dist/.claude/skills/ok-planning/scripts/ok-init.sh +14 -0
  13. package/dist/.claude/skills/ok-planning/scripts/ok-resume.sh +38 -0
  14. package/dist/.claude/skills/ok-planning/scripts/ok-status.sh +24 -0
  15. package/dist/agents/openkan.md +254 -0
  16. package/dist/bin/install-agent.mjs +76 -0
  17. package/dist/bin/ok-install.js +58 -0
  18. package/dist/bin/ok.js +138 -0
  19. package/dist/bin/openkan.js +804 -0
  20. package/dist/commands/organize.md +15 -0
  21. package/dist/kanban/agent-profile.js +8 -0
  22. package/dist/kanban/archive.js +49 -0
  23. package/dist/kanban/bizar.js +242 -0
  24. package/dist/kanban/board.js +367 -0
  25. package/dist/kanban/bulk.js +139 -0
  26. package/dist/kanban/changelog.js +186 -0
  27. package/dist/kanban/chat.js +1280 -0
  28. package/dist/kanban/claude-state.js +974 -0
  29. package/dist/kanban/comments.js +80 -0
  30. package/dist/kanban/docs.js +144 -0
  31. package/dist/kanban/fs.js +163 -0
  32. package/dist/kanban/git.js +196 -0
  33. package/dist/kanban/images.js +140 -0
  34. package/dist/kanban/import.js +295 -0
  35. package/dist/kanban/inputs.js +94 -0
  36. package/dist/kanban/insights.js +140 -0
  37. package/dist/kanban/io.js +75 -0
  38. package/dist/kanban/mdx-render.js +348 -0
  39. package/dist/kanban/mdx.js +231 -0
  40. package/dist/kanban/projects.js +545 -0
  41. package/dist/kanban/search.js +121 -0
  42. package/dist/kanban/server.js +3296 -0
  43. package/dist/kanban/tags.js +124 -0
  44. package/dist/kanban/template.js +145 -0
  45. package/dist/kanban/tsx-sandbox.js +187 -0
  46. package/dist/kanban/watcher.js +270 -0
  47. package/dist/ok/commands/goal.js +65 -0
  48. package/dist/ok/commands/index.js +87 -0
  49. package/dist/ok/commands/init.js +15 -0
  50. package/dist/ok/commands/plan.js +155 -0
  51. package/dist/ok/commands/prd.js +202 -0
  52. package/dist/ok/commands/progress.js +31 -0
  53. package/dist/ok/commands/task.js +377 -0
  54. package/dist/ok/ids.js +98 -0
  55. package/dist/ok/lock.js +156 -0
  56. package/dist/ok/migrate.js +197 -0
  57. package/dist/ok/schemas.js +402 -0
  58. package/dist/ok/storage.js +222 -0
  59. package/dist/skills/openkan/SKILL.md +111 -0
  60. package/dist/skills/openkan/agents/openai.yaml +4 -0
  61. package/dist/skills/openkan/examples/simple-task.mdx +34 -0
  62. package/dist/skills/openkan/examples/with-ask.mdx +32 -0
  63. package/dist/skills/openkan/examples/with-choice.mdx +51 -0
  64. package/dist/skills/openkan/examples/with-preview.mdx +54 -0
  65. package/dist/skills/openkan/references/api.md +169 -0
  66. package/dist/skills/openkan/templates/task.mdx +46 -0
  67. package/dist/web/api.js +257 -0
  68. package/dist/web/app.js +4251 -0
  69. package/dist/web/bizar.js +39 -0
  70. package/dist/web/brand/agent-activity-sprite.svg +1 -0
  71. package/dist/web/brand/banner-docs.svg +24 -0
  72. package/dist/web/brand/banner.svg +32 -0
  73. package/dist/web/brand/empty-sessions.svg +17 -0
  74. package/dist/web/brand/empty-tasks.svg +17 -0
  75. package/dist/web/brand/favicon.svg +9 -0
  76. package/dist/web/brand/infinity-loader-animated.svg +220 -0
  77. package/dist/web/brand/infinity-loader-spritesheet.svg +230 -0
  78. package/dist/web/brand/logo-wordmark.svg +10 -0
  79. package/dist/web/brand/logo.svg +9 -0
  80. package/dist/web/brand/pixel-infinity-track.svg +1 -0
  81. package/dist/web/brand/social-card.svg +26 -0
  82. package/dist/web/changelog-view.js +456 -0
  83. package/dist/web/charts.js +269 -0
  84. package/dist/web/chat-sidebar.js +2397 -0
  85. package/dist/web/chat-status-motion.js +154 -0
  86. package/dist/web/claude-pane.js +820 -0
  87. package/dist/web/command-palette.js +381 -0
  88. package/dist/web/contributors-view.js +317 -0
  89. package/dist/web/cross-tab.js +102 -0
  90. package/dist/web/docs-view.js +168 -0
  91. package/dist/web/experience.css +165 -0
  92. package/dist/web/goals-view.js +45 -0
  93. package/dist/web/home-view.js +113 -0
  94. package/dist/web/images.js +311 -0
  95. package/dist/web/index.html +485 -0
  96. package/dist/web/insights.js +217 -0
  97. package/dist/web/keyboard.js +446 -0
  98. package/dist/web/mdx-viewer.js +600 -0
  99. package/dist/web/path-picker.js +787 -0
  100. package/dist/web/preview-frame.html +187 -0
  101. package/dist/web/settings.js +582 -0
  102. package/dist/web/style.css +8545 -0
  103. package/dist/web/task-view.js +1759 -0
  104. package/dist/web/vendor/gsap.min.js +11 -0
  105. package/dist/web/workspace.css +1513 -0
  106. package/package.json +71 -0
  107. package/skills/openkan/SKILL.md +111 -0
  108. package/skills/openkan/agents/openai.yaml +4 -0
  109. package/skills/openkan/examples/simple-task.mdx +34 -0
  110. package/skills/openkan/examples/with-ask.mdx +32 -0
  111. package/skills/openkan/examples/with-choice.mdx +51 -0
  112. package/skills/openkan/examples/with-preview.mdx +54 -0
  113. package/skills/openkan/references/api.md +169 -0
  114. package/skills/openkan/templates/task.mdx +46 -0
@@ -0,0 +1,269 @@
1
+ // web/charts.js — pure-SVG stacked-bar renderer for the Insights tab.
2
+ //
3
+ // No dependencies. Uses document.createElementNS so the host must provide
4
+ // a DOM. Color and tokens come from the page (CSS variables on the SVG).
5
+ //
6
+ // data shape (matches /api/insights/velocity):
7
+ // {
8
+ // days: string[], // YYYY-MM-DD local, oldest first
9
+ // columns: { backlog, todo, doing, review, done: number[] },
10
+ // windowDays: number,
11
+ // generatedAt: string,
12
+ // }
13
+
14
+ (() => {
15
+ "use strict";
16
+
17
+ const SVG_NS = "http://www.w3.org/2000/svg";
18
+ const COLUMNS = [
19
+ { id: "backlog", token: "var(--ink-50)" },
20
+ { id: "todo", token: "var(--warn)" },
21
+ { id: "doing", token: "var(--accent)" },
22
+ { id: "review", token: "var(--coral)" },
23
+ { id: "done", token: "var(--success)" },
24
+ ];
25
+
26
+ const BAR_WIDTH = 18;
27
+ const BAR_GAP = 6;
28
+ const PLOT_PAD_LEFT = 32; // space for y-axis labels
29
+ const PLOT_PAD_RIGHT = 8;
30
+ const PLOT_PAD_TOP = 16;
31
+ const PLOT_PAD_BOTTOM = 28; // space for x-axis labels
32
+ const PLOT_HEIGHT = 180;
33
+
34
+ function svg(name, attrs) {
35
+ const el = document.createElementNS(SVG_NS, name);
36
+ if (attrs) {
37
+ for (const [k, v] of Object.entries(attrs)) {
38
+ if (v == null) continue;
39
+ el.setAttribute(k, String(v));
40
+ }
41
+ }
42
+ return el;
43
+ }
44
+
45
+ function clearChildren(parent) {
46
+ while (parent.firstChild) parent.removeChild(parent.firstChild);
47
+ }
48
+
49
+ function isAllZero(data) {
50
+ if (!data || !data.columns) return true;
51
+ for (const col of COLUMNS) {
52
+ const arr = data.columns[col.id];
53
+ if (!Array.isArray(arr)) continue;
54
+ for (const n of arr) if (n > 0) return false;
55
+ }
56
+ return true;
57
+ }
58
+
59
+ function dayTotal(data, dayIdx) {
60
+ let sum = 0;
61
+ for (const col of COLUMNS) {
62
+ const arr = data.columns[col.id] || [];
63
+ const v = arr[dayIdx] || 0;
64
+ if (v > 0) sum += v;
65
+ }
66
+ return sum;
67
+ }
68
+
69
+ function formatDayLabel(yyyyMmDd) {
70
+ // YYYY-MM-DD → Mon DD (e.g. "Aug 04")
71
+ const parts = yyyyMmDd.split("-").map(Number);
72
+ const y = parts[0], m = parts[1] - 1, d = parts[2];
73
+ const date = new Date(y, m, d, 12, 0, 0, 0);
74
+ if (isNaN(date.getTime())) return yyyyMmDd;
75
+ try {
76
+ return new Intl.DateTimeFormat(undefined, {
77
+ month: "short", day: "2-digit",
78
+ }).format(date);
79
+ } catch {
80
+ return yyyyMmDd;
81
+ }
82
+ }
83
+
84
+ function formatDayLabelLong(yyyyMmDd) {
85
+ const parts = yyyyMmDd.split("-").map(Number);
86
+ const y = parts[0], m = parts[1] - 1, d = parts[2];
87
+ const date = new Date(y, m, d, 12, 0, 0, 0);
88
+ if (isNaN(date.getTime())) return yyyyMmDd;
89
+ try {
90
+ return new Intl.DateTimeFormat(undefined, {
91
+ month: "short", day: "2-digit", year: "numeric",
92
+ }).format(date);
93
+ } catch {
94
+ return yyyyMmDd;
95
+ }
96
+ }
97
+
98
+ function renderStackedBar(svgEl, data) {
99
+ if (!svgEl || !data || !data.columns) return;
100
+ clearChildren(svgEl);
101
+
102
+ const days = data.days || [];
103
+ const dayCount = days.length;
104
+ const innerWidth = dayCount * (BAR_WIDTH + BAR_GAP) - BAR_GAP;
105
+ const totalWidth = innerWidth + PLOT_PAD_LEFT + PLOT_PAD_RIGHT;
106
+ const totalHeight = PLOT_HEIGHT + PLOT_PAD_TOP + PLOT_PAD_BOTTOM;
107
+
108
+ svgEl.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`);
109
+ svgEl.setAttribute("width", String(totalWidth));
110
+ svgEl.setAttribute("height", String(totalHeight));
111
+ svgEl.setAttribute("preserveAspectRatio", "xMinYMid meet");
112
+ svgEl.setAttribute("role", "img");
113
+
114
+ const summary = `Stacked bar chart of tasks moved per column over the last ${dayCount} days, generated ${data.generatedAt || "n/a"}.`;
115
+ const desc = svg("desc", {});
116
+ desc.textContent = summary;
117
+ svgEl.appendChild(desc);
118
+ svgEl.setAttribute("aria-label", summary);
119
+
120
+ // Compute the max per-day total to drive the y-axis.
121
+ let max = 0;
122
+ for (let i = 0; i < dayCount; i++) {
123
+ const t = dayTotal(data, i);
124
+ if (t > max) max = t;
125
+ }
126
+ const yMax = Math.max(1, max);
127
+
128
+ // Background plot rect (subtle, flat surface)
129
+ svgEl.appendChild(svg("rect", {
130
+ x: PLOT_PAD_LEFT,
131
+ y: PLOT_PAD_TOP,
132
+ width: innerWidth,
133
+ height: PLOT_HEIGHT,
134
+ fill: "transparent",
135
+ }));
136
+
137
+ // Y-axis ticks at 0, ceil(max/2), max.
138
+ const yTicks = [0, Math.ceil(yMax / 2), yMax];
139
+ for (const tick of yTicks) {
140
+ const ratio = yMax === 0 ? 0 : tick / yMax;
141
+ const y = PLOT_PAD_TOP + PLOT_HEIGHT - ratio * PLOT_HEIGHT;
142
+ svgEl.appendChild(svg("line", {
143
+ x1: PLOT_PAD_LEFT,
144
+ y1: y,
145
+ x2: PLOT_PAD_LEFT + innerWidth,
146
+ y2: y,
147
+ stroke: "var(--ink-30)",
148
+ "stroke-width": 1,
149
+ }));
150
+ const label = svg("text", {
151
+ x: PLOT_PAD_LEFT - 6,
152
+ y: y + 3,
153
+ "text-anchor": "end",
154
+ "font-size": 11,
155
+ "font-family": "inherit",
156
+ fill: "var(--ink-50)",
157
+ });
158
+ label.textContent = String(tick);
159
+ svgEl.appendChild(label);
160
+ }
161
+
162
+ // Bars
163
+ for (let i = 0; i < dayCount; i++) {
164
+ const x = PLOT_PAD_LEFT + i * (BAR_WIDTH + BAR_GAP);
165
+ // Determine which segments are non-zero (in fixed column order).
166
+ const segments = [];
167
+ for (const col of COLUMNS) {
168
+ const arr = data.columns[col.id] || [];
169
+ const v = arr[i] || 0;
170
+ if (v > 0) segments.push({ col: col.id, token: col.token, count: v });
171
+ }
172
+ if (segments.length === 0) {
173
+ // Empty day: render a faint baseline so the bar slot is visible.
174
+ const ghost = svg("rect", {
175
+ x, y: PLOT_PAD_TOP + PLOT_HEIGHT - 2,
176
+ width: BAR_WIDTH, height: 2,
177
+ fill: "var(--ink-30)", rx: 1,
178
+ });
179
+ svgEl.appendChild(ghost);
180
+ continue;
181
+ }
182
+ // Stack segments from bottom (PLOT_PAD_TOP + PLOT_HEIGHT) up.
183
+ const totalUnits = segments.reduce((a, s) => a + s.count, 0);
184
+ const usableHeight = PLOT_HEIGHT - 2; // 1-unit top/bottom inset
185
+ let cursor = PLOT_PAD_TOP + PLOT_HEIGHT - 1; // bottom edge
186
+ const segCount = segments.length;
187
+ segments.forEach((seg, sIdx) => {
188
+ const segH = Math.max(1, Math.round((seg.count / totalUnits) * usableHeight));
189
+ const isFirst = sIdx === 0;
190
+ const isLast = sIdx === segCount - 1;
191
+ const segY = cursor - segH;
192
+ const rect = svg("rect", {
193
+ x, y: segY,
194
+ width: BAR_WIDTH, height: segH,
195
+ fill: seg.token,
196
+ });
197
+ // Rounding on outermost segments.
198
+ if (isFirst) {
199
+ rect.setAttribute("rx", "4");
200
+ rect.setAttribute("ry", "4");
201
+ }
202
+ if (isLast) {
203
+ // top corners rounded for the top segment
204
+ rect.setAttribute("rx", "4");
205
+ rect.setAttribute("ry", "4");
206
+ }
207
+ // 2px surface gap between stacked segments.
208
+ if (!isFirst) {
209
+ rect.setAttribute("y", String(segY + 1));
210
+ rect.setAttribute("height", String(segH - 1));
211
+ }
212
+ // Title for hover (per the spec).
213
+ const titleText = `${seg.col} on ${formatDayLabelLong(days[i])}: ${seg.count} move${seg.count === 1 ? "" : "s"}`;
214
+ const titleEl = svg("title", {});
215
+ titleEl.textContent = titleText;
216
+ rect.appendChild(titleEl);
217
+ svgEl.appendChild(rect);
218
+ cursor = segY;
219
+ });
220
+ // Invisible full-height hit target for hover over zero-count gaps
221
+ // inside a day (per dataviz interaction guidance).
222
+ const hit = svg("rect", {
223
+ x, y: PLOT_PAD_TOP,
224
+ width: BAR_WIDTH, height: PLOT_HEIGHT,
225
+ fill: "transparent",
226
+ "pointer-events": "all",
227
+ });
228
+ const hitTitle = svg("title", {});
229
+ hitTitle.textContent = `${formatDayLabelLong(days[i])}: ${totalUnits} move${totalUnits === 1 ? "" : "s"}`;
230
+ hit.appendChild(hitTitle);
231
+ svgEl.appendChild(hit);
232
+ }
233
+
234
+ // X-axis labels every 5 days + day 29 (per spec).
235
+ for (let i = 0; i < dayCount; i++) {
236
+ const isMajor = i % 5 === 0;
237
+ const isLast = i === dayCount - 1;
238
+ if (!isMajor && !isLast) continue;
239
+ const x = PLOT_PAD_LEFT + i * (BAR_WIDTH + BAR_GAP) + BAR_WIDTH / 2;
240
+ const label = svg("text", {
241
+ x, y: PLOT_PAD_TOP + PLOT_HEIGHT + 16,
242
+ "text-anchor": "middle",
243
+ "font-size": 11,
244
+ "font-family": "inherit",
245
+ fill: "var(--ink-50)",
246
+ });
247
+ label.textContent = formatDayLabel(days[i]);
248
+ svgEl.appendChild(label);
249
+ }
250
+
251
+ // Axis baseline + left axis.
252
+ svgEl.appendChild(svg("line", {
253
+ x1: PLOT_PAD_LEFT,
254
+ y1: PLOT_PAD_TOP + PLOT_HEIGHT,
255
+ x2: PLOT_PAD_LEFT + innerWidth,
256
+ y2: PLOT_PAD_TOP + PLOT_HEIGHT,
257
+ stroke: "var(--ink-30)", "stroke-width": 1,
258
+ }));
259
+ svgEl.appendChild(svg("line", {
260
+ x1: PLOT_PAD_LEFT,
261
+ y1: PLOT_PAD_TOP,
262
+ x2: PLOT_PAD_LEFT,
263
+ y2: PLOT_PAD_TOP + PLOT_HEIGHT,
264
+ stroke: "var(--ink-30)", "stroke-width": 1,
265
+ }));
266
+ }
267
+
268
+ window.OpenKanCharts = { renderStackedBar, isAllZero };
269
+ })();