@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.
- package/README.md +1 -1
- package/app/globals.css +25 -0
- package/app/layout.tsx +15 -5
- package/app/setup/page.tsx +1 -1
- package/bin/lib/resolve-board.mjs +1 -4
- package/components/board/app-bar.tsx +3 -0
- package/components/board/board-screen.tsx +61 -117
- package/components/board/changelog-panel.tsx +26 -0
- package/components/board/epic-list.tsx +2 -0
- package/components/board/list-view-switcher.tsx +7 -3
- package/components/board/markdown-content.tsx +21 -1
- package/components/board/mermaid-diagram.tsx +88 -0
- package/components/board/metric-stat-card.tsx +4 -4
- package/components/board/overall-tree-list.tsx +467 -0
- package/components/board/overall-work-items-list.tsx +5 -1
- package/components/board/points-heatmap.tsx +183 -0
- package/components/board/project-status-metrics-strip.tsx +5 -22
- package/components/board/static-board-app.tsx +29 -94
- package/components/board/status-badge.tsx +8 -0
- package/components/board/story-list.tsx +2 -0
- package/components/board/task-list.tsx +5 -1
- package/components/board/work-item-detail-body.tsx +123 -8
- package/components/board/work-items-list.tsx +2 -0
- package/components/theme-provider.tsx +39 -0
- package/components/theme-toggle.tsx +27 -0
- package/components/ui/sonner.tsx +2 -2
- package/dist/prod-next/BUILD_ID +1 -1
- package/dist/prod-next/build-manifest.json +6 -6
- package/dist/prod-next/react-loadable-manifest.json +391 -1
- package/dist/prod-next/server/app/_global-error/page.js +2 -2
- package/dist/prod-next/server/app/_global-error/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/_global-error.html +1 -1
- package/dist/prod-next/server/app/_global-error.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found/page.js +2 -2
- package/dist/prod-next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/_not-found.html +1 -1
- package/dist/prod-next/server/app/_not-found.rsc +19 -17
- package/dist/prod-next/server/app/_not-found.segments/_full.segment.rsc +19 -17
- package/dist/prod-next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_index.segment.rsc +8 -6
- package/dist/prod-next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/prod-next/server/app/api/taskmark-reload/route.js +1 -1
- package/dist/prod-next/server/app/api/taskmark-reload/route.js.nft.json +1 -1
- package/dist/prod-next/server/app/board/page.js +2 -2
- package/dist/prod-next/server/app/board/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/board/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/favicon.ico/route.js +1 -1
- package/dist/prod-next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/dist/prod-next/server/app/page.js +2 -2
- package/dist/prod-next/server/app/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/setup/page.js +2 -2
- package/dist/prod-next/server/app/setup/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/chunks/102.js +90 -0
- package/dist/prod-next/server/chunks/11.js +1 -0
- package/dist/prod-next/server/chunks/1286.js +1 -0
- package/dist/prod-next/server/chunks/1412.js +38 -0
- package/dist/prod-next/server/chunks/1605.js +1 -0
- package/dist/prod-next/server/chunks/169.js +1 -0
- package/dist/prod-next/server/chunks/18.js +86 -0
- package/dist/prod-next/server/chunks/2.js +1 -0
- package/dist/prod-next/server/chunks/2022.js +1 -0
- package/dist/prod-next/server/chunks/2156.js +1 -0
- package/dist/prod-next/server/chunks/2157.js +1 -0
- package/dist/prod-next/server/chunks/2325.js +82 -0
- package/dist/prod-next/server/chunks/2838.js +1 -0
- package/dist/prod-next/server/chunks/2897.js +79 -0
- package/dist/prod-next/server/chunks/3227.js +1 -0
- package/dist/prod-next/server/chunks/3266.js +1 -0
- package/dist/prod-next/server/chunks/{192.js → 3307.js} +5 -5
- package/dist/prod-next/server/chunks/3387.js +19 -0
- package/dist/prod-next/server/chunks/3428.js +32 -0
- package/dist/prod-next/server/chunks/3479.js +1 -0
- package/dist/prod-next/server/chunks/367.js +115 -0
- package/dist/prod-next/server/chunks/3698.js +93 -0
- package/dist/prod-next/server/chunks/3709.js +148 -0
- package/dist/prod-next/server/chunks/41.js +1 -0
- package/dist/prod-next/server/chunks/4107.js +62 -0
- package/dist/prod-next/server/chunks/4569.js +34 -0
- package/dist/prod-next/server/chunks/{741.js → 4741.js} +1 -1
- package/dist/prod-next/server/chunks/4755.js +153 -0
- package/dist/prod-next/server/chunks/5002.js +1 -0
- package/dist/prod-next/server/chunks/5112.js +1 -0
- package/dist/prod-next/server/chunks/5289.js +8 -0
- package/dist/prod-next/server/chunks/5318.js +1 -0
- package/dist/prod-next/server/chunks/5452.js +23 -0
- package/dist/prod-next/server/chunks/5453.js +1 -0
- package/dist/prod-next/server/chunks/5749.js +27 -0
- package/dist/prod-next/server/chunks/5782.js +36 -0
- package/dist/prod-next/server/chunks/6163.js +104 -0
- package/dist/prod-next/server/chunks/6169.js +64 -0
- package/dist/prod-next/server/chunks/6208.js +172 -0
- package/dist/prod-next/server/chunks/6399.js +24 -0
- package/dist/prod-next/server/chunks/6496.js +1 -0
- package/dist/prod-next/server/chunks/6823.js +215 -0
- package/dist/prod-next/server/chunks/6888.js +1 -0
- package/dist/prod-next/server/chunks/6980.js +1 -0
- package/dist/prod-next/server/chunks/7245.js +77 -0
- package/dist/prod-next/server/chunks/7307.js +1 -0
- package/dist/prod-next/server/chunks/7372.js +287 -0
- package/dist/prod-next/server/chunks/7384.js +226 -0
- package/dist/prod-next/server/chunks/7516.js +1 -0
- package/dist/prod-next/server/chunks/7531.js +24 -0
- package/dist/prod-next/server/chunks/7591.js +1 -0
- package/dist/prod-next/server/chunks/7755.js +138 -0
- package/dist/prod-next/server/chunks/7916.js +1 -0
- package/dist/prod-next/server/chunks/8078.js +94 -0
- package/dist/prod-next/server/chunks/8253.js +41 -0
- package/dist/prod-next/server/chunks/8635.js +1 -0
- package/dist/prod-next/server/chunks/8688.js +1 -0
- package/dist/prod-next/server/chunks/8872.js +1 -0
- package/dist/prod-next/server/chunks/9006.js +1 -0
- package/dist/prod-next/server/chunks/9018.js +1 -0
- package/dist/prod-next/server/chunks/9047.js +1 -0
- package/dist/prod-next/server/chunks/9108.js +1 -0
- package/dist/prod-next/server/chunks/9280.js +256 -0
- package/dist/prod-next/server/chunks/9419.js +1 -0
- package/dist/prod-next/server/chunks/9430.js +1 -0
- package/dist/prod-next/server/chunks/9586.js +1 -0
- package/dist/prod-next/server/chunks/9775.js +1 -0
- package/dist/prod-next/server/chunks/992.js +1 -0
- package/dist/prod-next/server/chunks/9926.js +1 -0
- package/dist/prod-next/server/chunks/9984.js +8 -0
- package/dist/prod-next/server/middleware-build-manifest.js +1 -1
- package/dist/prod-next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/prod-next/server/pages/404.html +1 -1
- package/dist/prod-next/server/pages/500.html +1 -1
- package/dist/prod-next/server/server-reference-manifest.js +1 -1
- package/dist/prod-next/server/server-reference-manifest.json +1 -1
- package/dist/prod-next/server/webpack-runtime.js +1 -1
- package/dist/prod-next/static/chunks/1213.e5c6e70a79b5f7e6.js +24 -0
- package/dist/prod-next/static/chunks/1519.2f596e5f5af9ef57.js +62 -0
- package/dist/prod-next/static/chunks/1759-f5b8fb456f8fb420.js +1 -0
- package/dist/prod-next/static/chunks/1974.885c9a133b86d8a4.js +32 -0
- package/dist/prod-next/static/chunks/2280.6a7f540787704698.js +1 -0
- package/dist/prod-next/static/chunks/2521.7f745f50d30076e7.js +77 -0
- package/dist/prod-next/static/chunks/2585.4d6aa847c780c666.js +1 -0
- package/dist/prod-next/static/chunks/2589.1aad68c09021b1ac.js +1 -0
- package/dist/prod-next/static/chunks/2600.45ba17a0f756fc31.js +1 -0
- package/dist/prod-next/static/chunks/2619.9aac8983f30c7c8a.js +1 -0
- package/dist/prod-next/static/chunks/26d302b2.9f6383e42abdfe16.js +122 -0
- package/dist/prod-next/static/chunks/2768.305914f7900745b6.js +1 -0
- package/dist/prod-next/static/chunks/2931.c2acdbf72808b646.js +41 -0
- package/dist/prod-next/static/chunks/3012-838f69b72a0dc1b3.js +1 -0
- package/dist/prod-next/static/chunks/3016.406e568aa95ff867.js +1 -0
- package/dist/prod-next/static/chunks/3022.972047558a20553a.js +172 -0
- package/dist/prod-next/static/chunks/3064.3a2a1e7cd4fdca5f.js +1 -0
- package/dist/prod-next/static/chunks/3174.03fbed7b7d7a8256.js +1 -0
- package/dist/prod-next/static/chunks/3189.b048866768087750.js +1 -0
- package/dist/prod-next/static/chunks/3217.1a053843c58b70b5.js +24 -0
- package/dist/prod-next/static/chunks/3308.30b95d524f9c1daf.js +74 -0
- package/dist/prod-next/static/chunks/331.fc5d78e1e91daf43.js +1 -0
- package/dist/prod-next/static/chunks/3510.5d4d1c79533e080c.js +1 -0
- package/dist/prod-next/static/chunks/364.ce5400a9dd70b9dc.js +1 -0
- package/dist/prod-next/static/chunks/3661-1f08d73aaffb011d.js +1 -0
- package/dist/prod-next/static/chunks/370.57cfa27da8567918.js +1 -0
- package/dist/prod-next/static/chunks/3789.c418b69121761983.js +1 -0
- package/dist/prod-next/static/chunks/3794-c66b3d8325973459.js +32 -0
- package/dist/prod-next/static/chunks/3831.25ae62d10dccc27a.js +1 -0
- package/dist/prod-next/static/chunks/395.b41e9d76613d37b5.js +90 -0
- package/dist/prod-next/static/chunks/3a7aa434.93dbd20997cfd24f.js +82 -0
- package/dist/prod-next/static/chunks/4020.3cc19c463c49f969.js +23 -0
- package/dist/prod-next/static/chunks/4035.0acc428926d5c8dd.js +1 -0
- package/dist/prod-next/static/chunks/405.0902e4f6eb7bd9a2.js +215 -0
- package/dist/prod-next/static/chunks/4159.49ae537ff687bdc6.js +138 -0
- package/dist/prod-next/static/chunks/4208.384b381ca2757643.js +287 -0
- package/dist/prod-next/static/chunks/4263.ffa8d9bd8a0faaae.js +1 -0
- package/dist/prod-next/static/chunks/4561.d1a3aaca8bae3224.js +34 -0
- package/dist/prod-next/static/chunks/4592.d0f37bc216b9d988.js +93 -0
- package/dist/prod-next/static/chunks/4799.0dc7aa21bff2fd2a.js +1 -0
- package/dist/prod-next/static/chunks/4bd1b696-e356ca5ba0218e27.js +1 -0
- package/dist/prod-next/static/chunks/514.b804c51b26f5e126.js +1 -0
- package/dist/prod-next/static/chunks/5270.28592864d38e881c.js +1 -0
- package/dist/prod-next/static/chunks/52733464.3870f99cc446f9ff.js +1 -0
- package/dist/prod-next/static/chunks/5354.b559c717a607dc5c.js +1 -0
- package/dist/prod-next/static/chunks/5448.d3972d9fb464b54f.js +38 -0
- package/dist/prod-next/static/chunks/587.d52737d7288c11b4.js +1 -0
- package/dist/prod-next/static/chunks/5909.9e37f7b495b11995.js +115 -0
- package/dist/prod-next/static/chunks/5949.5504b3545fcc3d34.js +104 -0
- package/dist/prod-next/static/chunks/6322-4e97920c006f7933.js +1 -0
- package/dist/prod-next/static/chunks/6361.893d04ac34fa1b49.js +148 -0
- package/dist/prod-next/static/chunks/6774.8cd53427dbf32cac.js +86 -0
- package/dist/prod-next/static/chunks/6968.b80277c724dbc96e.js +1 -0
- package/dist/prod-next/static/chunks/7113.4b9e7ea870aa300a.js +8 -0
- package/dist/prod-next/static/chunks/7236.55a9728d35f5b8a6.js +1 -0
- package/dist/prod-next/static/chunks/7390.dde9b5bc2f6e24b8.js +1 -0
- package/dist/prod-next/static/chunks/7806.a903780fcfa3a30e.js +1 -0
- package/dist/prod-next/static/chunks/7997.be57e92e583fe635.js +1 -0
- package/dist/prod-next/static/chunks/8216.64a72d4d35ea7be0.js +1 -0
- package/dist/prod-next/static/chunks/830.5d5274d7ad1cb58e.js +1 -0
- package/dist/prod-next/static/chunks/8378.fa91de3406f554c9.js +226 -0
- package/dist/prod-next/static/chunks/8501.cd06aa1b0c7e1c6a.js +79 -0
- package/dist/prod-next/static/chunks/8663.6ec5792fb4952e45.js +27 -0
- package/dist/prod-next/static/chunks/872.96937e758ddce860.js +1 -0
- package/dist/prod-next/static/chunks/8803.75bde095fe3613f5.js +1 -0
- package/dist/prod-next/static/chunks/8863.72f5dbb820a0e4c3.js +1 -0
- package/dist/prod-next/static/chunks/90542734.f7a7b2972ef5a749.js +1 -0
- package/dist/prod-next/static/chunks/9379.5bc9c9343ec5a16c.js +64 -0
- package/dist/prod-next/static/chunks/9389.906fe7aa9cd6a170.js +1 -0
- package/dist/prod-next/static/chunks/9472.8818ee52cfa931a4.js +94 -0
- package/dist/prod-next/static/chunks/966.d6babbf8119162cc.js +1 -0
- package/dist/prod-next/static/chunks/9884.d4ac1a961b3366f1.js +36 -0
- package/dist/prod-next/static/chunks/9909.67cf9940b125243b.js +1 -0
- package/dist/prod-next/static/chunks/app/_global-error/page-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/app/_not-found/page-799e445a538b0024.js +1 -0
- package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/app/board/page-5c181f45738daf1c.js +1 -0
- package/dist/prod-next/static/chunks/app/layout-21782e6d13a6f275.js +1 -0
- package/dist/prod-next/static/chunks/app/page-51bfe4356fe233e3.js +1 -0
- package/dist/prod-next/static/chunks/app/setup/page-3ccfba00c10f9b17.js +1 -0
- package/dist/prod-next/static/chunks/d3ac728e.7964f816a1ca64e5.js +1 -0
- package/dist/prod-next/static/chunks/e413bedd.3a12a8cb02e28fdf.js +153 -0
- package/dist/prod-next/static/chunks/ea233666.f00b64a7d99b4950.js +62 -0
- package/dist/prod-next/static/chunks/framework-711ef29bc66f648c.js +1 -0
- package/dist/prod-next/static/chunks/main-app-dd26d4f9d120646e.js +1 -0
- package/dist/prod-next/static/chunks/{main-bb33c46a9c25dee8.js → main-df4bcaed74f274eb.js} +2 -2
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/global-error-45a0cf77e03361ed.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/webpack-713747aaa9716c4c.js +1 -0
- package/dist/prod-next/static/css/cb78d4b0062d9a3f.css +1 -0
- package/dist/public/taskmark-snapshot.json +1 -1
- package/lib/taskmark/board-index.ts +101 -0
- package/lib/taskmark/build-snapshot.ts +28 -7
- package/lib/taskmark/derive-parents.test.ts +23 -0
- package/lib/taskmark/derive-parents.ts +323 -0
- package/lib/taskmark/detail-load.ts +55 -7
- package/lib/taskmark/detail-types.ts +10 -2
- package/lib/taskmark/dev-reload-token.ts +1 -1
- package/lib/taskmark/discover.ts +4 -7
- package/lib/taskmark/epic-types.ts +2 -1
- package/lib/taskmark/flat-work-item-types.ts +1 -4
- package/lib/taskmark/identity.ts +3 -3
- package/lib/taskmark/item-types.ts +2 -1
- package/lib/taskmark/list-filters.test.ts +23 -0
- package/lib/taskmark/list-filters.ts +7 -2
- package/lib/taskmark/list-view-mode.test.ts +45 -0
- package/lib/taskmark/list-view-mode.ts +19 -8
- package/lib/taskmark/load-changelog.test.ts +32 -0
- package/lib/taskmark/load-changelog.ts +18 -0
- package/lib/taskmark/overall-tree.test.ts +196 -0
- package/lib/taskmark/overall-tree.ts +194 -0
- package/lib/taskmark/parse-detail.ts +5 -5
- package/lib/taskmark/parse-epics.ts +19 -16
- package/lib/taskmark/parse-flat-lists.ts +32 -28
- package/lib/taskmark/parse-items.ts +47 -9
- package/lib/taskmark/parse-stories.ts +18 -15
- package/lib/taskmark/points-calendar.test.ts +72 -0
- package/lib/taskmark/points-calendar.ts +86 -0
- package/lib/taskmark/project-metrics.ts +11 -7
- package/lib/taskmark/snapshot-types.ts +2 -0
- package/lib/taskmark/story-types.ts +2 -1
- package/lib/taskmark/table-sort.ts +4 -3
- package/lib/taskmark/timing.ts +29 -18
- package/lib/taskmark/validate.ts +1 -1
- package/lib/theme-cookie.test.ts +27 -0
- package/lib/theme-cookie.ts +43 -0
- package/package.json +3 -1
- package/public/taskmark-snapshot.json +1 -1
- package/dist/prod-next/server/chunks/463.js +0 -1
- package/dist/prod-next/server/chunks/54.js +0 -19
- package/dist/prod-next/server/chunks/57.js +0 -8
- package/dist/prod-next/server/chunks/724.js +0 -1
- package/dist/prod-next/static/chunks/4bd1b696-deb4a0a1da1923b0.js +0 -1
- package/dist/prod-next/static/chunks/613-6f794154d28bb75a.js +0 -1
- package/dist/prod-next/static/chunks/636-626c380f021b95c8.js +0 -1
- package/dist/prod-next/static/chunks/669-8103441c67b185da.js +0 -1
- package/dist/prod-next/static/chunks/701-df3ee5f1cf837b1a.js +0 -1
- package/dist/prod-next/static/chunks/794-25668765560cb197.js +0 -32
- package/dist/prod-next/static/chunks/909.932f2af3be7f1e89.js +0 -1
- package/dist/prod-next/static/chunks/app/_global-error/page-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/app/_not-found/page-0b3785af737e7968.js +0 -1
- package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/app/board/page-636f60191db62f52.js +0 -1
- package/dist/prod-next/static/chunks/app/layout-856e8a282e69e7d0.js +0 -1
- package/dist/prod-next/static/chunks/app/page-562a33b50da7665b.js +0 -1
- package/dist/prod-next/static/chunks/app/setup/page-4cf527b758b1d51e.js +0 -1
- package/dist/prod-next/static/chunks/framework-b29f9083d3cda3b2.js +0 -1
- package/dist/prod-next/static/chunks/main-app-bfb36455d24b3e5a.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/global-error-dc53ab29e0491fe4.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/webpack-d33b8a6298c94a64.js +0 -1
- package/dist/prod-next/static/css/2e6ec4c80894e3c0.css +0 -1
- /package/dist/prod-next/static/{yyRnYAFQrJjF5_b5ZIeqh → EI1zQaa_kQ7DqUSE4o3Qo}/_buildManifest.js +0 -0
- /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 (`
|
|
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
|
|
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
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
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
|
)
|
package/app/setup/page.tsx
CHANGED
|
@@ -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
|
|
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 {
|
|
5
|
+
import { ChangelogPanel } from "@/components/board/changelog-panel"
|
|
7
6
|
import { ListViewSwitcher } from "@/components/board/list-view-switcher"
|
|
8
|
-
import {
|
|
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 "
|
|
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
|
|
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
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
130
|
+
epic.id,
|
|
131
|
+
row.id,
|
|
132
|
+
boardIndex
|
|
116
133
|
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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 === "
|
|
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 =
|
|
140
|
-
|
|
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
|
|
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
|
-
? ` ·
|
|
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={
|
|
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
|
-
<
|
|
197
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
:
|
|
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-
|
|
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-
|
|
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-[
|
|
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-
|
|
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 ? (
|