@taskmark/ui 0.3.0 → 0.3.1

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 (301) hide show
  1. package/README.md +1 -1
  2. package/app/globals.css +25 -0
  3. package/app/layout.tsx +15 -5
  4. package/app/setup/page.tsx +1 -1
  5. package/bin/lib/resolve-board.mjs +1 -4
  6. package/components/board/app-bar.tsx +3 -0
  7. package/components/board/board-screen.tsx +61 -117
  8. package/components/board/changelog-panel.tsx +26 -0
  9. package/components/board/epic-list.tsx +2 -0
  10. package/components/board/list-view-switcher.tsx +7 -3
  11. package/components/board/markdown-content.tsx +21 -1
  12. package/components/board/mermaid-diagram.tsx +88 -0
  13. package/components/board/metric-stat-card.tsx +4 -4
  14. package/components/board/overall-tree-list.tsx +467 -0
  15. package/components/board/overall-work-items-list.tsx +5 -1
  16. package/components/board/points-heatmap.tsx +183 -0
  17. package/components/board/project-status-metrics-strip.tsx +5 -22
  18. package/components/board/static-board-app.tsx +29 -94
  19. package/components/board/status-badge.tsx +8 -0
  20. package/components/board/story-list.tsx +2 -0
  21. package/components/board/task-list.tsx +5 -1
  22. package/components/board/work-item-detail-body.tsx +123 -8
  23. package/components/board/work-items-list.tsx +2 -0
  24. package/components/theme-provider.tsx +39 -0
  25. package/components/theme-toggle.tsx +27 -0
  26. package/components/ui/sonner.tsx +2 -2
  27. package/dist/prod-next/BUILD_ID +1 -1
  28. package/dist/prod-next/build-manifest.json +6 -6
  29. package/dist/prod-next/react-loadable-manifest.json +391 -1
  30. package/dist/prod-next/server/app/_global-error/page.js +2 -2
  31. package/dist/prod-next/server/app/_global-error/page.js.nft.json +1 -1
  32. package/dist/prod-next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  33. package/dist/prod-next/server/app/_global-error.html +1 -1
  34. package/dist/prod-next/server/app/_global-error.rsc +1 -1
  35. package/dist/prod-next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  36. package/dist/prod-next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  37. package/dist/prod-next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  38. package/dist/prod-next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  39. package/dist/prod-next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  40. package/dist/prod-next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  41. package/dist/prod-next/server/app/_not-found/page.js +2 -2
  42. package/dist/prod-next/server/app/_not-found/page.js.nft.json +1 -1
  43. package/dist/prod-next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  44. package/dist/prod-next/server/app/_not-found.html +1 -1
  45. package/dist/prod-next/server/app/_not-found.rsc +19 -17
  46. package/dist/prod-next/server/app/_not-found.segments/_full.segment.rsc +19 -17
  47. package/dist/prod-next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/dist/prod-next/server/app/_not-found.segments/_index.segment.rsc +8 -6
  49. package/dist/prod-next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/dist/prod-next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/dist/prod-next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  52. package/dist/prod-next/server/app/api/taskmark-reload/route.js +1 -1
  53. package/dist/prod-next/server/app/api/taskmark-reload/route.js.nft.json +1 -1
  54. package/dist/prod-next/server/app/board/page.js +2 -2
  55. package/dist/prod-next/server/app/board/page.js.nft.json +1 -1
  56. package/dist/prod-next/server/app/board/page_client-reference-manifest.js +1 -1
  57. package/dist/prod-next/server/app/favicon.ico/route.js +1 -1
  58. package/dist/prod-next/server/app/favicon.ico/route.js.nft.json +1 -1
  59. package/dist/prod-next/server/app/page.js +2 -2
  60. package/dist/prod-next/server/app/page.js.nft.json +1 -1
  61. package/dist/prod-next/server/app/page_client-reference-manifest.js +1 -1
  62. package/dist/prod-next/server/app/setup/page.js +2 -2
  63. package/dist/prod-next/server/app/setup/page.js.nft.json +1 -1
  64. package/dist/prod-next/server/app/setup/page_client-reference-manifest.js +1 -1
  65. package/dist/prod-next/server/chunks/102.js +90 -0
  66. package/dist/prod-next/server/chunks/11.js +1 -0
  67. package/dist/prod-next/server/chunks/1286.js +1 -0
  68. package/dist/prod-next/server/chunks/1412.js +38 -0
  69. package/dist/prod-next/server/chunks/1605.js +1 -0
  70. package/dist/prod-next/server/chunks/169.js +1 -0
  71. package/dist/prod-next/server/chunks/18.js +86 -0
  72. package/dist/prod-next/server/chunks/2.js +1 -0
  73. package/dist/prod-next/server/chunks/2022.js +1 -0
  74. package/dist/prod-next/server/chunks/2156.js +1 -0
  75. package/dist/prod-next/server/chunks/2157.js +1 -0
  76. package/dist/prod-next/server/chunks/2325.js +82 -0
  77. package/dist/prod-next/server/chunks/2838.js +1 -0
  78. package/dist/prod-next/server/chunks/2897.js +79 -0
  79. package/dist/prod-next/server/chunks/3227.js +1 -0
  80. package/dist/prod-next/server/chunks/3266.js +1 -0
  81. package/dist/prod-next/server/chunks/{192.js → 3307.js} +5 -5
  82. package/dist/prod-next/server/chunks/3387.js +19 -0
  83. package/dist/prod-next/server/chunks/3428.js +32 -0
  84. package/dist/prod-next/server/chunks/3479.js +1 -0
  85. package/dist/prod-next/server/chunks/367.js +115 -0
  86. package/dist/prod-next/server/chunks/3698.js +93 -0
  87. package/dist/prod-next/server/chunks/3709.js +148 -0
  88. package/dist/prod-next/server/chunks/41.js +1 -0
  89. package/dist/prod-next/server/chunks/4107.js +62 -0
  90. package/dist/prod-next/server/chunks/4569.js +34 -0
  91. package/dist/prod-next/server/chunks/{741.js → 4741.js} +1 -1
  92. package/dist/prod-next/server/chunks/4755.js +153 -0
  93. package/dist/prod-next/server/chunks/5002.js +1 -0
  94. package/dist/prod-next/server/chunks/5112.js +1 -0
  95. package/dist/prod-next/server/chunks/5289.js +8 -0
  96. package/dist/prod-next/server/chunks/5318.js +1 -0
  97. package/dist/prod-next/server/chunks/5452.js +23 -0
  98. package/dist/prod-next/server/chunks/5453.js +1 -0
  99. package/dist/prod-next/server/chunks/5749.js +27 -0
  100. package/dist/prod-next/server/chunks/5782.js +36 -0
  101. package/dist/prod-next/server/chunks/6163.js +104 -0
  102. package/dist/prod-next/server/chunks/6169.js +64 -0
  103. package/dist/prod-next/server/chunks/6208.js +172 -0
  104. package/dist/prod-next/server/chunks/6399.js +24 -0
  105. package/dist/prod-next/server/chunks/6496.js +1 -0
  106. package/dist/prod-next/server/chunks/6823.js +215 -0
  107. package/dist/prod-next/server/chunks/6888.js +1 -0
  108. package/dist/prod-next/server/chunks/6980.js +1 -0
  109. package/dist/prod-next/server/chunks/7245.js +77 -0
  110. package/dist/prod-next/server/chunks/7307.js +1 -0
  111. package/dist/prod-next/server/chunks/7372.js +287 -0
  112. package/dist/prod-next/server/chunks/7384.js +226 -0
  113. package/dist/prod-next/server/chunks/7516.js +1 -0
  114. package/dist/prod-next/server/chunks/7531.js +24 -0
  115. package/dist/prod-next/server/chunks/7591.js +1 -0
  116. package/dist/prod-next/server/chunks/7755.js +138 -0
  117. package/dist/prod-next/server/chunks/7916.js +1 -0
  118. package/dist/prod-next/server/chunks/8078.js +94 -0
  119. package/dist/prod-next/server/chunks/8253.js +41 -0
  120. package/dist/prod-next/server/chunks/8635.js +1 -0
  121. package/dist/prod-next/server/chunks/8688.js +1 -0
  122. package/dist/prod-next/server/chunks/8872.js +1 -0
  123. package/dist/prod-next/server/chunks/9006.js +1 -0
  124. package/dist/prod-next/server/chunks/9018.js +1 -0
  125. package/dist/prod-next/server/chunks/9047.js +1 -0
  126. package/dist/prod-next/server/chunks/9108.js +1 -0
  127. package/dist/prod-next/server/chunks/9280.js +256 -0
  128. package/dist/prod-next/server/chunks/9419.js +1 -0
  129. package/dist/prod-next/server/chunks/9430.js +1 -0
  130. package/dist/prod-next/server/chunks/9586.js +1 -0
  131. package/dist/prod-next/server/chunks/9775.js +1 -0
  132. package/dist/prod-next/server/chunks/992.js +1 -0
  133. package/dist/prod-next/server/chunks/9926.js +1 -0
  134. package/dist/prod-next/server/chunks/9984.js +8 -0
  135. package/dist/prod-next/server/middleware-build-manifest.js +1 -1
  136. package/dist/prod-next/server/middleware-react-loadable-manifest.js +1 -1
  137. package/dist/prod-next/server/pages/404.html +1 -1
  138. package/dist/prod-next/server/pages/500.html +1 -1
  139. package/dist/prod-next/server/server-reference-manifest.js +1 -1
  140. package/dist/prod-next/server/server-reference-manifest.json +1 -1
  141. package/dist/prod-next/server/webpack-runtime.js +1 -1
  142. package/dist/prod-next/static/chunks/1213.e5c6e70a79b5f7e6.js +24 -0
  143. package/dist/prod-next/static/chunks/1519.2f596e5f5af9ef57.js +62 -0
  144. package/dist/prod-next/static/chunks/1759-f5b8fb456f8fb420.js +1 -0
  145. package/dist/prod-next/static/chunks/1974.885c9a133b86d8a4.js +32 -0
  146. package/dist/prod-next/static/chunks/2280.6a7f540787704698.js +1 -0
  147. package/dist/prod-next/static/chunks/2521.7f745f50d30076e7.js +77 -0
  148. package/dist/prod-next/static/chunks/2585.4d6aa847c780c666.js +1 -0
  149. package/dist/prod-next/static/chunks/2589.1aad68c09021b1ac.js +1 -0
  150. package/dist/prod-next/static/chunks/2600.45ba17a0f756fc31.js +1 -0
  151. package/dist/prod-next/static/chunks/2619.9aac8983f30c7c8a.js +1 -0
  152. package/dist/prod-next/static/chunks/26d302b2.9f6383e42abdfe16.js +122 -0
  153. package/dist/prod-next/static/chunks/2768.305914f7900745b6.js +1 -0
  154. package/dist/prod-next/static/chunks/2931.c2acdbf72808b646.js +41 -0
  155. package/dist/prod-next/static/chunks/3012-838f69b72a0dc1b3.js +1 -0
  156. package/dist/prod-next/static/chunks/3016.406e568aa95ff867.js +1 -0
  157. package/dist/prod-next/static/chunks/3022.972047558a20553a.js +172 -0
  158. package/dist/prod-next/static/chunks/3064.3a2a1e7cd4fdca5f.js +1 -0
  159. package/dist/prod-next/static/chunks/3174.03fbed7b7d7a8256.js +1 -0
  160. package/dist/prod-next/static/chunks/3189.b048866768087750.js +1 -0
  161. package/dist/prod-next/static/chunks/3217.1a053843c58b70b5.js +24 -0
  162. package/dist/prod-next/static/chunks/3308.30b95d524f9c1daf.js +74 -0
  163. package/dist/prod-next/static/chunks/331.fc5d78e1e91daf43.js +1 -0
  164. package/dist/prod-next/static/chunks/3510.5d4d1c79533e080c.js +1 -0
  165. package/dist/prod-next/static/chunks/364.ce5400a9dd70b9dc.js +1 -0
  166. package/dist/prod-next/static/chunks/3661-1f08d73aaffb011d.js +1 -0
  167. package/dist/prod-next/static/chunks/370.57cfa27da8567918.js +1 -0
  168. package/dist/prod-next/static/chunks/3789.c418b69121761983.js +1 -0
  169. package/dist/prod-next/static/chunks/3794-c66b3d8325973459.js +32 -0
  170. package/dist/prod-next/static/chunks/3831.25ae62d10dccc27a.js +1 -0
  171. package/dist/prod-next/static/chunks/395.b41e9d76613d37b5.js +90 -0
  172. package/dist/prod-next/static/chunks/3a7aa434.93dbd20997cfd24f.js +82 -0
  173. package/dist/prod-next/static/chunks/4020.3cc19c463c49f969.js +23 -0
  174. package/dist/prod-next/static/chunks/4035.0acc428926d5c8dd.js +1 -0
  175. package/dist/prod-next/static/chunks/405.0902e4f6eb7bd9a2.js +215 -0
  176. package/dist/prod-next/static/chunks/4159.49ae537ff687bdc6.js +138 -0
  177. package/dist/prod-next/static/chunks/4208.384b381ca2757643.js +287 -0
  178. package/dist/prod-next/static/chunks/4263.ffa8d9bd8a0faaae.js +1 -0
  179. package/dist/prod-next/static/chunks/4561.d1a3aaca8bae3224.js +34 -0
  180. package/dist/prod-next/static/chunks/4592.d0f37bc216b9d988.js +93 -0
  181. package/dist/prod-next/static/chunks/4799.0dc7aa21bff2fd2a.js +1 -0
  182. package/dist/prod-next/static/chunks/4bd1b696-e356ca5ba0218e27.js +1 -0
  183. package/dist/prod-next/static/chunks/514.b804c51b26f5e126.js +1 -0
  184. package/dist/prod-next/static/chunks/5270.28592864d38e881c.js +1 -0
  185. package/dist/prod-next/static/chunks/52733464.3870f99cc446f9ff.js +1 -0
  186. package/dist/prod-next/static/chunks/5354.b559c717a607dc5c.js +1 -0
  187. package/dist/prod-next/static/chunks/5448.d3972d9fb464b54f.js +38 -0
  188. package/dist/prod-next/static/chunks/587.d52737d7288c11b4.js +1 -0
  189. package/dist/prod-next/static/chunks/5909.9e37f7b495b11995.js +115 -0
  190. package/dist/prod-next/static/chunks/5949.5504b3545fcc3d34.js +104 -0
  191. package/dist/prod-next/static/chunks/6322-4e97920c006f7933.js +1 -0
  192. package/dist/prod-next/static/chunks/6361.893d04ac34fa1b49.js +148 -0
  193. package/dist/prod-next/static/chunks/6774.8cd53427dbf32cac.js +86 -0
  194. package/dist/prod-next/static/chunks/6968.b80277c724dbc96e.js +1 -0
  195. package/dist/prod-next/static/chunks/7113.4b9e7ea870aa300a.js +8 -0
  196. package/dist/prod-next/static/chunks/7236.55a9728d35f5b8a6.js +1 -0
  197. package/dist/prod-next/static/chunks/7390.dde9b5bc2f6e24b8.js +1 -0
  198. package/dist/prod-next/static/chunks/7806.a903780fcfa3a30e.js +1 -0
  199. package/dist/prod-next/static/chunks/7997.be57e92e583fe635.js +1 -0
  200. package/dist/prod-next/static/chunks/8216.64a72d4d35ea7be0.js +1 -0
  201. package/dist/prod-next/static/chunks/830.5d5274d7ad1cb58e.js +1 -0
  202. package/dist/prod-next/static/chunks/8378.fa91de3406f554c9.js +226 -0
  203. package/dist/prod-next/static/chunks/8501.cd06aa1b0c7e1c6a.js +79 -0
  204. package/dist/prod-next/static/chunks/8663.6ec5792fb4952e45.js +27 -0
  205. package/dist/prod-next/static/chunks/872.96937e758ddce860.js +1 -0
  206. package/dist/prod-next/static/chunks/8803.75bde095fe3613f5.js +1 -0
  207. package/dist/prod-next/static/chunks/8863.72f5dbb820a0e4c3.js +1 -0
  208. package/dist/prod-next/static/chunks/90542734.f7a7b2972ef5a749.js +1 -0
  209. package/dist/prod-next/static/chunks/9379.5bc9c9343ec5a16c.js +64 -0
  210. package/dist/prod-next/static/chunks/9389.906fe7aa9cd6a170.js +1 -0
  211. package/dist/prod-next/static/chunks/9472.8818ee52cfa931a4.js +94 -0
  212. package/dist/prod-next/static/chunks/966.d6babbf8119162cc.js +1 -0
  213. package/dist/prod-next/static/chunks/9884.d4ac1a961b3366f1.js +36 -0
  214. package/dist/prod-next/static/chunks/9909.67cf9940b125243b.js +1 -0
  215. package/dist/prod-next/static/chunks/app/_global-error/page-8be5492b3b872a4c.js +1 -0
  216. package/dist/prod-next/static/chunks/app/_not-found/page-799e445a538b0024.js +1 -0
  217. package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-8be5492b3b872a4c.js +1 -0
  218. package/dist/prod-next/static/chunks/app/board/page-5c181f45738daf1c.js +1 -0
  219. package/dist/prod-next/static/chunks/app/layout-21782e6d13a6f275.js +1 -0
  220. package/dist/prod-next/static/chunks/app/page-51bfe4356fe233e3.js +1 -0
  221. package/dist/prod-next/static/chunks/app/setup/page-3ccfba00c10f9b17.js +1 -0
  222. package/dist/prod-next/static/chunks/d3ac728e.7964f816a1ca64e5.js +1 -0
  223. package/dist/prod-next/static/chunks/e413bedd.3a12a8cb02e28fdf.js +153 -0
  224. package/dist/prod-next/static/chunks/ea233666.f00b64a7d99b4950.js +62 -0
  225. package/dist/prod-next/static/chunks/framework-711ef29bc66f648c.js +1 -0
  226. package/dist/prod-next/static/chunks/main-app-dd26d4f9d120646e.js +1 -0
  227. package/dist/prod-next/static/chunks/{main-bb33c46a9c25dee8.js → main-df4bcaed74f274eb.js} +2 -2
  228. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-8be5492b3b872a4c.js +1 -0
  229. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-8be5492b3b872a4c.js +1 -0
  230. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/global-error-45a0cf77e03361ed.js +1 -0
  231. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-8be5492b3b872a4c.js +1 -0
  232. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-8be5492b3b872a4c.js +1 -0
  233. package/dist/prod-next/static/chunks/webpack-713747aaa9716c4c.js +1 -0
  234. package/dist/prod-next/static/css/cb78d4b0062d9a3f.css +1 -0
  235. package/dist/public/taskmark-snapshot.json +1 -1
  236. package/lib/taskmark/board-index.ts +101 -0
  237. package/lib/taskmark/build-snapshot.ts +28 -7
  238. package/lib/taskmark/derive-parents.test.ts +23 -0
  239. package/lib/taskmark/derive-parents.ts +323 -0
  240. package/lib/taskmark/detail-load.ts +55 -7
  241. package/lib/taskmark/detail-types.ts +10 -2
  242. package/lib/taskmark/dev-reload-token.ts +1 -1
  243. package/lib/taskmark/discover.ts +4 -7
  244. package/lib/taskmark/epic-types.ts +2 -1
  245. package/lib/taskmark/flat-work-item-types.ts +1 -4
  246. package/lib/taskmark/identity.ts +3 -3
  247. package/lib/taskmark/item-types.ts +2 -1
  248. package/lib/taskmark/list-filters.test.ts +23 -0
  249. package/lib/taskmark/list-filters.ts +7 -2
  250. package/lib/taskmark/list-view-mode.test.ts +45 -0
  251. package/lib/taskmark/list-view-mode.ts +19 -8
  252. package/lib/taskmark/load-changelog.test.ts +32 -0
  253. package/lib/taskmark/load-changelog.ts +18 -0
  254. package/lib/taskmark/overall-tree.test.ts +196 -0
  255. package/lib/taskmark/overall-tree.ts +194 -0
  256. package/lib/taskmark/parse-detail.ts +5 -5
  257. package/lib/taskmark/parse-epics.ts +19 -16
  258. package/lib/taskmark/parse-flat-lists.ts +32 -28
  259. package/lib/taskmark/parse-items.ts +47 -9
  260. package/lib/taskmark/parse-stories.ts +18 -15
  261. package/lib/taskmark/points-calendar.test.ts +72 -0
  262. package/lib/taskmark/points-calendar.ts +86 -0
  263. package/lib/taskmark/project-metrics.ts +11 -7
  264. package/lib/taskmark/snapshot-types.ts +2 -0
  265. package/lib/taskmark/story-types.ts +2 -1
  266. package/lib/taskmark/table-sort.ts +4 -3
  267. package/lib/taskmark/timing.ts +29 -18
  268. package/lib/taskmark/validate.ts +1 -1
  269. package/lib/theme-cookie.test.ts +27 -0
  270. package/lib/theme-cookie.ts +43 -0
  271. package/package.json +3 -1
  272. package/public/taskmark-snapshot.json +1 -1
  273. package/dist/prod-next/server/chunks/463.js +0 -1
  274. package/dist/prod-next/server/chunks/54.js +0 -19
  275. package/dist/prod-next/server/chunks/57.js +0 -8
  276. package/dist/prod-next/server/chunks/724.js +0 -1
  277. package/dist/prod-next/static/chunks/4bd1b696-deb4a0a1da1923b0.js +0 -1
  278. package/dist/prod-next/static/chunks/613-6f794154d28bb75a.js +0 -1
  279. package/dist/prod-next/static/chunks/636-626c380f021b95c8.js +0 -1
  280. package/dist/prod-next/static/chunks/669-8103441c67b185da.js +0 -1
  281. package/dist/prod-next/static/chunks/701-df3ee5f1cf837b1a.js +0 -1
  282. package/dist/prod-next/static/chunks/794-25668765560cb197.js +0 -32
  283. package/dist/prod-next/static/chunks/909.932f2af3be7f1e89.js +0 -1
  284. package/dist/prod-next/static/chunks/app/_global-error/page-a1e3320be3da9be1.js +0 -1
  285. package/dist/prod-next/static/chunks/app/_not-found/page-0b3785af737e7968.js +0 -1
  286. package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-a1e3320be3da9be1.js +0 -1
  287. package/dist/prod-next/static/chunks/app/board/page-636f60191db62f52.js +0 -1
  288. package/dist/prod-next/static/chunks/app/layout-856e8a282e69e7d0.js +0 -1
  289. package/dist/prod-next/static/chunks/app/page-562a33b50da7665b.js +0 -1
  290. package/dist/prod-next/static/chunks/app/setup/page-4cf527b758b1d51e.js +0 -1
  291. package/dist/prod-next/static/chunks/framework-b29f9083d3cda3b2.js +0 -1
  292. package/dist/prod-next/static/chunks/main-app-bfb36455d24b3e5a.js +0 -1
  293. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-a1e3320be3da9be1.js +0 -1
  294. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-a1e3320be3da9be1.js +0 -1
  295. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/global-error-dc53ab29e0491fe4.js +0 -1
  296. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-a1e3320be3da9be1.js +0 -1
  297. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-a1e3320be3da9be1.js +0 -1
  298. package/dist/prod-next/static/chunks/webpack-d33b8a6298c94a64.js +0 -1
  299. package/dist/prod-next/static/css/2e6ec4c80894e3c0.css +0 -1
  300. /package/dist/prod-next/static/{yyRnYAFQrJjF5_b5ZIeqh → EI1zQaa_kQ7DqUSE4o3Qo}/_buildManifest.js +0 -0
  301. /package/dist/prod-next/static/{yyRnYAFQrJjF5_b5ZIeqh → EI1zQaa_kQ7DqUSE4o3Qo}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -82,7 +82,7 @@ Board resolution (bound mode only):
