@taskmark/ui 0.2.4 → 0.2.6
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 +46 -10
- package/app/api/taskmark-reload/route.ts +103 -0
- package/app/board/page.tsx +6 -4
- package/app/layout.tsx +2 -0
- package/app/setup/page.tsx +4 -1
- package/bin/lib/resolve-board.mjs +50 -1
- package/bin/lib/watch-board-markdown.mjs +65 -0
- package/bin/taskmark.js +310 -67
- package/components/board/app-bar.tsx +31 -34
- package/components/board/board-dev-reloader.tsx +49 -0
- package/components/board/epic-list.tsx +2 -4
- package/components/board/overall-work-items-list.tsx +2 -1
- package/components/board/static-board-app.tsx +0 -1
- package/components/board/story-list.tsx +2 -1
- package/components/board/task-list.tsx +2 -1
- package/components/board/work-item-sheet.tsx +15 -2
- package/components/board/work-items-list.tsx +2 -2
- package/components/setup/setup-wizard.tsx +2 -6
- package/dist/prod-next/BUILD_ID +1 -0
- package/dist/{standalone/.next → prod-next}/app-path-routes-manifest.json +3 -2
- package/dist/{standalone/.next → prod-next}/build-manifest.json +3 -3
- package/dist/prod-next/export-marker.json +6 -0
- package/dist/prod-next/images-manifest.json +68 -0
- package/dist/prod-next/next-minimal-server.js.nft.json +1 -0
- package/dist/prod-next/next-server.js.nft.json +1 -0
- package/dist/{standalone/.next → prod-next}/prerender-manifest.json +27 -3
- package/dist/{standalone/.next → prod-next}/react-loadable-manifest.json +2 -2
- package/dist/prod-next/required-server-files.js +337 -0
- package/dist/{standalone/.next → prod-next}/required-server-files.json +0 -7
- package/dist/{standalone/.next → prod-next}/routes-manifest.json +6 -0
- package/dist/prod-next/server/app/_global-error/page.js +32 -0
- package/dist/prod-next/server/app/_global-error/page.js.nft.json +1 -0
- package/dist/prod-next/server/app/_global-error/page_client-reference-manifest.js +1 -0
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.html +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found/page.js +2 -0
- package/dist/prod-next/server/app/_not-found/page.js.nft.json +1 -0
- package/dist/prod-next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/dist/prod-next/server/app/_not-found.html +1 -0
- package/dist/prod-next/server/app/_not-found.rsc +21 -0
- package/dist/prod-next/server/app/_not-found.segments/_full.segment.rsc +21 -0
- package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_index.segment.rsc +5 -4
- package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/prod-next/server/app/api/taskmark-reload/route.js +1 -0
- package/dist/prod-next/server/app/api/taskmark-reload/route.js.nft.json +1 -0
- package/dist/prod-next/server/app/api/taskmark-reload/route_client-reference-manifest.js +1 -0
- package/dist/{standalone/.next → prod-next}/server/app/board/page.js +14 -14
- package/dist/prod-next/server/app/board/page.js.nft.json +1 -0
- package/dist/prod-next/server/app/board/page_client-reference-manifest.js +1 -0
- package/dist/{standalone/.next → prod-next}/server/app/favicon.ico/route.js +1 -1
- package/dist/prod-next/server/app/favicon.ico/route.js.nft.json +1 -0
- package/dist/prod-next/server/app/page.js +2 -0
- package/dist/prod-next/server/app/page.js.nft.json +1 -0
- package/dist/prod-next/server/app/page_client-reference-manifest.js +1 -0
- package/dist/prod-next/server/app/setup/page.js +2 -0
- package/dist/prod-next/server/app/setup/page.js.nft.json +1 -0
- package/dist/prod-next/server/app/setup/page_client-reference-manifest.js +1 -0
- package/dist/prod-next/server/app/setup.html +1 -0
- package/dist/prod-next/server/app/setup.meta +17 -0
- package/dist/{standalone/.next/server/app/_not-found.rsc → prod-next/server/app/setup.rsc} +17 -15
- package/dist/{standalone/.next/server/app/_not-found.segments → prod-next/server/app/setup.segments}/_full.segment.rsc +17 -15
- package/dist/prod-next/server/app/setup.segments/_head.segment.rsc +6 -0
- package/dist/prod-next/server/app/setup.segments/_index.segment.rsc +8 -0
- package/dist/prod-next/server/app/setup.segments/_tree.segment.rsc +5 -0
- package/dist/prod-next/server/app/setup.segments/setup/__PAGE__.segment.rsc +6 -0
- package/dist/prod-next/server/app/setup.segments/setup.segment.rsc +5 -0
- package/dist/{standalone/.next → prod-next}/server/app-paths-manifest.json +3 -2
- package/dist/{standalone/.next → prod-next}/server/chunks/319.js +1 -1
- package/dist/{standalone/.next → prod-next}/server/chunks/490.js +2 -2
- package/dist/prod-next/server/chunks/57.js +19 -0
- package/dist/prod-next/server/chunks/616.js +1 -0
- package/dist/prod-next/server/chunks/708.js +1 -0
- package/dist/prod-next/server/chunks/724.js +1 -0
- package/dist/{standalone/.next/server/chunks/445.js → prod-next/server/chunks/741.js} +2 -2
- package/dist/prod-next/server/functions-config-manifest.json +6 -0
- package/dist/prod-next/server/interception-route-rewrite-manifest.js +1 -0
- package/dist/{standalone/.next → prod-next}/server/middleware-build-manifest.js +1 -1
- package/dist/prod-next/server/middleware-react-loadable-manifest.js +1 -0
- package/dist/prod-next/server/pages/404.html +1 -0
- package/dist/{standalone/.next → prod-next}/server/pages/500.html +1 -1
- package/dist/prod-next/server/server-reference-manifest.js +1 -0
- package/dist/prod-next/server/server-reference-manifest.json +1 -0
- package/dist/prod-next/static/chunks/51.e9139a9ea6eb36b9.js +1 -0
- package/dist/{standalone/.next/static/chunks/781-f9f201ddc8c8591a.js → prod-next/static/chunks/613-6f794154d28bb75a.js} +1 -1
- package/dist/prod-next/static/chunks/app/_global-error/page-d34451c04262dd7d.js +1 -0
- package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-d34451c04262dd7d.js +1 -0
- package/dist/prod-next/static/chunks/app/board/page-cd1d9be0c0127225.js +1 -0
- package/dist/prod-next/static/chunks/app/layout-856e8a282e69e7d0.js +1 -0
- package/dist/prod-next/static/chunks/app/page-d34451c04262dd7d.js +1 -0
- package/dist/prod-next/static/chunks/app/setup/page-c3003359e8becc84.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-d34451c04262dd7d.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-d34451c04262dd7d.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-d34451c04262dd7d.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-d34451c04262dd7d.js +1 -0
- package/dist/{standalone/.next/static/chunks/webpack-b59eaf276b7fb38f.js → prod-next/static/chunks/webpack-bfe8992962004e2b.js} +1 -1
- package/dist/prod-next/static/sAWr5ATVuqk6Wqs18kAWs/_buildManifest.js +1 -0
- package/dist/public/taskmark-snapshot.json +1 -0
- package/lib/display-path.ts +6 -0
- package/lib/taskmark/autoconfig.ts +54 -3
- package/lib/taskmark/dev-reload-token.ts +5 -0
- package/lib/taskmark/discover.ts +2 -1
- package/lib/taskmark/project-label.ts +3 -2
- package/lib/taskmark/validate.ts +4 -4
- package/lib/taskmark/workspace-mode.ts +24 -0
- package/lib/taskmark/workspace.ts +5 -2
- package/next.config.ts +3 -8
- package/package.json +7 -4
- package/public/taskmark-snapshot.json +1 -1
- package/scripts/build-static.mjs +10 -1
- package/scripts/prepare-standalone.mjs +53 -20
- package/dist/standalone/.next/BUILD_ID +0 -1
- package/dist/standalone/.next/server/app/_global-error/page.js +0 -32
- package/dist/standalone/.next/server/app/_global-error/page.js.nft.json +0 -1
- package/dist/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +0 -1
- package/dist/standalone/.next/server/app/_not-found/page.js +0 -2
- package/dist/standalone/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/dist/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
- package/dist/standalone/.next/server/app/_not-found.html +0 -1
- package/dist/standalone/.next/server/app/board/page.js.nft.json +0 -1
- package/dist/standalone/.next/server/app/board/page_client-reference-manifest.js +0 -1
- package/dist/standalone/.next/server/app/favicon.ico/route.js.nft.json +0 -1
- package/dist/standalone/.next/server/app/page.js +0 -2
- package/dist/standalone/.next/server/app/page.js.nft.json +0 -1
- package/dist/standalone/.next/server/app/page_client-reference-manifest.js +0 -1
- package/dist/standalone/.next/server/app/setup/page.js +0 -2
- package/dist/standalone/.next/server/app/setup/page.js.nft.json +0 -1
- package/dist/standalone/.next/server/app/setup/page_client-reference-manifest.js +0 -1
- package/dist/standalone/.next/server/chunks/164.js +0 -1
- package/dist/standalone/.next/server/chunks/57.js +0 -19
- package/dist/standalone/.next/server/chunks/570.js +0 -1
- package/dist/standalone/.next/server/chunks/666.js +0 -1
- package/dist/standalone/.next/server/functions-config-manifest.json +0 -4
- package/dist/standalone/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/dist/standalone/.next/server/pages/404.html +0 -1
- package/dist/standalone/.next/server/server-reference-manifest.js +0 -1
- package/dist/standalone/.next/server/server-reference-manifest.json +0 -1
- package/dist/standalone/.next/static/chunks/211.a43663747d668728.js +0 -1
- package/dist/standalone/.next/static/chunks/app/_global-error/page-82d456ade9f47021.js +0 -1
- package/dist/standalone/.next/static/chunks/app/board/page-6db8dec814516c28.js +0 -1
- package/dist/standalone/.next/static/chunks/app/layout-3745e06d87691845.js +0 -1
- package/dist/standalone/.next/static/chunks/app/page-82d456ade9f47021.js +0 -1
- package/dist/standalone/.next/static/chunks/app/setup/page-8a54c6f26633bdca.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-82d456ade9f47021.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-82d456ade9f47021.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-82d456ade9f47021.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-82d456ade9f47021.js +0 -1
- package/dist/standalone/.next/static/jm3kxf8c6qGe0cYKCktaO/_buildManifest.js +0 -1
- package/dist/standalone/node_modules/next/dist/compiled/jest-worker/processChild.js +0 -1
- package/dist/standalone/node_modules/next/dist/compiled/jest-worker/threadChild.js +0 -1
- package/dist/standalone/node_modules/next/dist/server/lib/start-server.js +0 -494
- package/dist/standalone/node_modules/next/dist/server/next-server.js +0 -1429
- package/dist/standalone/node_modules/next/dist/server/next.js +0 -455
- package/dist/standalone/node_modules/next/dist/server/require-hook.js +0 -82
- package/dist/standalone/node_modules/next/dist/server/route-modules/app-page/module.compiled.js +0 -36
- package/dist/standalone/node_modules/next/dist/server/route-modules/pages/module.compiled.js +0 -20
- package/dist/standalone/node_modules/styled-jsx/index.js +0 -1
- package/dist/standalone/node_modules/styled-jsx/package.json +0 -132
- package/dist/standalone/node_modules/styled-jsx/style.js +0 -1
- package/dist/standalone/package.json +0 -77
- package/dist/standalone/public/taskmark-snapshot.json +0 -1
- package/dist/standalone/server.js +0 -44
- /package/dist/{standalone/.next → prod-next}/package.json +0 -0
- /package/dist/{standalone/.next → prod-next}/server/app/_global-error.meta +0 -0
- /package/dist/{standalone/.next → prod-next}/server/app/_not-found.meta +0 -0
- /package/dist/{standalone/.next → prod-next}/server/app/favicon.ico.body +0 -0
- /package/dist/{standalone/.next → prod-next}/server/app/favicon.ico.meta +0 -0
- /package/dist/{standalone/.next → prod-next}/server/chunks/318.js +0 -0
- /package/dist/{standalone/.next → prod-next}/server/middleware-manifest.json +0 -0
- /package/dist/{standalone/.next → prod-next}/server/next-font-manifest.js +0 -0
- /package/dist/{standalone/.next → prod-next}/server/next-font-manifest.json +0 -0
- /package/dist/{standalone/.next → prod-next}/server/pages-manifest.json +0 -0
- /package/dist/{standalone/.next → prod-next}/server/prefetch-hints.json +0 -0
- /package/dist/{standalone/.next → prod-next}/server/webpack-runtime.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/4bd1b696-deb4a0a1da1923b0.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/669-8103441c67b185da.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/701-df3ee5f1cf837b1a.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/794-25668765560cb197.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/app/_not-found/page-0b3785af737e7968.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/framework-b29f9083d3cda3b2.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/main-app-bfb36455d24b3e5a.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/main-bb33c46a9c25dee8.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/next/dist/client/components/builtin/global-error-dc53ab29e0491fe4.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- /package/dist/{standalone/.next → prod-next}/static/css/2e6ec4c80894e3c0.css +0 -0
- /package/dist/{standalone/.next → prod-next}/static/css/c593627ceef17a4b.css +0 -0
- /package/dist/{standalone/.next → prod-next}/static/media/36966cca54120369-s.p.woff2 +0 -0
- /package/dist/{standalone/.next → prod-next}/static/media/982ceffe7b733b3b-s.p.woff2 +0 -0
- /package/dist/{standalone/.next → prod-next}/static/media/b7387a63dd068245-s.woff2 +0 -0
- /package/dist/{standalone/.next → prod-next}/static/media/b8bfb68ab9ab617c-s.woff2 +0 -0
- /package/dist/{standalone/.next → prod-next}/static/media/e1aab0933260df4d-s.woff2 +0 -0
- /package/dist/{standalone/.next/static/jm3kxf8c6qGe0cYKCktaO → prod-next/static/sAWr5ATVuqk6Wqs18kAWs}/_ssgManifest.js +0 -0
- /package/dist/{standalone/public → public}/apple-touch-icon.png +0 -0
- /package/dist/{standalone/public → public}/favicon-96x96.png +0 -0
- /package/dist/{standalone/public → public}/favicon.ico +0 -0
- /package/dist/{standalone/public → public}/favicon.svg +0 -0
- /package/dist/{standalone/public → public}/file.svg +0 -0
- /package/dist/{standalone/public → public}/globe.svg +0 -0
- /package/dist/{standalone/public → public}/next.svg +0 -0
- /package/dist/{standalone/public → public}/og/og-default.png +0 -0
- /package/dist/{standalone/public → public}/site.webmanifest +0 -0
- /package/dist/{standalone/public → public}/tm_dark.png +0 -0
- /package/dist/{standalone/public → public}/tm_light.png +0 -0
- /package/dist/{standalone/public → public}/vercel.svg +0 -0
- /package/dist/{standalone/public → public}/web-app-manifest-192x192.png +0 -0
- /package/dist/{standalone/public → public}/web-app-manifest-512x512.png +0 -0
- /package/dist/{standalone/public → public}/window.svg +0 -0
package/bin/taskmark.js
CHANGED
|
@@ -3,46 +3,65 @@ import { spawn } from "node:child_process"
|
|
|
3
3
|
import fs from "node:fs"
|
|
4
4
|
import http from "node:http"
|
|
5
5
|
import path from "node:path"
|
|
6
|
+
import { createRequire } from "node:module"
|
|
6
7
|
import { fileURLToPath } from "node:url"
|
|
7
8
|
|
|
8
9
|
import { resolveServeBoard } from "./lib/resolve-board.mjs"
|
|
9
10
|
import { startStaticServer } from "./lib/static-preview.mjs"
|
|
11
|
+
import { watchBoardMarkdown } from "./lib/watch-board-markdown.mjs"
|
|
10
12
|
|
|
11
13
|
const DEFAULT_PORT = 8275
|
|
12
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
15
|
const packageRoot = path.resolve(__dirname, "..")
|
|
16
|
+
const requireFromPackage = createRequire(path.join(packageRoot, "package.json"))
|
|
14
17
|
|
|
15
18
|
function printHelp() {
|
|
16
19
|
console.log(`Usage:
|
|
20
|
+
taskmark [options] smart default: local board if found, else setup
|
|
21
|
+
taskmark open [options]
|
|
17
22
|
taskmark serve [options]
|
|
23
|
+
taskmark dev [options]
|
|
18
24
|
taskmark build [options]
|
|
19
25
|
taskmark preview [options]
|
|
20
26
|
|
|
21
27
|
Commands:
|
|
22
|
-
|
|
28
|
+
(default) Bind to a board in/near cwd when one exists; otherwise open
|
|
29
|
+
the standalone setup / project picker (same as global use)
|
|
30
|
+
open Always start in workspace mode (setup / project picker)
|
|
31
|
+
serve Start the prebuilt UI bound to one resolved board (port ${DEFAULT_PORT})
|
|
32
|
+
dev Next.js development server with board markdown live reload
|
|
23
33
|
build Production static HTML export for Vercel / static hosting
|
|
24
34
|
preview Serve an existing static export (default: <board>/out)
|
|
25
35
|
|
|
26
36
|
Options:
|
|
27
37
|
--port, -p <n> Listen port (default ${DEFAULT_PORT}; env PORT / TASKMARK_PORT)
|
|
28
38
|
--board <path> Board or product root (sets TASKMARK_BOARD)
|
|
39
|
+
--workspace, -w Force multi-project / setup mode (skip local board binding)
|
|
29
40
|
--out <dir> Static output directory for build/preview (default: <board>/out)
|
|
30
|
-
--no-open Do not open a browser (serve / preview)
|
|
41
|
+
--no-open Do not open a browser (default / open / serve / preview / dev)
|
|
31
42
|
--help, -h Show help
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
Modes:
|
|
45
|
+
Bound One board from --board / env / cwd / sibling *-taskmark; no project picker.
|
|
46
|
+
Workspace Setup wizard + app-bar project picker (cookies). Used when no local
|
|
47
|
+
board is found, or with open / --workspace.
|
|
48
|
+
|
|
49
|
+
Board resolution for bound mode (same as the UI):
|
|
34
50
|
1. --board / TASKMARK_BOARD
|
|
35
51
|
2. TASKMARK_MASTER
|
|
36
52
|
3. TASKMARK_CWD → npm INIT_CWD → process.cwd()
|
|
37
53
|
(product root with ./taskmark/, or dedicated *-taskmark root)
|
|
54
|
+
4. Sibling <parent>-taskmark next to a product repo (multi-git)
|
|
38
55
|
|
|
39
56
|
Examples:
|
|
57
|
+
npx @taskmark/ui # global: setup / pick projects
|
|
58
|
+
cd my-app-taskmark && npx taskmark # local board in this folder
|
|
40
59
|
npm i @taskmark/ui --save && npx taskmark serve
|
|
60
|
+
npx taskmark open # force setup even inside a board folder
|
|
61
|
+
npx taskmark dev --board .
|
|
41
62
|
npx taskmark build --board .
|
|
42
63
|
npm run preview
|
|
43
|
-
npx taskmark
|
|
44
|
-
npx -p @taskmark/ui taskmark serve
|
|
45
|
-
TASKMARK_BOARD=/path/to/my-app/taskmark npx taskmark serve
|
|
64
|
+
TASKMARK_BOARD=/path/to/board npx taskmark serve
|
|
46
65
|
`)
|
|
47
66
|
}
|
|
48
67
|
|
|
@@ -53,20 +72,24 @@ function parseArgs(argv) {
|
|
|
53
72
|
board: null,
|
|
54
73
|
out: null,
|
|
55
74
|
open: true,
|
|
75
|
+
workspace: false,
|
|
56
76
|
help: false,
|
|
57
77
|
}
|
|
58
78
|
const rest = [...argv]
|
|
59
|
-
if (rest.length === 0
|
|
60
|
-
args.
|
|
79
|
+
if (rest.length === 0) {
|
|
80
|
+
args.command = "auto"
|
|
61
81
|
return args
|
|
62
82
|
}
|
|
63
|
-
|
|
83
|
+
// Bare `taskmark` with only flags → smart default (local board or setup).
|
|
84
|
+
args.command = rest[0].startsWith("-") ? "auto" : rest.shift()
|
|
64
85
|
while (rest.length) {
|
|
65
86
|
const token = rest.shift()
|
|
66
87
|
if (token === "--help" || token === "-h") {
|
|
67
88
|
args.help = true
|
|
68
89
|
} else if (token === "--no-open") {
|
|
69
90
|
args.open = false
|
|
91
|
+
} else if (token === "--workspace" || token === "-w") {
|
|
92
|
+
args.workspace = true
|
|
70
93
|
} else if (token === "--port" || token === "-p") {
|
|
71
94
|
args.port = rest.shift()
|
|
72
95
|
} else if (token === "--board") {
|
|
@@ -97,15 +120,73 @@ function resolvePort(raw) {
|
|
|
97
120
|
return n
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
function resolveNextBin() {
|
|
124
|
+
try {
|
|
125
|
+
return requireFromPackage.resolve("next/dist/bin/next")
|
|
126
|
+
} catch {
|
|
127
|
+
const fallback = path.join(
|
|
128
|
+
packageRoot,
|
|
129
|
+
"node_modules",
|
|
130
|
+
"next",
|
|
131
|
+
"dist",
|
|
132
|
+
"bin",
|
|
133
|
+
"next"
|
|
134
|
+
)
|
|
135
|
+
if (fs.existsSync(fallback)) return fallback
|
|
136
|
+
throw new Error(
|
|
137
|
+
"next binary not found. Reinstall @taskmark/ui (next is required for serve/dev)."
|
|
138
|
+
)
|
|
107
139
|
}
|
|
108
|
-
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Ensure packageRoot/.next points at a usable production build.
|
|
144
|
+
* Published packages ship dist/prod-next (npm cannot pack nested node_modules
|
|
145
|
+
* used by Next standalone).
|
|
146
|
+
*/
|
|
147
|
+
function ensureProdNext() {
|
|
148
|
+
const nextDir = path.join(packageRoot, ".next")
|
|
149
|
+
const shipped = path.join(packageRoot, "dist", "prod-next")
|
|
150
|
+
const hasLocalServer = fs.existsSync(path.join(nextDir, "server"))
|
|
151
|
+
const hasShippedServer = fs.existsSync(path.join(shipped, "server"))
|
|
152
|
+
|
|
153
|
+
if (hasLocalServer) return nextDir
|
|
154
|
+
if (!hasShippedServer) {
|
|
155
|
+
throw new Error(`Prebuilt UI not found.
|
|
156
|
+
|
|
157
|
+
Expected ${shipped} (or a local .next/server from npm run build).
|
|
158
|
+
Reinstall @taskmark/ui or rebuild the package.`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (fs.existsSync(nextDir)) {
|
|
162
|
+
const st = fs.lstatSync(nextDir)
|
|
163
|
+
if (st.isSymbolicLink() || st.isDirectory()) {
|
|
164
|
+
fs.rmSync(nextDir, { recursive: true, force: true })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const type = process.platform === "win32" ? "junction" : "dir"
|
|
170
|
+
fs.symlinkSync(shipped, nextDir, type)
|
|
171
|
+
} catch {
|
|
172
|
+
fs.cpSync(shipped, nextDir, { recursive: true })
|
|
173
|
+
}
|
|
174
|
+
return nextDir
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function touchDevReloadToken() {
|
|
178
|
+
const tokenFile = path.join(
|
|
179
|
+
packageRoot,
|
|
180
|
+
"lib",
|
|
181
|
+
"taskmark",
|
|
182
|
+
"dev-reload-token.ts"
|
|
183
|
+
)
|
|
184
|
+
const stamp = String(Date.now())
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
tokenFile,
|
|
187
|
+
`/**\n * Touched by \`taskmark dev\` when board markdown changes so Next Fast Refresh\n * re-runs server components that import this module.\n */\nexport const DEV_RELOAD_TOKEN = "${stamp}"\n`,
|
|
188
|
+
"utf8"
|
|
189
|
+
)
|
|
109
190
|
}
|
|
110
191
|
|
|
111
192
|
function openBrowser(url) {
|
|
@@ -155,8 +236,48 @@ function waitForServer(port, timeoutMs = 60_000) {
|
|
|
155
236
|
})
|
|
156
237
|
}
|
|
157
238
|
|
|
158
|
-
|
|
159
|
-
|
|
239
|
+
function callerCwd() {
|
|
240
|
+
return process.env.INIT_CWD || process.cwd()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isWorkspaceRun(args) {
|
|
244
|
+
return args.command === "open" || args.workspace === true
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Decide bound vs workspace for the default (`auto`) command.
|
|
249
|
+
* Returns a serve-compatible args object with workspace forced when unbound.
|
|
250
|
+
*/
|
|
251
|
+
function resolveAutoArgs(args) {
|
|
252
|
+
if (args.workspace) {
|
|
253
|
+
return { ...args, command: "open", workspace: true }
|
|
254
|
+
}
|
|
255
|
+
if (args.board) {
|
|
256
|
+
return { ...args, command: "serve", workspace: false }
|
|
257
|
+
}
|
|
258
|
+
const resolved = resolveServeBoard(null)
|
|
259
|
+
if (resolved) {
|
|
260
|
+
return { ...args, command: "serve", workspace: false }
|
|
261
|
+
}
|
|
262
|
+
return { ...args, command: "open", workspace: true }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Workspace mode must out-rank an inherited board bind, so blank those vars for
|
|
267
|
+
* the child (the UI reads empty as unset) instead of only adding the flag.
|
|
268
|
+
*/
|
|
269
|
+
function workspaceEnv() {
|
|
270
|
+
return {
|
|
271
|
+
TASKMARK_WORKSPACE: "1",
|
|
272
|
+
TASKMARK_WORKSPACE_ROOT: callerCwd(),
|
|
273
|
+
TASKMARK_BOARD: "",
|
|
274
|
+
TASKMARK_MASTER: "",
|
|
275
|
+
TASKMARK_CWD: "",
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function resolveBoardOrExit(boardArg, command) {
|
|
280
|
+
const resolved = resolveServeBoard(boardArg)
|
|
160
281
|
if (!resolved) {
|
|
161
282
|
console.error(`No Taskmark board found.
|
|
162
283
|
|
|
@@ -164,52 +285,157 @@ Looked for:
|
|
|
164
285
|
- --board / TASKMARK_BOARD
|
|
165
286
|
- TASKMARK_MASTER
|
|
166
287
|
- cwd layouts: nested ./taskmark/ or dedicated *-taskmark root
|
|
288
|
+
- sibling <parent>-taskmark beside a product repo
|
|
167
289
|
|
|
168
290
|
Fix: cd into a product repo (with ./taskmark/) or a board root, or set TASKMARK_BOARD.
|
|
169
|
-
Then re-run: npx taskmark
|
|
170
|
-
(or: npx -p @taskmark/ui taskmark
|
|
291
|
+
Then re-run: npx taskmark ${command}
|
|
292
|
+
(or: npx -p @taskmark/ui taskmark ${command})
|
|
293
|
+
|
|
294
|
+
To pick a project in the browser instead: npx taskmark`)
|
|
171
295
|
process.exit(1)
|
|
172
296
|
}
|
|
297
|
+
return resolved
|
|
298
|
+
}
|
|
173
299
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
From the taskmark package source, run:
|
|
179
|
-
npm run build
|
|
180
|
-
|
|
181
|
-
Or install a published build that includes dist/standalone.`)
|
|
182
|
-
process.exit(1)
|
|
300
|
+
function attachChildLifecycle(child, onShutdown) {
|
|
301
|
+
const shutdown = (signal) => {
|
|
302
|
+
onShutdown?.()
|
|
303
|
+
if (!child.killed) child.kill(signal)
|
|
183
304
|
}
|
|
305
|
+
process.on("SIGINT", () => shutdown("SIGINT"))
|
|
306
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"))
|
|
307
|
+
child.on("exit", (code, signal) => {
|
|
308
|
+
onShutdown?.()
|
|
309
|
+
if (signal) process.exit(1)
|
|
310
|
+
process.exit(code ?? 0)
|
|
311
|
+
})
|
|
312
|
+
return shutdown
|
|
313
|
+
}
|
|
184
314
|
|
|
315
|
+
async function serve(args) {
|
|
316
|
+
const workspace = isWorkspaceRun(args)
|
|
317
|
+
const resolved = workspace ? null : resolveBoardOrExit(args.board, "serve")
|
|
185
318
|
const port = resolvePort(args.port)
|
|
186
319
|
const url = `http://localhost:${port}`
|
|
187
|
-
const
|
|
320
|
+
const nextBin = resolveNextBin()
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
ensureProdNext()
|
|
324
|
+
} catch (err) {
|
|
325
|
+
console.error(String(err?.message || err))
|
|
326
|
+
process.exit(1)
|
|
327
|
+
}
|
|
188
328
|
|
|
189
329
|
console.log(`Taskmark UI`)
|
|
190
|
-
|
|
330
|
+
if (workspace) {
|
|
331
|
+
console.log(` mode: workspace (choose projects in the browser)`)
|
|
332
|
+
console.log(` from: ${callerCwd()}`)
|
|
333
|
+
} else {
|
|
334
|
+
console.log(` board: ${resolved.boardPath} (${resolved.source})`)
|
|
335
|
+
}
|
|
191
336
|
console.log(` listen: ${url}`)
|
|
192
337
|
|
|
193
|
-
const child = spawn(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
338
|
+
const child = spawn(
|
|
339
|
+
process.execPath,
|
|
340
|
+
[nextBin, "start", "-H", "0.0.0.0", "-p", String(port)],
|
|
341
|
+
{
|
|
342
|
+
cwd: packageRoot,
|
|
343
|
+
env: {
|
|
344
|
+
...process.env,
|
|
345
|
+
PORT: String(port),
|
|
346
|
+
HOSTNAME: "0.0.0.0",
|
|
347
|
+
...(workspace
|
|
348
|
+
? workspaceEnv()
|
|
349
|
+
: { TASKMARK_BOARD: resolved.boardPath }),
|
|
350
|
+
NODE_ENV: "production",
|
|
351
|
+
},
|
|
352
|
+
stdio: "inherit",
|
|
353
|
+
}
|
|
354
|
+
)
|
|
203
355
|
|
|
204
|
-
const shutdown = (
|
|
205
|
-
|
|
356
|
+
const shutdown = attachChildLifecycle(child)
|
|
357
|
+
|
|
358
|
+
const hosted =
|
|
359
|
+
Boolean(process.env.VERCEL) ||
|
|
360
|
+
Boolean(process.env.NOW_REGION) ||
|
|
361
|
+
process.env.CI === "true"
|
|
362
|
+
const shouldOpen = args.open && !hosted
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
await waitForServer(port)
|
|
366
|
+
if (shouldOpen) {
|
|
367
|
+
openBrowser(url)
|
|
368
|
+
} else {
|
|
369
|
+
console.log(`Ready: ${url}`)
|
|
370
|
+
}
|
|
371
|
+
} catch (err) {
|
|
372
|
+
console.error(String(err?.message || err))
|
|
373
|
+
shutdown("SIGTERM")
|
|
374
|
+
process.exit(1)
|
|
206
375
|
}
|
|
207
|
-
|
|
208
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"))
|
|
376
|
+
}
|
|
209
377
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
378
|
+
async function dev(args) {
|
|
379
|
+
const workspace = isWorkspaceRun(args)
|
|
380
|
+
const resolved = workspace ? null : resolveBoardOrExit(args.board, "dev")
|
|
381
|
+
const port = resolvePort(args.port)
|
|
382
|
+
const url = `http://localhost:${port}`
|
|
383
|
+
const nextBin = resolveNextBin()
|
|
384
|
+
|
|
385
|
+
console.log(`Taskmark UI (dev)`)
|
|
386
|
+
if (workspace) {
|
|
387
|
+
console.log(` mode: workspace (choose projects in the browser)`)
|
|
388
|
+
console.log(` from: ${callerCwd()}`)
|
|
389
|
+
} else {
|
|
390
|
+
console.log(` board: ${resolved.boardPath} (${resolved.source})`)
|
|
391
|
+
}
|
|
392
|
+
console.log(` listen: ${url}`)
|
|
393
|
+
console.log(
|
|
394
|
+
workspace
|
|
395
|
+
? ` watch: off (no single board bound)`
|
|
396
|
+
: ` watch: **/*.md under board`
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
const child = spawn(
|
|
400
|
+
process.execPath,
|
|
401
|
+
[nextBin, "dev", "--webpack", "-H", "0.0.0.0", "-p", String(port)],
|
|
402
|
+
{
|
|
403
|
+
cwd: packageRoot,
|
|
404
|
+
env: {
|
|
405
|
+
...process.env,
|
|
406
|
+
PORT: String(port),
|
|
407
|
+
...(workspace
|
|
408
|
+
? workspaceEnv()
|
|
409
|
+
: {
|
|
410
|
+
TASKMARK_BOARD: resolved.boardPath,
|
|
411
|
+
TASKMARK_CWD: resolved.boardPath,
|
|
412
|
+
}),
|
|
413
|
+
NODE_ENV: "development",
|
|
414
|
+
},
|
|
415
|
+
stdio: "inherit",
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
let debounce = null
|
|
420
|
+
const stopWatch = workspace
|
|
421
|
+
? () => {}
|
|
422
|
+
: watchBoardMarkdown(resolved.boardPath, () => {
|
|
423
|
+
if (debounce) clearTimeout(debounce)
|
|
424
|
+
debounce = setTimeout(() => {
|
|
425
|
+
try {
|
|
426
|
+
touchDevReloadToken()
|
|
427
|
+
console.log(`[taskmark] board markdown changed — refreshing`)
|
|
428
|
+
} catch (err) {
|
|
429
|
+
console.warn(
|
|
430
|
+
`[taskmark] reload touch failed: ${err?.message || err}`
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
}, 200)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
const shutdown = attachChildLifecycle(child, () => {
|
|
437
|
+
stopWatch()
|
|
438
|
+
if (debounce) clearTimeout(debounce)
|
|
213
439
|
})
|
|
214
440
|
|
|
215
441
|
const hosted =
|
|
@@ -219,7 +445,7 @@ Or install a published build that includes dist/standalone.`)
|
|
|
219
445
|
const shouldOpen = args.open && !hosted
|
|
220
446
|
|
|
221
447
|
try {
|
|
222
|
-
await waitForServer(port)
|
|
448
|
+
await waitForServer(port, 120_000)
|
|
223
449
|
if (shouldOpen) {
|
|
224
450
|
openBrowser(url)
|
|
225
451
|
} else {
|
|
@@ -241,19 +467,7 @@ async function preview(args) {
|
|
|
241
467
|
}
|
|
242
468
|
|
|
243
469
|
const boardArg = resolveCallerPath(args.board)
|
|
244
|
-
const resolved =
|
|
245
|
-
if (!resolved) {
|
|
246
|
-
console.error(`No Taskmark board found.
|
|
247
|
-
|
|
248
|
-
Looked for:
|
|
249
|
-
- --board / TASKMARK_BOARD
|
|
250
|
-
- TASKMARK_MASTER
|
|
251
|
-
- cwd layouts: nested ./taskmark/ or dedicated *-taskmark root
|
|
252
|
-
|
|
253
|
-
Fix: cd into a product repo (with ./taskmark/) or a board root, or set TASKMARK_BOARD.
|
|
254
|
-
Then re-run: npx taskmark preview`)
|
|
255
|
-
process.exit(1)
|
|
256
|
-
}
|
|
470
|
+
const resolved = resolveBoardOrExit(boardArg, "preview")
|
|
257
471
|
|
|
258
472
|
const outDir = args.out
|
|
259
473
|
? resolveCallerPath(args.out)
|
|
@@ -299,7 +513,30 @@ async function main() {
|
|
|
299
513
|
printHelp()
|
|
300
514
|
process.exit(args.help || !args.command ? 0 : 1)
|
|
301
515
|
}
|
|
302
|
-
|
|
516
|
+
const workspaceCommands = new Set(["auto", "open", "serve", "dev"])
|
|
517
|
+
if (args.workspace && !workspaceCommands.has(args.command)) {
|
|
518
|
+
console.error(`--workspace applies to the default command, open, serve, and dev.
|
|
519
|
+
|
|
520
|
+
Use: npx taskmark (or: npx taskmark open)`)
|
|
521
|
+
process.exit(1)
|
|
522
|
+
}
|
|
523
|
+
if ((isWorkspaceRun(args) || (args.command === "auto" && args.workspace)) && args.board) {
|
|
524
|
+
console.error(`--board and workspace mode are mutually exclusive.
|
|
525
|
+
|
|
526
|
+
Bind one board: npx taskmark serve --board ${args.board}
|
|
527
|
+
Pick projects in UI: npx taskmark open`)
|
|
528
|
+
process.exit(1)
|
|
529
|
+
}
|
|
530
|
+
if (args.command === "auto") {
|
|
531
|
+
try {
|
|
532
|
+
await serve(resolveAutoArgs(args))
|
|
533
|
+
} catch (err) {
|
|
534
|
+
console.error(String(err?.message || err))
|
|
535
|
+
process.exit(1)
|
|
536
|
+
}
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
if (args.command === "open" || args.command === "serve") {
|
|
303
540
|
try {
|
|
304
541
|
await serve(args)
|
|
305
542
|
} catch (err) {
|
|
@@ -308,6 +545,15 @@ async function main() {
|
|
|
308
545
|
}
|
|
309
546
|
return
|
|
310
547
|
}
|
|
548
|
+
if (args.command === "dev") {
|
|
549
|
+
try {
|
|
550
|
+
await dev(args)
|
|
551
|
+
} catch (err) {
|
|
552
|
+
console.error(String(err?.message || err))
|
|
553
|
+
process.exit(1)
|
|
554
|
+
}
|
|
555
|
+
return
|
|
556
|
+
}
|
|
311
557
|
if (args.command === "preview") {
|
|
312
558
|
try {
|
|
313
559
|
await preview(args)
|
|
@@ -319,14 +565,11 @@ async function main() {
|
|
|
319
565
|
}
|
|
320
566
|
if (args.command === "build") {
|
|
321
567
|
const buildScript = path.join(packageRoot, "scripts", "build-static.mjs")
|
|
322
|
-
// Resolve relative paths against the caller's cwd (npm/yarn script dir),
|
|
323
|
-
// not packageRoot — the child runs with cwd = @taskmark/ui.
|
|
324
568
|
const callerCwd = process.env.INIT_CWD || process.cwd()
|
|
325
569
|
const childArgs = [buildScript]
|
|
326
570
|
if (args.board) {
|
|
327
571
|
childArgs.push("--board", path.resolve(callerCwd, args.board))
|
|
328
572
|
} else {
|
|
329
|
-
// Prefer the invoke directory as the board when no --board flag.
|
|
330
573
|
childArgs.push("--board", callerCwd)
|
|
331
574
|
}
|
|
332
575
|
if (args.out) {
|
|
@@ -9,15 +9,13 @@ import type { DiscoveredProject } from "@/lib/taskmark/types"
|
|
|
9
9
|
type AppBarProps = {
|
|
10
10
|
projects: DiscoveredProject[]
|
|
11
11
|
activeProjectId: string
|
|
12
|
-
|
|
13
|
-
/** When true, workspace is env/cwd-bound — hide multi-master add. */
|
|
12
|
+
/** When true, workspace is env/cwd-bound — hide project switcher and add. */
|
|
14
13
|
autoconfig?: boolean
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export function AppBar({
|
|
18
17
|
projects,
|
|
19
18
|
activeProjectId,
|
|
20
|
-
masterCount,
|
|
21
19
|
autoconfig = false,
|
|
22
20
|
}: AppBarProps) {
|
|
23
21
|
return (
|
|
@@ -49,42 +47,41 @@ export function AppBar({
|
|
|
49
47
|
</span>
|
|
50
48
|
</p>
|
|
51
49
|
<p className="truncate text-xs text-muted-foreground transition-transform duration-200 group-hover:translate-y-1">
|
|
52
|
-
|
|
53
|
-
{masterCount > 0
|
|
54
|
-
? ` · ${masterCount} workspace${masterCount === 1 ? "" : "s"}`
|
|
55
|
-
: ""}
|
|
50
|
+
Product memory for agent work
|
|
56
51
|
</p>
|
|
57
52
|
</div>
|
|
58
53
|
</div>
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<label htmlFor="projectId" className="sr-only">
|
|
65
|
-
Active project
|
|
66
|
-
</label>
|
|
67
|
-
<select
|
|
68
|
-
id="projectId"
|
|
69
|
-
name="projectId"
|
|
70
|
-
defaultValue={activeProjectId}
|
|
71
|
-
onChange={(e) => {
|
|
72
|
-
e.currentTarget.form?.requestSubmit()
|
|
73
|
-
}}
|
|
74
|
-
className="max-w-[20rem] rounded border-2 border-border bg-input px-3 py-1.5 text-sm shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
55
|
+
{!autoconfig ? (
|
|
56
|
+
<form
|
|
57
|
+
action={selectActiveProject}
|
|
58
|
+
className="flex min-w-0 flex-wrap items-center gap-2"
|
|
75
59
|
>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
60
|
+
<label htmlFor="projectId" className="sr-only">
|
|
61
|
+
Active project
|
|
62
|
+
</label>
|
|
63
|
+
<select
|
|
64
|
+
id="projectId"
|
|
65
|
+
name="projectId"
|
|
66
|
+
defaultValue={activeProjectId}
|
|
67
|
+
onChange={(e) => {
|
|
68
|
+
e.currentTarget.form?.requestSubmit()
|
|
69
|
+
}}
|
|
70
|
+
className="max-w-[20rem] rounded border-2 border-border bg-input px-3 py-1.5 text-sm shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
71
|
+
>
|
|
72
|
+
{projects.map((project) => (
|
|
73
|
+
<option key={project.id} value={project.id}>
|
|
74
|
+
{projectOptionLabel(project, projects)}
|
|
75
|
+
</option>
|
|
76
|
+
))}
|
|
77
|
+
</select>
|
|
78
|
+
<noscript>
|
|
79
|
+
<Button type="submit" size="sm" variant="outline">
|
|
80
|
+
Switch project
|
|
81
|
+
</Button>
|
|
82
|
+
</noscript>
|
|
83
|
+
</form>
|
|
84
|
+
) : null}
|
|
88
85
|
|
|
89
86
|
{!autoconfig ? (
|
|
90
87
|
<form action={openAddProject}>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation"
|
|
4
|
+
import { useEffect, useRef } from "react"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* In development, poll the board-reload SSE endpoint and soft-refresh RSC
|
|
8
|
+
* trees when markdown on disk changes.
|
|
9
|
+
*/
|
|
10
|
+
export function BoardDevReloader() {
|
|
11
|
+
const router = useRouter()
|
|
12
|
+
const lastAt = useRef(0)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (process.env.NODE_ENV !== "development") return
|
|
16
|
+
|
|
17
|
+
let closed = false
|
|
18
|
+
let source: EventSource | null = null
|
|
19
|
+
let retryTimer: ReturnType<typeof setTimeout> | null = null
|
|
20
|
+
|
|
21
|
+
const connect = () => {
|
|
22
|
+
if (closed) return
|
|
23
|
+
source = new EventSource("/api/taskmark-reload")
|
|
24
|
+
source.addEventListener("reload", (event) => {
|
|
25
|
+
const data = String((event as MessageEvent).data || "")
|
|
26
|
+
const at = Number.parseInt(data, 10)
|
|
27
|
+
if (Number.isFinite(at) && at <= lastAt.current) return
|
|
28
|
+
lastAt.current = Number.isFinite(at) ? at : Date.now()
|
|
29
|
+
router.refresh()
|
|
30
|
+
})
|
|
31
|
+
source.onerror = () => {
|
|
32
|
+
source?.close()
|
|
33
|
+
source = null
|
|
34
|
+
if (!closed) {
|
|
35
|
+
retryTimer = setTimeout(connect, 1500)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
connect()
|
|
41
|
+
return () => {
|
|
42
|
+
closed = true
|
|
43
|
+
if (retryTimer) clearTimeout(retryTimer)
|
|
44
|
+
source?.close()
|
|
45
|
+
}
|
|
46
|
+
}, [router])
|
|
47
|
+
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
@@ -33,6 +33,7 @@ import { usePaginatedRows } from "@/hooks/use-paginated-rows"
|
|
|
33
33
|
import { usePersistedHideCompleted } from "@/hooks/use-persisted-hide-completed"
|
|
34
34
|
import { useTableSort } from "@/hooks/use-table-sort"
|
|
35
35
|
import { cn } from "@/lib/utils"
|
|
36
|
+
import { displayFileName } from "@/lib/display-path"
|
|
36
37
|
import type { EpicSummary, ProjectEpicList } from "@/lib/taskmark/epic-types"
|
|
37
38
|
import { isGeneralEpic } from "@/lib/taskmark/general-epic"
|
|
38
39
|
import {
|
|
@@ -145,9 +146,6 @@ function ProjectEpicCard({
|
|
|
145
146
|
<Card>
|
|
146
147
|
<CardHeader>
|
|
147
148
|
<CardTitle className="font-head text-xl">{project.name}</CardTitle>
|
|
148
|
-
<CardDescription className="font-mono text-xs">
|
|
149
|
-
{project.boardPath}
|
|
150
|
-
</CardDescription>
|
|
151
149
|
{hasSourceRows ? (
|
|
152
150
|
<CardAction>
|
|
153
151
|
<TimeframeFilter
|
|
@@ -173,7 +171,7 @@ function ProjectEpicCard({
|
|
|
173
171
|
<ul className="mt-2 flex flex-col gap-1 font-mono text-xs text-muted-foreground">
|
|
174
172
|
{errors.map((err) => (
|
|
175
173
|
<li key={err.filePath}>
|
|
176
|
-
{err.filePath}: {err.message}
|
|
174
|
+
{displayFileName(err.filePath)}: {err.message}
|
|
177
175
|
</li>
|
|
178
176
|
))}
|
|
179
177
|
</ul>
|