@taskmark/ui 0.2.6 → 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 +16 -1
- package/app/board/page.tsx +3 -272
- package/app/globals.css +25 -0
- package/app/layout.tsx +15 -5
- package/app/page.tsx +14 -14
- package/app/setup/actions.ts +4 -4
- package/app/setup/page.tsx +9 -4
- package/bin/lib/resolve-board.mjs +1 -4
- package/bin/taskmark.js +15 -2
- package/components/board/app-bar.tsx +3 -0
- package/components/board/board-screen.tsx +229 -0
- package/components/board/changelog-panel.tsx +26 -0
- package/components/board/epic-list.tsx +4 -2
- 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 +6 -2
- 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 +3 -1
- 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/app-path-routes-manifest.json +2 -2
- package/dist/prod-next/build-manifest.json +6 -6
- package/dist/prod-next/prerender-manifest.json +3 -27
- package/dist/prod-next/react-loadable-manifest.json +392 -2
- 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 -50
- 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/app-paths-manifest.json +2 -2
- 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/3307.js +49 -0
- 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/487.js +1 -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 +20 -9
- 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/scripts/build-static.mjs +61 -3
- package/dist/prod-next/server/app/setup.html +0 -1
- package/dist/prod-next/server/app/setup.meta +0 -17
- package/dist/prod-next/server/app/setup.rsc +0 -22
- package/dist/prod-next/server/app/setup.segments/_full.segment.rsc +0 -22
- package/dist/prod-next/server/app/setup.segments/_head.segment.rsc +0 -6
- package/dist/prod-next/server/app/setup.segments/_index.segment.rsc +0 -8
- package/dist/prod-next/server/app/setup.segments/_tree.segment.rsc +0 -5
- package/dist/prod-next/server/app/setup.segments/setup/__PAGE__.segment.rsc +0 -6
- package/dist/prod-next/server/app/setup.segments/setup.segment.rsc +0 -5
- package/dist/prod-next/server/chunks/318.js +0 -1
- package/dist/prod-next/server/chunks/490.js +0 -8
- package/dist/prod-next/server/chunks/57.js +0 -19
- package/dist/prod-next/server/chunks/616.js +0 -1
- package/dist/prod-next/server/chunks/708.js +0 -1
- 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/51.e9139a9ea6eb36b9.js +0 -1
- package/dist/prod-next/static/chunks/613-6f794154d28bb75a.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/app/_global-error/page-d34451c04262dd7d.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-d34451c04262dd7d.js +0 -1
- package/dist/prod-next/static/chunks/app/board/page-cd1d9be0c0127225.js +0 -1
- package/dist/prod-next/static/chunks/app/layout-856e8a282e69e7d0.js +0 -1
- package/dist/prod-next/static/chunks/app/page-d34451c04262dd7d.js +0 -1
- package/dist/prod-next/static/chunks/app/setup/page-c3003359e8becc84.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-d34451c04262dd7d.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-d34451c04262dd7d.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-d34451c04262dd7d.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-d34451c04262dd7d.js +0 -1
- package/dist/prod-next/static/chunks/webpack-bfe8992962004e2b.js +0 -1
- package/dist/prod-next/static/css/2e6ec4c80894e3c0.css +0 -1
- /package/dist/prod-next/static/{sAWr5ATVuqk6Wqs18kAWs → EI1zQaa_kQ7DqUSE4o3Qo}/_buildManifest.js +0 -0
- /package/dist/prod-next/static/{sAWr5ATVuqk6Wqs18kAWs → EI1zQaa_kQ7DqUSE4o3Qo}/_ssgManifest.js +0 -0
package/lib/taskmark/discover.ts
CHANGED
|
@@ -14,13 +14,10 @@ import {
|
|
|
14
14
|
import type { DiscoveredProject } from "@/lib/taskmark/types"
|
|
15
15
|
|
|
16
16
|
function hasBoardMarkers(boardPath: string): boolean {
|
|
17
|
-
return (
|
|
18
|
-
fs.existsSync(path.join(boardPath, "INDEX.md")) ||
|
|
19
|
-
fs.existsSync(path.join(boardPath, "epics"))
|
|
20
|
-
)
|
|
17
|
+
return fs.existsSync(path.join(boardPath, "epics"))
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
/** Single-project layout: `<project>/taskmark/` with
|
|
20
|
+
/** Single-project layout: `<project>/taskmark/` with epics/. */
|
|
24
21
|
export function nestedBoardPath(candidateProjectRoot: string): string | null {
|
|
25
22
|
const boardPath = path.join(candidateProjectRoot, "taskmark")
|
|
26
23
|
try {
|
|
@@ -33,7 +30,7 @@ export function nestedBoardPath(candidateProjectRoot: string): string | null {
|
|
|
33
30
|
|
|
34
31
|
/**
|
|
35
32
|
* Multi-project layout: dedicated `<name>-taskmark` repo whose root is the board
|
|
36
|
-
* (
|
|
33
|
+
* (`epics/` at the folder root — no nested taskmark/).
|
|
37
34
|
*/
|
|
38
35
|
export function flatBoardPath(candidateRoot: string): string | null {
|
|
39
36
|
if (!isDedicatedBoardRepoName(path.basename(candidateRoot))) return null
|
|
@@ -95,7 +92,7 @@ function shouldSkipDir(name: string): boolean {
|
|
|
95
92
|
* Walk a master folder and find Taskmark boards in subfolders.
|
|
96
93
|
*
|
|
97
94
|
* Recognizes:
|
|
98
|
-
* - Single-project: any directory with a nested `taskmark/` board
|
|
95
|
+
* - Single-project: any directory with a nested `taskmark/epics/` board
|
|
99
96
|
* - Multi-project: dedicated `*-taskmark` folders with board files at the repo root
|
|
100
97
|
*
|
|
101
98
|
* Does not require a board at the master root, but includes the master itself if it
|
|
@@ -8,7 +8,6 @@ export type EpicSummary = {
|
|
|
8
8
|
priority: string
|
|
9
9
|
size: string
|
|
10
10
|
points: number | null
|
|
11
|
-
estimateMinutes: number | null
|
|
12
11
|
actualMinutes: number | null
|
|
13
12
|
actualMs: number | null
|
|
14
13
|
tags: string[]
|
|
@@ -16,6 +15,8 @@ export type EpicSummary = {
|
|
|
16
15
|
resolvers: ContributorIdentity[]
|
|
17
16
|
/** Frontmatter created date (YYYY-MM-DD or ISO). */
|
|
18
17
|
created: string
|
|
18
|
+
/** Frontmatter updated date (YYYY-MM-DD or ISO). */
|
|
19
|
+
updated: string
|
|
19
20
|
/** Frontmatter completed_at (ISO) when solved; empty if open. */
|
|
20
21
|
completedAt: string
|
|
21
22
|
/** Stories + tasks + bugs under this epic (story children and epic-direct). */
|
|
@@ -12,7 +12,6 @@ export type FlatWorkItemRow = {
|
|
|
12
12
|
priority: string
|
|
13
13
|
size: string
|
|
14
14
|
points: number | null
|
|
15
|
-
estimateMinutes: number | null
|
|
16
15
|
actualMinutes: number | null
|
|
17
16
|
actualMs: number | null
|
|
18
17
|
epicId: string | null
|
|
@@ -36,7 +35,6 @@ export type StoryWithEpicRow = {
|
|
|
36
35
|
priority: string
|
|
37
36
|
size: string
|
|
38
37
|
points: number | null
|
|
39
|
-
estimateMinutes: number | null
|
|
40
38
|
actualMinutes: number | null
|
|
41
39
|
actualMs: number | null
|
|
42
40
|
epicId: string
|
|
@@ -60,7 +58,6 @@ export type TaskWithParentsRow = {
|
|
|
60
58
|
priority: string
|
|
61
59
|
size: string
|
|
62
60
|
points: number | null
|
|
63
|
-
estimateMinutes: number | null
|
|
64
61
|
actualMinutes: number | null
|
|
65
62
|
actualMs: number | null
|
|
66
63
|
epicId: string
|
|
@@ -87,7 +84,6 @@ export type WorkItemsViewRow = {
|
|
|
87
84
|
priority: string
|
|
88
85
|
size: string
|
|
89
86
|
points: number | null
|
|
90
|
-
estimateMinutes: number | null
|
|
91
87
|
actualMinutes: number | null
|
|
92
88
|
actualMs: number | null
|
|
93
89
|
epicId: string
|
|
@@ -96,6 +92,7 @@ export type WorkItemsViewRow = {
|
|
|
96
92
|
reporters: ContributorIdentity[]
|
|
97
93
|
resolvers: ContributorIdentity[]
|
|
98
94
|
created: string
|
|
95
|
+
updated: string
|
|
99
96
|
/** Frontmatter completed_at when solved; empty if open. */
|
|
100
97
|
completedAt: string
|
|
101
98
|
/** Child tasks/bugs under a story; 0 for epic-direct leaves. */
|
package/lib/taskmark/identity.ts
CHANGED
|
@@ -123,13 +123,13 @@ export function buildAttributionAvatars(
|
|
|
123
123
|
const name = acc.identity.name || "Unknown"
|
|
124
124
|
if (acc.created && acc.resolved) {
|
|
125
125
|
role = "both"
|
|
126
|
-
label = `
|
|
126
|
+
label = `Reported and implemented by ${name}`
|
|
127
127
|
} else if (acc.created) {
|
|
128
128
|
role = "created"
|
|
129
|
-
label = `
|
|
129
|
+
label = `Reported by ${name}`
|
|
130
130
|
} else {
|
|
131
131
|
role = "resolved"
|
|
132
|
-
label = `
|
|
132
|
+
label = `Implemented by ${name}`
|
|
133
133
|
}
|
|
134
134
|
out.push({ identity: acc.identity, role, label })
|
|
135
135
|
}
|
|
@@ -11,7 +11,6 @@ export type ItemSummary = {
|
|
|
11
11
|
priority: string
|
|
12
12
|
size: string
|
|
13
13
|
points: number | null
|
|
14
|
-
estimateMinutes: number | null
|
|
15
14
|
actualMinutes: number | null
|
|
16
15
|
actualMs: number | null
|
|
17
16
|
tags: string[]
|
|
@@ -19,6 +18,8 @@ export type ItemSummary = {
|
|
|
19
18
|
resolvers: ContributorIdentity[]
|
|
20
19
|
/** Frontmatter created date (YYYY-MM-DD or ISO). */
|
|
21
20
|
created: string
|
|
21
|
+
/** Frontmatter updated date (YYYY-MM-DD or ISO). */
|
|
22
|
+
updated: string
|
|
22
23
|
/** Frontmatter completed_at (ISO) when solved; empty if open. */
|
|
23
24
|
completedAt: string
|
|
24
25
|
parent: string
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import test from "node:test"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
isCompletedStatus,
|
|
6
|
+
passesHideCompleted,
|
|
7
|
+
} from "./list-filters"
|
|
8
|
+
|
|
9
|
+
test("treats shelved as completed", () => {
|
|
10
|
+
assert.equal(isCompletedStatus("shelved"), true)
|
|
11
|
+
assert.equal(passesHideCompleted(true, "shelved"), false)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test("uses terminal child progress when filtering parents", () => {
|
|
15
|
+
assert.equal(
|
|
16
|
+
passesHideCompleted(true, "shelved", { done: 2, total: 2 }),
|
|
17
|
+
false
|
|
18
|
+
)
|
|
19
|
+
assert.equal(
|
|
20
|
+
passesHideCompleted(true, "shelved", { done: 1, total: 2 }),
|
|
21
|
+
true
|
|
22
|
+
)
|
|
23
|
+
})
|
|
@@ -87,10 +87,15 @@ export function matchesWorkItemSearch(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export function isCompletedStatus(status: string): boolean {
|
|
90
|
-
|
|
90
|
+
const normalized = status.trim().toLowerCase()
|
|
91
|
+
return (
|
|
92
|
+
normalized === "done" ||
|
|
93
|
+
normalized === "shelved" ||
|
|
94
|
+
normalized === "cancelled"
|
|
95
|
+
)
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
/** True when every child is
|
|
98
|
+
/** True when every child is terminal (and there is at least one child). */
|
|
94
99
|
export function isChildProgressComplete(
|
|
95
100
|
done?: number,
|
|
96
101
|
total?: number
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import test from "node:test"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
compareByStatusThenPriority,
|
|
6
|
+
listViewModes,
|
|
7
|
+
parseListViewMode,
|
|
8
|
+
statusSortRank,
|
|
9
|
+
} from "./list-view-mode"
|
|
10
|
+
|
|
11
|
+
test("omits changelog from the switcher when the file is absent", () => {
|
|
12
|
+
assert.deepEqual(listViewModes(false), ["overall", "workitems"])
|
|
13
|
+
assert.equal(parseListViewMode("changelog"), "overall")
|
|
14
|
+
assert.equal(parseListViewMode("changelog", { hasChangelog: false }), "overall")
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test("keeps changelog when content is present", () => {
|
|
18
|
+
assert.deepEqual(listViewModes(true), ["overall", "workitems", "changelog"])
|
|
19
|
+
assert.equal(parseListViewMode("changelog", { hasChangelog: true }), "changelog")
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test("sorts every incomplete status before shelved", () => {
|
|
23
|
+
assert.ok(statusSortRank("backlog") < statusSortRank("shelved"))
|
|
24
|
+
assert.ok(statusSortRank("in_progress") < statusSortRank("shelved"))
|
|
25
|
+
assert.ok(statusSortRank("blocked") < statusSortRank("shelved"))
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("sorts shelved rows by completion date instead of priority", () => {
|
|
29
|
+
const olderHighPriority = {
|
|
30
|
+
id: "T-1",
|
|
31
|
+
status: "shelved",
|
|
32
|
+
priority: "critical",
|
|
33
|
+
completedAt: "2026-08-01",
|
|
34
|
+
}
|
|
35
|
+
const newerLowPriority = {
|
|
36
|
+
id: "T-2",
|
|
37
|
+
status: "shelved",
|
|
38
|
+
priority: "low",
|
|
39
|
+
completedAt: "2026-08-29",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
assert.ok(
|
|
43
|
+
compareByStatusThenPriority(olderHighPriority, newerLowPriority) > 0
|
|
44
|
+
)
|
|
45
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const LIST_VIEW_MODES = ["overall", "workitems"] as const
|
|
1
|
+
export const LIST_VIEW_MODES = ["overall", "workitems", "changelog"] as const
|
|
2
2
|
|
|
3
3
|
export type ListViewMode = (typeof LIST_VIEW_MODES)[number]
|
|
4
4
|
|
|
@@ -7,17 +7,27 @@ export const DEFAULT_LIST_VIEW_MODE: ListViewMode = "overall"
|
|
|
7
7
|
export const LIST_VIEW_LABELS: Record<ListViewMode, string> = {
|
|
8
8
|
overall: "Overall",
|
|
9
9
|
workitems: "Work items",
|
|
10
|
+
changelog: "Changelog",
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/** Legacy view query values that map to Work items. */
|
|
13
14
|
const LEGACY_WORKITEMS_VIEWS = new Set(["all", "stories", "tasks", "workitems"])
|
|
14
15
|
|
|
16
|
+
export function listViewModes(hasChangelog: boolean): ListViewMode[] {
|
|
17
|
+
if (hasChangelog) return [...LIST_VIEW_MODES]
|
|
18
|
+
return LIST_VIEW_MODES.filter((mode) => mode !== "changelog")
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export function parseListViewMode(
|
|
16
|
-
value: string | string[] | undefined
|
|
22
|
+
value: string | string[] | undefined,
|
|
23
|
+
options?: { hasChangelog?: boolean }
|
|
17
24
|
): ListViewMode {
|
|
18
25
|
const raw = typeof value === "string" ? value.trim().toLowerCase() : ""
|
|
19
26
|
if (raw === "overall") return "overall"
|
|
20
27
|
if (LEGACY_WORKITEMS_VIEWS.has(raw)) return "workitems"
|
|
28
|
+
if (raw === "changelog") {
|
|
29
|
+
return options?.hasChangelog ? "changelog" : DEFAULT_LIST_VIEW_MODE
|
|
30
|
+
}
|
|
21
31
|
return DEFAULT_LIST_VIEW_MODE
|
|
22
32
|
}
|
|
23
33
|
|
|
@@ -39,15 +49,16 @@ export function boardHref(opts: {
|
|
|
39
49
|
}
|
|
40
50
|
if (opts.item) params.set("item", opts.item)
|
|
41
51
|
const qs = params.toString()
|
|
42
|
-
return qs ?
|
|
52
|
+
return qs ? `/?${qs}` : "/"
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
const STATUS_RANK: Record<string, number> = {
|
|
46
56
|
backlog: 0,
|
|
47
57
|
in_progress: 1,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
blocked: 2,
|
|
59
|
+
done: 3,
|
|
60
|
+
shelved: 4,
|
|
61
|
+
cancelled: 5,
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
const PRIORITY_RANK: Record<string, number> = {
|
|
@@ -66,9 +77,9 @@ export function prioritySortRank(priority: string): number {
|
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
/**
|
|
69
|
-
* Primary: status
|
|
80
|
+
* Primary: status, with every incomplete state before terminal outcomes;
|
|
70
81
|
* For open statuses: priority (highest first), then created (newest first);
|
|
71
|
-
* For done/cancelled: solved date only (completedAt newest first; no priority);
|
|
82
|
+
* For done/shelved/cancelled: solved date only (completedAt newest first; no priority);
|
|
72
83
|
* then id.
|
|
73
84
|
*/
|
|
74
85
|
export function compareByStatusThenPriority(
|
|
@@ -104,7 +115,7 @@ export function compareByStatusThenPriority(
|
|
|
104
115
|
|
|
105
116
|
function usesCompletedDate(status: string): boolean {
|
|
106
117
|
const s = status.trim().toLowerCase()
|
|
107
|
-
return s === "done" || s === "cancelled"
|
|
118
|
+
return s === "done" || s === "shelved" || s === "cancelled"
|
|
108
119
|
}
|
|
109
120
|
|
|
110
121
|
function compareByDateThenId(
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import os from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import test from "node:test"
|
|
6
|
+
|
|
7
|
+
import { loadBoardChangelogMarkdown } from "./load-changelog"
|
|
8
|
+
|
|
9
|
+
function tempBoard(): string {
|
|
10
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "taskmark-changelog-"))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test("returns markdown when CHANGELOG.md has content", () => {
|
|
14
|
+
const dir = tempBoard()
|
|
15
|
+
fs.writeFileSync(
|
|
16
|
+
path.join(dir, "CHANGELOG.md"),
|
|
17
|
+
"# Changelog\n\n## Unreleased\n\n- A button was added.\n"
|
|
18
|
+
)
|
|
19
|
+
const loaded = loadBoardChangelogMarkdown(dir)
|
|
20
|
+
assert.ok(loaded)
|
|
21
|
+
assert.match(loaded, /A button was added/)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("treats missing CHANGELOG.md as absent", () => {
|
|
25
|
+
assert.equal(loadBoardChangelogMarkdown(tempBoard()), null)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("treats whitespace-only CHANGELOG.md as absent", () => {
|
|
29
|
+
const dir = tempBoard()
|
|
30
|
+
fs.writeFileSync(path.join(dir, "CHANGELOG.md"), " \n\t\n")
|
|
31
|
+
assert.equal(loadBoardChangelogMarkdown(dir), null)
|
|
32
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read board-root CHANGELOG.md for the UI. Never writes.
|
|
6
|
+
* Missing, unreadable, or whitespace-only files are absent.
|
|
7
|
+
*/
|
|
8
|
+
export function loadBoardChangelogMarkdown(boardPath: string): string | null {
|
|
9
|
+
const filePath = path.join(boardPath, "CHANGELOG.md")
|
|
10
|
+
try {
|
|
11
|
+
const raw = fs.readFileSync(filePath, "utf8")
|
|
12
|
+
const trimmed = raw.trim()
|
|
13
|
+
if (!trimmed) return null
|
|
14
|
+
return raw
|
|
15
|
+
} catch {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import test from "node:test"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildOverallTree,
|
|
6
|
+
compareOverallNodes,
|
|
7
|
+
filterOverallTree,
|
|
8
|
+
searchRevealIds,
|
|
9
|
+
type OverallTreeNode,
|
|
10
|
+
} from "./overall-tree"
|
|
11
|
+
|
|
12
|
+
function node(
|
|
13
|
+
id: string,
|
|
14
|
+
overrides: Partial<OverallTreeNode> = {}
|
|
15
|
+
): OverallTreeNode {
|
|
16
|
+
return {
|
|
17
|
+
kind: "task",
|
|
18
|
+
id,
|
|
19
|
+
title: id,
|
|
20
|
+
status: "backlog",
|
|
21
|
+
size: "S",
|
|
22
|
+
points: 3,
|
|
23
|
+
tags: [],
|
|
24
|
+
reporters: [],
|
|
25
|
+
resolvers: [],
|
|
26
|
+
created: "2026-01-01",
|
|
27
|
+
updated: "2026-01-01",
|
|
28
|
+
completedAt: "",
|
|
29
|
+
filePath: `/${id}.md`,
|
|
30
|
+
children: [],
|
|
31
|
+
workItemCount: 0,
|
|
32
|
+
doneWorkItemCount: 0,
|
|
33
|
+
...overrides,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test("sorts incomplete before done, shelved, and cancelled", () => {
|
|
38
|
+
const rows = [
|
|
39
|
+
node("T-4", { status: "done", created: "2026-08-29" }),
|
|
40
|
+
node("T-3", { status: "cancelled", created: "2026-08-30" }),
|
|
41
|
+
node("T-6", { status: "shelved", created: "2026-08-31" }),
|
|
42
|
+
node("T-2", { created: "2026-08-28", updated: "2026-08-29" }),
|
|
43
|
+
node("T-1", { created: "2026-08-28", updated: "2026-08-30" }),
|
|
44
|
+
node("T-0", { created: "2026-08-28", updated: "2026-08-30" }),
|
|
45
|
+
node("T-5", { created: "2026-08-29" }),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
assert.deepEqual(
|
|
49
|
+
rows.sort(compareOverallNodes).map((row) => row.id),
|
|
50
|
+
["T-5", "T-0", "T-1", "T-2", "T-6", "T-3", "T-4"]
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("hides an empty General epic like the previous Overall list", () => {
|
|
55
|
+
const general = {
|
|
56
|
+
...node("E-008", {
|
|
57
|
+
kind: "epic",
|
|
58
|
+
title: "General",
|
|
59
|
+
filePath: "/epics/E-008-general/epic.md",
|
|
60
|
+
}),
|
|
61
|
+
priority: "medium",
|
|
62
|
+
actualMinutes: null,
|
|
63
|
+
actualMs: null,
|
|
64
|
+
project: {},
|
|
65
|
+
}
|
|
66
|
+
const list = {
|
|
67
|
+
project: {},
|
|
68
|
+
epics: [{ ...general, workItemCount: 0 }],
|
|
69
|
+
errors: [],
|
|
70
|
+
} as unknown as Parameters<typeof buildOverallTree>[0]
|
|
71
|
+
|
|
72
|
+
assert.deepEqual(buildOverallTree(list, {}, {}), [])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test("search retains ancestors of matching descendants", () => {
|
|
76
|
+
const tree = [
|
|
77
|
+
node("E-1", {
|
|
78
|
+
kind: "epic",
|
|
79
|
+
title: "Parent",
|
|
80
|
+
children: [
|
|
81
|
+
node("S-1", {
|
|
82
|
+
kind: "story",
|
|
83
|
+
children: [node("T-1", { title: "Needle task" })],
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
}),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
const filtered = filterOverallTree(tree, {
|
|
90
|
+
query: "needle",
|
|
91
|
+
hideCompleted: false,
|
|
92
|
+
timeframe: { mode: "none" },
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
assert.equal(filtered[0]?.id, "E-1")
|
|
96
|
+
assert.equal(filtered[0]?.children[0]?.id, "S-1")
|
|
97
|
+
assert.equal(filtered[0]?.children[0]?.children[0]?.id, "T-1")
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("hide-completed retains completed ancestors with incomplete descendants", () => {
|
|
101
|
+
const tree = [
|
|
102
|
+
node("E-1", {
|
|
103
|
+
kind: "epic",
|
|
104
|
+
status: "done",
|
|
105
|
+
children: [
|
|
106
|
+
node("S-1", {
|
|
107
|
+
kind: "story",
|
|
108
|
+
status: "done",
|
|
109
|
+
children: [node("T-1", { status: "backlog" })],
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
}),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
const filtered = filterOverallTree(tree, {
|
|
116
|
+
query: "",
|
|
117
|
+
hideCompleted: true,
|
|
118
|
+
timeframe: { mode: "none" },
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
assert.equal(filtered[0]?.children[0]?.children[0]?.id, "T-1")
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("hide-completed removes shelved branches", () => {
|
|
125
|
+
const tree = [
|
|
126
|
+
node("E-1", {
|
|
127
|
+
kind: "epic",
|
|
128
|
+
status: "shelved",
|
|
129
|
+
children: [
|
|
130
|
+
node("S-1", {
|
|
131
|
+
kind: "story",
|
|
132
|
+
status: "shelved",
|
|
133
|
+
children: [node("T-1", { status: "shelved" })],
|
|
134
|
+
}),
|
|
135
|
+
],
|
|
136
|
+
}),
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
assert.deepEqual(
|
|
140
|
+
filterOverallTree(tree, {
|
|
141
|
+
query: "",
|
|
142
|
+
hideCompleted: true,
|
|
143
|
+
timeframe: { mode: "none" },
|
|
144
|
+
}),
|
|
145
|
+
[]
|
|
146
|
+
)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test("only a search reveals ancestors, so toggles keep the expansion state", () => {
|
|
150
|
+
const tree = [
|
|
151
|
+
node("E-1", {
|
|
152
|
+
kind: "epic",
|
|
153
|
+
children: [
|
|
154
|
+
node("S-1", { kind: "story", children: [node("T-1")] }),
|
|
155
|
+
],
|
|
156
|
+
}),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
assert.deepEqual(searchRevealIds(tree, ""), [])
|
|
160
|
+
assert.deepEqual(searchRevealIds(tree, " "), [])
|
|
161
|
+
assert.deepEqual(searchRevealIds(tree, "needle"), ["E-1", "S-1"])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("timeframe retains ancestor context for matching completed leaves", () => {
|
|
165
|
+
const tree = [
|
|
166
|
+
node("E-1", {
|
|
167
|
+
kind: "epic",
|
|
168
|
+
children: [
|
|
169
|
+
node("S-1", {
|
|
170
|
+
kind: "story",
|
|
171
|
+
children: [
|
|
172
|
+
node("T-old", {
|
|
173
|
+
status: "done",
|
|
174
|
+
completedAt: "2026-01-01",
|
|
175
|
+
}),
|
|
176
|
+
node("T-match", {
|
|
177
|
+
status: "done",
|
|
178
|
+
completedAt: "2026-08-29",
|
|
179
|
+
}),
|
|
180
|
+
],
|
|
181
|
+
}),
|
|
182
|
+
],
|
|
183
|
+
}),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
const filtered = filterOverallTree(tree, {
|
|
187
|
+
query: "",
|
|
188
|
+
hideCompleted: false,
|
|
189
|
+
timeframe: { mode: "range", from: "2026-08-29", to: "2026-08-29" },
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
assert.deepEqual(
|
|
193
|
+
filtered[0]?.children[0]?.children.map((child) => child.id),
|
|
194
|
+
["T-match"]
|
|
195
|
+
)
|
|
196
|
+
})
|