82
82
  | Layout | Where to install / run |
83
83
  |--------|------------------------|
84
84
  | Single-git | Product root (discovers `./taskmark/`) or the `taskmark/` folder |
85
- | Multi-git | Dedicated sibling `*-taskmark` repo root (`INDEX.md` at root) |
85
+ | Multi-git | Dedicated sibling `*-taskmark` repo root (`epics/` at root) |
86
86
 
87
87
  You do **not** need to clone `taskmark-frontend`.
88
88
 
package/app/globals.css CHANGED
@@ -94,6 +94,14 @@
94
94
  --shadow-lg: 9px 9px 0 0 var(--shadow-color);
95
95
  --shadow-xl: 14px 14px 0 1px var(--shadow-color);
96
96
  --shadow-2xl: 20px 20px 0 2px var(--shadow-color);
97
+ --surface-gradient: linear-gradient(180deg, #ffffff 0%, #f7f4ff 100%);
98
+ --surface-glow: radial-gradient(ellipse at top, #eadffe 0%, transparent 55%);
99
+ }
100
+
101
+ @layer base {
102
+ body {
103
+ @apply bg-background text-foreground;
104
+ }
97
105
  }
98
106
 
99
107
  /* Markdown body: reset outer margins without Tailwind arbitrary :first-child variants
@@ -105,6 +113,21 @@
105
113
  margin-bottom: 0;
106
114
  }
107
115
 
116
+ .tm-mermaid svg {
117
+ max-width: 100%;
118
+ height: auto;
119
+ }
120
+
121
+ /* Page surfaces follow the theme tokens instead of a light-only gradient. */
122
+ .tm-surface {
123
+ background-color: var(--background);
124
+ background-image: var(--surface-gradient);
125
+ }
126
+
127
+ .tm-surface-glow {
128
+ background-image: var(--surface-glow), var(--surface-gradient);
129
+ }
130
+
108
131
  .dark {
109
132
  --background: #0f0f0f;
110
133
  --foreground: #fafafa;
@@ -134,6 +157,8 @@
134
157
  --chart-5: #7b6df5;
135
158
  --radius: 0.25rem;
136
159
  --border-w: 2px;
160
+ --surface-gradient: linear-gradient(180deg, #0f0f0f 0%, #171325 100%);
161
+ --surface-glow: radial-gradient(ellipse at top, #2f2542 0%, transparent 55%);
137
162
  --shadow-xs: 2px 2px 0 0 var(--shadow-color);
138
163
  --shadow-sm: 3px 3px 0 0 var(--shadow-color);
139
164
  --shadow: 5px 5px 0 0 var(--shadow-color);
package/app/layout.tsx CHANGED
@@ -1,8 +1,11 @@
1
1
  import type { Metadata, Viewport } from "next"
2
2
  import { Archivo_Black, Space_Grotesk } from "next/font/google"
3
+ import Script from "next/script"
3
4
  import { BoardDevReloader } from "@/components/board/board-dev-reloader"
5
+ import { ThemeProvider } from "@/components/theme-provider"
4
6
  import { Toaster } from "@/components/ui/sonner"
5
7
  import { SITE } from "@/lib/site"
8
+ import { THEME_INIT_SCRIPT } from "@/lib/theme-cookie"
6
9
  import "./globals.css"
7
10
 
8
11
  const archivoBlack = Archivo_Black({
@@ -19,7 +22,7 @@ const spaceGrotesk = Space_Grotesk({
19
22
  })
20
23
 
21
24
  const siteDescription =
22
- "Taskmark — product memory for agent work. Local markdown boards for Cursor with sizing, work logs, and velocity."
25
+ "Taskmark — product memory for agent work. Local markdown boards for Cursor with static sizing and work logs."
23
26
 
24
27
  export const viewport: Viewport = {
25
28
  themeColor: "#111111",
@@ -76,13 +79,20 @@ export default function RootLayout({
76
79
  children: React.ReactNode
77
80
  }) {
78
81
  return (
79
- <html lang="en">
82
+ <html lang="en" suppressHydrationWarning>
80
83
  <body
81
84
  className={`${archivoBlack.variable} ${spaceGrotesk.variable} font-sans antialiased`}
82
85
  >
83
- <Toaster />
84
- <BoardDevReloader />
85
- {children}
86
+ <Script
87
+ id="taskmark-theme-init"
88
+ strategy="beforeInteractive"
89
+ dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }}
90
+ />
91
+ <ThemeProvider>
92
+ <Toaster />
93
+ <BoardDevReloader />
94
+ {children}
95
+ </ThemeProvider>
86
96
  </body>
87
97
  </html>
88
98
  )
@@ -39,7 +39,7 @@ export default async function SetupPage({ searchParams }: SetupPageProps) {
39
39
  <div className="relative flex min-h-svh flex-col items-center justify-center px-4 py-16">
40
40
  <div
41
41
  aria-hidden
42
- className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,_#eadffe_0%,_transparent_55%),_linear-gradient(180deg,_#fff_0%,_#f7f4ff_100%)]"
42
+ className="tm-surface-glow pointer-events-none absolute inset-0"
43
43
  />
44
44
  <div className="relative z-10 flex w-full max-w-xl flex-col items-center gap-8">
45
45
  <div className="flex flex-col items-center text-center">
@@ -2,10 +2,7 @@ import fs from "node:fs"
2
2
  import path from "node:path"
3
3
 
4
4
  function hasBoardMarkers(boardPath) {
5
- return (
6
- fs.existsSync(path.join(boardPath, "INDEX.md")) ||
7
- fs.existsSync(path.join(boardPath, "epics"))
8
- )
5
+ return fs.existsSync(path.join(boardPath, "epics"))
9
6
  }
10
7
 
11
8
  function isDedicatedBoardRepoName(name) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { selectActiveProject, openAddProject } from "@/app/setup/actions"
4
4
  import { BrandLogo } from "@/components/brand-logo"
5
+ import { ThemeToggle } from "@/components/theme-toggle"
5
6
  import { Button } from "@/components/ui/button"
6
7
  import { projectOptionLabel } from "@/lib/taskmark/project-label"
7
8
  import type { DiscoveredProject } from "@/lib/taskmark/types"
@@ -90,6 +91,8 @@ export function AppBar({
90
91
  </Button>
91
92
  </form>
92
93
  ) : null}
94
+
95
+ <ThemeToggle />
93
96
  </div>
94
97
  </header>
95
98
  )
@@ -1,17 +1,17 @@
1
1
  import { Suspense } from "react"
2
- import Link from "next/link"
3
2
  import { redirect } from "next/navigation"
4
3
 
5
4
  import { AppBar } from "@/components/board/app-bar"
6
- import { EpicList } from "@/components/board/epic-list"
5
+ import { ChangelogPanel } from "@/components/board/changelog-panel"
7
6
  import { ListViewSwitcher } from "@/components/board/list-view-switcher"
8
- import { OverallWorkItemsList } from "@/components/board/overall-work-items-list"
7
+ import { OverallTreeList } from "@/components/board/overall-tree-list"
8
+ import { PointsHeatmap } from "@/components/board/points-heatmap"
9
9
  import { ProjectStatusMetricsStrip } from "@/components/board/project-status-metrics-strip"
10
10
  import { StaticBoardApp } from "@/components/board/static-board-app"
11
- import { TaskList } from "@/components/board/task-list"
12
11
  import { WorkItemsList } from "@/components/board/work-items-list"
13
12
  import { WorkItemSheetProvider } from "@/components/board/work-item-sheet"
14
13
  import { resolveActiveProject } from "@/lib/taskmark/active-project"
14
+ import { buildBoardIndex } from "@/lib/taskmark/board-index"
15
15
  import {
16
16
  getActiveProjectCookie,
17
17
  getHideCompletedCookies,
@@ -19,7 +19,6 @@ import {
19
19
  } from "@/lib/taskmark/cookies"
20
20
  import {
21
21
  LIST_VIEW_LABELS,
22
- boardHref,
23
22
  parseListViewMode,
24
23
  type ListViewMode,
25
24
  } from "@/lib/taskmark/list-view-mode"
@@ -30,7 +29,9 @@ import {
30
29
  parseWorkItemsForEpic,
31
30
  parseWorkItemsViewForProject,
32
31
  } from "@/lib/taskmark/parse-flat-lists"
32
+ import { loadBoardChangelogMarkdown } from "@/lib/taskmark/load-changelog"
33
33
  import { parseItemsForStory } from "@/lib/taskmark/parse-items"
34
+ import { storyKey } from "@/lib/taskmark/snapshot-types"
34
35
  import {
35
36
  collectCompletedLeafPointSamples,
36
37
  collectMetricLeaves,
@@ -58,7 +59,7 @@ function paramValue(value: string | string[] | undefined): string | null {
58
59
  }
59
60
 
60
61
  function headingForView(view: ListViewMode): string {
61
- if (view === "overall") return "Epics"
62
+ if (view === "overall") return "Overall"
62
63
  return LIST_VIEW_LABELS[view]
63
64
  }
64
65
 
@@ -90,16 +91,17 @@ export async function BoardScreen({ searchParams }: BoardScreenProps) {
90
91
  }
91
92
 
92
93
  const params = await searchParams
93
- const activeView = parseListViewMode(params.view)
94
+ const boardIndex = buildBoardIndex(activeProject.boardPath)
95
+ const changelogMarkdown = loadBoardChangelogMarkdown(activeProject.boardPath)
96
+ const hasChangelog = changelogMarkdown != null
97
+ const activeView = parseListViewMode(params.view, { hasChangelog })
94
98
  const selectedEpicId =
95
99
  activeView === "overall" ? paramValue(params.epic) : null
96
100
  const selectedStoryId =
97
101
  activeView === "overall" && selectedEpicId
98
102
  ? paramValue(params.story)
99
103
  : null
100
- const selectedItemId = paramValue(params.item)
101
-
102
- const list = parseEpicsForProject(activeProject)
104
+ const list = parseEpicsForProject(activeProject, boardIndex)
103
105
  const epicCount = list.epics.length
104
106
  const errorCount = list.errors.length
105
107
 
@@ -107,44 +109,50 @@ export async function BoardScreen({ searchParams }: BoardScreenProps) {
107
109
  ? list.epics.find((e) => e.id === selectedEpicId) ?? null
108
110
  : null
109
111
 
110
- const epicWorkItems =
111
- activeView === "overall" && selectedEpicId != null
112
- ? parseWorkItemsForEpic(
112
+ const workItemsByEpic: Record<
113
+ string,
114
+ ReturnType<typeof parseWorkItemsForEpic>
115
+ > = {}
116
+ const itemsByStory: Record<string, ReturnType<typeof parseItemsForStory>> = {}
117
+ if (activeView === "overall") {
118
+ for (const epic of list.epics) {
119
+ const epicItems = parseWorkItemsForEpic(
120
+ activeProject,
121
+ epic.id,
122
+ epic.title,
123
+ boardIndex
124
+ )
125
+ workItemsByEpic[epic.id] = epicItems
126
+ for (const row of epicItems.rows) {
127
+ if (row.kind !== "story") continue
128
+ itemsByStory[storyKey(epic.id, row.id)] = parseItemsForStory(
113
129
  activeProject,
114
- selectedEpicId,
115
- selectedEpic?.title ?? null
130
+ epic.id,
131
+ row.id,
132
+ boardIndex
116
133
  )
117
- : null
118
-
119
- const selectedStory =
120
- epicWorkItems && selectedStoryId
121
- ? (epicWorkItems.rows.find(
122
- (row) => row.kind === "story" && row.id === selectedStoryId
123
- ) ?? null)
124
- : null
125
-
126
- const itemList =
127
- activeView === "overall" &&
128
- selectedEpicId != null &&
129
- selectedStory != null
130
- ? parseItemsForStory(activeProject, selectedEpicId, selectedStory.id)
131
- : null
134
+ }
135
+ }
136
+ }
132
137
 
133
138
  const workItemsList =
134
- activeView === "overall" || activeView === "workitems"
135
- ? parseWorkItemsViewForProject(activeProject)
139
+ activeView === "workitems"
140
+ ? parseWorkItemsViewForProject(activeProject, boardIndex)
136
141
  : null
137
142
 
143
+ const metricLeaves = collectMetricLeaves(activeProject, boardIndex)
138
144
  /** Match Current Speed exactly: completed task/bug leaf points only. */
139
- const countableCompletions = collectCompletedLeafPointSamples(
140
- collectMetricLeaves(activeProject)
145
+ const countableCompletions =
146
+ collectCompletedLeafPointSamples(metricLeaves)
147
+ const statusMetrics = computeProjectStatusMetrics(
148
+ activeProject,
149
+ boardIndex,
150
+ metricLeaves
141
151
  )
142
152
 
143
- const statusMetrics = computeProjectStatusMetrics(activeProject)
144
-
145
153
  return (
146
154
  <WorkItemSheetProvider>
147
- <div className="min-h-svh bg-[linear-gradient(180deg,_#fff_0%,_#f7f4ff_100%)]">
155
+ <div className="tm-surface min-h-svh">
148
156
  <AppBar
149
157
  projects={workspace.projects}
150
158
  activeProjectId={activeProject.id}
@@ -169,23 +177,20 @@ export async function BoardScreen({ searchParams }: BoardScreenProps) {
169
177
  ? ` · ${workItemsList.rows.length} item${workItemsList.rows.length === 1 ? "" : "s"}`
170
178
  : ""}
171
179
  {activeView === "overall" && selectedEpic
172
- ? ` · selected ${selectedEpic.id}`
180
+ ? ` · expanded ${selectedEpic.id}`
173
181
  : activeView === "overall" && selectedEpicId
174
182
  ? ` · epic ${selectedEpicId} not in list`
175
- : activeView === "overall"
176
- ? " · select an epic to view work items"
177
- : ""}
178
- {activeView === "overall" && selectedStory
179
- ? ` · story ${selectedStory.id}`
180
- : activeView === "overall" && selectedStoryId
181
- ? ` · story ${selectedStoryId} not in list`
182
183
  : ""}
184
+ {activeView === "overall" && selectedStoryId
185
+ ? ` · story ${selectedStoryId}`
186
+ : ""}
183
187
  </p>
184
188
  </div>
185
189
  <ListViewSwitcher
186
190
  activeView={activeView}
187
191
  selectedEpicId={selectedEpicId}
188
- selectedStoryId={selectedStory?.id ?? null}
192
+ selectedStoryId={selectedStoryId}
193
+ hasChangelog={hasChangelog}
189
194
  />
190
195
  </div>
191
196
 
@@ -193,81 +198,16 @@ export async function BoardScreen({ searchParams }: BoardScreenProps) {
193
198
 
194
199
  {activeView === "overall" ? (
195
200
  <>
196
- <EpicList
197
- lists={[list]}
201
+ <PointsHeatmap samples={countableCompletions} />
202
+ <OverallTreeList
203
+ list={list}
204
+ workItemsByEpic={workItemsByEpic}
205
+ itemsByStory={itemsByStory}
198
206
  selectedEpicId={selectedEpicId}
207
+ selectedStoryId={selectedStoryId}
199
208
  countableCompletions={countableCompletions}
200
209
  initialHideCompleted={hideCompletedPrefs.epics}
201
210
  />
202
-
203
- {epicWorkItems ? (
204
- <div className="flex flex-col gap-3">
205
- <div className="flex items-baseline justify-between gap-3">
206
- <h2 className="font-head text-2xl tracking-tight">
207
- Work items for{" "}
208
- {epicWorkItems.epicTitle ?? epicWorkItems.epicId}
209
- </h2>
210
- <Link
211
- href={boardHref({
212
- view: "overall",
213
- item: selectedItemId,
214
- })}
215
- className="text-sm text-muted-foreground underline-offset-2 hover:underline"
216
- >
217
- Clear selection
218
- </Link>
219
- </div>
220
- <p className="text-sm text-muted-foreground">
221
- {epicWorkItems.rows.length} work item
222
- {epicWorkItems.rows.length === 1 ? "" : "s"}
223
- {epicWorkItems.errors.length > 0
224
- ? ` · ${epicWorkItems.errors.length} issue${epicWorkItems.errors.length === 1 ? "" : "s"}`
225
- : ""}
226
- {!selectedStory
227
- ? " · select a story to view its sub tasks"
228
- : ""}
229
- </p>
230
- <OverallWorkItemsList
231
- list={epicWorkItems}
232
- selectedStoryId={selectedStory?.id ?? null}
233
- countableCompletions={countableCompletions}
234
- initialHideCompleted={hideCompletedPrefs.overallWorkItems}
235
- />
236
- </div>
237
- ) : null}
238
-
239
- {itemList && selectedStory ? (
240
- <div className="flex flex-col gap-3">
241
- <div className="flex items-baseline justify-between gap-3">
242
- <h2 className="font-head text-2xl tracking-tight">
243
- Sub Tasks for{" "}
244
- {itemList.storyTitle ?? itemList.storyId}
245
- </h2>
246
- <Link
247
- href={boardHref({
248
- view: "overall",
249
- epic: itemList.epicId,
250
- item: selectedItemId,
251
- })}
252
- className="text-sm text-muted-foreground underline-offset-2 hover:underline"
253
- >
254
- Clear story
255
- </Link>
256
- </div>
257
- <p className="text-sm text-muted-foreground">
258
- {itemList.items.length} sub task
259
- {itemList.items.length === 1 ? "" : "s"}
260
- {itemList.errors.length > 0
261
- ? ` · ${itemList.errors.length} issue${itemList.errors.length === 1 ? "" : "s"}`
262
- : ""}
263
- </p>
264
- <TaskList
265
- list={itemList}
266
- countableCompletions={countableCompletions}
267
- initialHideCompleted={hideCompletedPrefs.tasks}
268
- />
269
- </div>
270
- ) : null}
271
211
  </>
272
212
  ) : null}
273
213
 
@@ -278,6 +218,10 @@ export async function BoardScreen({ searchParams }: BoardScreenProps) {
278
218
  initialHideCompleted={hideCompletedPrefs.workItems}
279
219
  />
280
220
  ) : null}
221
+
222
+ {activeView === "changelog" && changelogMarkdown ? (
223
+ <ChangelogPanel markdown={changelogMarkdown} />
224
+ ) : null}
281
225
  </div>
282
226
  </div>
283
227
  </WorkItemSheetProvider>
@@ -0,0 +1,26 @@
1
+ "use client"
2
+
3
+ import { MarkdownContent } from "@/components/board/markdown-content"
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@/components/ui/card"
11
+
12
+ export function ChangelogPanel({ markdown }: { markdown: string }) {
13
+ return (
14
+ <Card>
15
+ <CardHeader>
16
+ <CardTitle className="font-head text-xl">Changelog</CardTitle>
17
+ <CardDescription>
18
+ Board-root CHANGELOG.md (Keep a Changelog)
19
+ </CardDescription>
20
+ </CardHeader>
21
+ <CardContent>
22
+ <MarkdownContent text={markdown} />
23
+ </CardContent>
24
+ </Card>
25
+ )
26
+ }
@@ -26,6 +26,7 @@ import { ChildProgressBar } from "@/components/board/child-progress-bar"
26
26
  import { ListFiltersBar } from "@/components/board/list-filters-bar"
27
27
  import { ListPagination } from "@/components/board/list-pagination"
28
28
  import { SortableTableHead } from "@/components/board/sortable-table-head"
29
+ import { statusRowClass } from "@/components/board/status-badge"
29
30
  import { TimeframeFilter } from "@/components/board/timeframe-filter"
30
31
  import { ViewWorkItemButton } from "@/components/board/work-item-sheet"
31
32
  import { AttributionAvatarGroup } from "@/components/board/attribution-avatars"
@@ -246,6 +247,7 @@ function ProjectEpicCard({
246
247
  key={`${project.id}:${epic.id}:${epic.filePath}`}
247
248
  data-state={selected ? "selected" : undefined}
248
249
  className={cn(
250
+ statusRowClass(epic.status),
249
251
  selected && "bg-muted/60",
250
252
  "hover:bg-muted/40"
251
253
  )}
@@ -6,8 +6,8 @@ import { useRouter, useSearchParams } from "next/navigation"
6
6
  import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
7
7
  import {
8
8
  LIST_VIEW_LABELS,
9
- LIST_VIEW_MODES,
10
9
  boardHref,
10
+ listViewModes,
11
11
  type ListViewMode,
12
12
  } from "@/lib/taskmark/list-view-mode"
13
13
 
@@ -15,16 +15,19 @@ type ListViewSwitcherProps = {
15
15
  activeView: ListViewMode
16
16
  selectedEpicId?: string | null
17
17
  selectedStoryId?: string | null
18
+ hasChangelog?: boolean
18
19
  }
19
20
 
20
21
  function ListViewSwitcherInner({
21
22
  activeView,
22
23
  selectedEpicId = null,
23
24
  selectedStoryId = null,
25
+ hasChangelog = false,
24
26
  }: ListViewSwitcherProps) {
25
27
  const router = useRouter()
26
28
  const searchParams = useSearchParams()
27
29
  const itemId = searchParams.get("item")
30
+ const modes = listViewModes(hasChangelog)
28
31
 
29
32
  return (
30
33
  <Tabs
@@ -43,7 +46,7 @@ function ListViewSwitcherInner({
43
46
  }}
44
47
  >
45
48
  <TabsList aria-label="Board list view">
46
- {LIST_VIEW_MODES.map((mode) => (
49
+ {modes.map((mode) => (
47
50
  <TabsTrigger key={mode} value={mode}>
48
51
  {LIST_VIEW_LABELS[mode]}
49
52
  </TabsTrigger>
@@ -54,12 +57,13 @@ function ListViewSwitcherInner({
54
57
  }
55
58
 
56
59
  export function ListViewSwitcher(props: ListViewSwitcherProps) {
60
+ const modes = listViewModes(props.hasChangelog ?? false)
57
61
  return (
58
62
  <React.Suspense
59
63
  fallback={
60
64
  <Tabs value={props.activeView}>
61
65
  <TabsList aria-label="Board list view">
62
- {LIST_VIEW_MODES.map((mode) => (
66
+ {modes.map((mode) => (
63
67
  <TabsTrigger key={mode} value={mode} disabled>
64
68
  {LIST_VIEW_LABELS[mode]}
65
69
  </TabsTrigger>
@@ -1,10 +1,22 @@
1
1
  "use client"
2
2
 
3
+ import { isValidElement, type ReactNode } from "react"
3
4
  import ReactMarkdown from "react-markdown"
4
5
  import remarkGfm from "remark-gfm"
5
6
 
7
+ import { MermaidDiagram } from "@/components/board/mermaid-diagram"
6
8
  import { cn } from "@/lib/utils"
7
9
 
10
+ function mermaidSourceFromPreChildren(children: ReactNode): string | null {
11
+ const child = Array.isArray(children) ? children[0] : children
12
+ if (!isValidElement<{ className?: string; children?: ReactNode }>(child)) {
13
+ return null
14
+ }
15
+ const className = child.props.className ?? ""
16
+ if (!className.includes("language-mermaid")) return null
17
+ return String(child.props.children ?? "").replace(/\n$/, "")
18
+ }
19
+
8
20
  type MarkdownContentProps = {
9
21
  text: string
10
22
  className?: string
@@ -71,7 +83,15 @@ export function MarkdownContent({
71
83
  ol: ({ children }) => <span>{children}</span>,
72
84
  li: ({ children }) => <span>{children} </span>,
73
85
  }
74
- : undefined
86
+ : {
87
+ pre({ children }) {
88
+ const mermaidSource = mermaidSourceFromPreChildren(children)
89
+ if (mermaidSource !== null) {
90
+ return <MermaidDiagram chart={mermaidSource} />
91
+ }
92
+ return <pre>{children}</pre>
93
+ },
94
+ }
75
95
  }
76
96
  >
77
97
  {trimmed}
@@ -0,0 +1,88 @@
1
+ "use client"
2
+
3
+ import { useEffect, useId, useState } from "react"
4
+
5
+ function pageIsDark(): boolean {
6
+ if (typeof document === "undefined") return false
7
+ return Boolean(
8
+ document.documentElement.classList.contains("dark") ||
9
+ document.body.classList.contains("dark")
10
+ )
11
+ }
12
+
13
+ export function MermaidDiagram({ chart }: { chart: string }) {
14
+ const reactId = useId().replace(/:/g, "")
15
+ const [dark, setDark] = useState(false)
16
+ const [svg, setSvg] = useState<string | null>(null)
17
+ const [error, setError] = useState<string | null>(null)
18
+
19
+ useEffect(() => {
20
+ const syncDark = () => setDark(pageIsDark())
21
+ syncDark()
22
+ const observer = new MutationObserver(syncDark)
23
+ observer.observe(document.documentElement, {
24
+ attributes: true,
25
+ attributeFilter: ["class"],
26
+ })
27
+ observer.observe(document.body, {
28
+ attributes: true,
29
+ attributeFilter: ["class"],
30
+ })
31
+ return () => observer.disconnect()
32
+ }, [])
33
+
34
+ useEffect(() => {
35
+ let cancelled = false
36
+
37
+ async function renderChart() {
38
+ try {
39
+ const mermaid = (await import("mermaid")).default
40
+ mermaid.initialize({
41
+ startOnLoad: false,
42
+ securityLevel: "strict",
43
+ theme: dark ? "dark" : "neutral",
44
+ })
45
+ const { svg: rendered } = await mermaid.render(
46
+ `tm-mermaid-${reactId}-${dark ? "d" : "l"}`,
47
+ chart
48
+ )
49
+ if (!cancelled) {
50
+ setSvg(rendered)
51
+ setError(null)
52
+ }
53
+ } catch (cause) {
54
+ if (!cancelled) {
55
+ setSvg(null)
56
+ setError(
57
+ cause instanceof Error ? cause.message : "Could not render diagram"
58
+ )
59
+ }
60
+ }
61
+ }
62
+
63
+ void renderChart()
64
+ return () => {
65
+ cancelled = true
66
+ }
67
+ }, [chart, reactId, dark])
68
+
69
+ if (error) {
70
+ return (
71
+ <div className="my-2 rounded border-2 border-border bg-muted p-3">
72
+ <p className="text-xs text-destructive">{error}</p>
73
+ <pre className="mt-2 overflow-x-auto font-mono text-xs">{chart}</pre>
74
+ </div>
75
+ )
76
+ }
77
+
78
+ if (!svg) {
79
+ return <p className="text-sm text-muted-foreground">Rendering diagram…</p>
80
+ }
81
+
82
+ return (
83
+ <div
84
+ className="tm-mermaid my-2 max-w-full overflow-x-auto"
85
+ dangerouslySetInnerHTML={{ __html: svg }}
86
+ />
87
+ )
88
+ }
@@ -26,15 +26,15 @@ export function MetricStatCard({
26
26
  return (
27
27
  <div
28
28
  className={cn(
29
- "flex min-h-[7.5rem] flex-col justify-between gap-3 border-2 border-black bg-white p-4 shadow-none",
29
+ "flex min-h-[7.5rem] flex-col justify-between gap-3 border-2 border-black bg-card p-4 shadow-none",
30
30
  className
31
31
  )}
32
32
  >
33
33
  <div className="flex items-start justify-between gap-3">
34
- <p className="text-sm font-medium text-neutral-800">{title}</p>
34
+ <p className="text-sm font-medium text-card-foreground">{title}</p>
35
35
  <div
36
36
  className={cn(
37
- "flex size-9 shrink-0 items-center justify-center border-2 border-black text-black shadow-[3px_3px_0_0_#000]",
37
+ "flex size-9 shrink-0 items-center justify-center border-2 border-black text-black shadow-[3px_3px_0_0_var(--shadow-color)]",
38
38
  accentClassName
39
39
  )}
40
40
  aria-hidden
@@ -43,7 +43,7 @@ export function MetricStatCard({
43
43
  </div>
44
44
  </div>
45
45
  <div className="flex flex-col gap-1">
46
- <p className="font-head text-3xl font-bold leading-none tracking-tight text-black tabular-nums sm:text-4xl">
46
+ <p className="font-head text-3xl font-bold leading-none tracking-tight text-card-foreground tabular-nums sm:text-4xl">
47
47
  {value}
48
48
  </p>
49
49
  {subtitle ? (