@taskmark/ui 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -7
- package/app/board/page.tsx +3 -272
- package/app/page.tsx +14 -14
- package/app/setup/actions.ts +4 -4
- package/app/setup/page.tsx +12 -4
- package/bin/lib/resolve-board.mjs +50 -1
- package/bin/taskmark.js +154 -32
- package/components/board/board-screen.tsx +285 -0
- package/components/board/epic-list.tsx +2 -2
- package/components/board/overall-work-items-list.tsx +1 -1
- package/components/board/story-list.tsx +1 -1
- 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 +3 -3
- package/dist/prod-next/prerender-manifest.json +3 -3
- package/dist/prod-next/react-loadable-manifest.json +2 -2
- 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_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 +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/prod-next/server/app/api/taskmark-reload/route.js +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/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/192.js +49 -0
- package/dist/prod-next/server/chunks/463.js +1 -0
- package/dist/prod-next/server/chunks/487.js +1 -0
- package/dist/prod-next/server/chunks/54.js +19 -0
- package/dist/prod-next/server/chunks/57.js +8 -19
- 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/static/chunks/636-626c380f021b95c8.js +1 -0
- package/dist/prod-next/static/chunks/909.932f2af3be7f1e89.js +1 -0
- package/dist/prod-next/static/chunks/app/_global-error/page-a1e3320be3da9be1.js +1 -0
- package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-a1e3320be3da9be1.js +1 -0
- package/dist/prod-next/static/chunks/app/board/page-636f60191db62f52.js +1 -0
- package/dist/prod-next/static/chunks/app/page-562a33b50da7665b.js +1 -0
- package/dist/prod-next/static/chunks/app/setup/page-4cf527b758b1d51e.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-a1e3320be3da9be1.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-a1e3320be3da9be1.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-a1e3320be3da9be1.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-a1e3320be3da9be1.js +1 -0
- package/dist/prod-next/static/chunks/webpack-d33b8a6298c94a64.js +1 -0
- package/dist/public/taskmark-snapshot.json +1 -1
- package/lib/taskmark/autoconfig.ts +54 -3
- package/lib/taskmark/discover.ts +2 -1
- package/lib/taskmark/list-view-mode.ts +1 -1
- package/lib/taskmark/workspace-mode.ts +24 -0
- package/lib/taskmark/workspace.ts +5 -2
- package/package.json +4 -2
- package/public/taskmark-snapshot.json +1 -1
- package/scripts/build-static.mjs +61 -3
- 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/616.js +0 -1
- package/dist/prod-next/server/chunks/708.js +0 -1
- package/dist/prod-next/static/chunks/51.e9139a9ea6eb36b9.js +0 -1
- package/dist/prod-next/static/chunks/app/_global-error/page-d34451c04262dd7d.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/page-d34451c04262dd7d.js +0 -1
- package/dist/prod-next/static/chunks/app/setup/page-c3003359e8becc84.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/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/{wbcgYS3Jw87KqhnA9ttOL → yyRnYAFQrJjF5_b5ZIeqh}/_buildManifest.js +0 -0
- /package/dist/prod-next/static/{wbcgYS3Jw87KqhnA9ttOL → yyRnYAFQrJjF5_b5ZIeqh}/_ssgManifest.js +0 -0
package/bin/taskmark.js
CHANGED
|
@@ -17,13 +17,18 @@ const requireFromPackage = createRequire(path.join(packageRoot, "package.json"))
|
|
|
17
17
|
|
|
18
18
|
function printHelp() {
|
|
19
19
|
console.log(`Usage:
|
|
20
|
+
taskmark [options] smart default: local board if found, else setup
|
|
21
|
+
taskmark open [options]
|
|
20
22
|
taskmark serve [options]
|
|
21
23
|
taskmark dev [options]
|
|
22
24
|
taskmark build [options]
|
|
23
25
|
taskmark preview [options]
|
|
24
26
|
|
|
25
27
|
Commands:
|
|
26
|
-
|
|
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})
|
|
27
32
|
dev Next.js development server with board markdown live reload
|
|
28
33
|
build Production static HTML export for Vercel / static hosting
|
|
29
34
|
preview Serve an existing static export (default: <board>/out)
|
|
@@ -31,24 +36,32 @@ Commands:
|
|
|
31
36
|
Options:
|
|
32
37
|
--port, -p <n> Listen port (default ${DEFAULT_PORT}; env PORT / TASKMARK_PORT)
|
|
33
38
|
--board <path> Board or product root (sets TASKMARK_BOARD)
|
|
39
|
+
--workspace, -w Force multi-project / setup mode (skip local board binding)
|
|
34
40
|
--out <dir> Static output directory for build/preview (default: <board>/out)
|
|
35
|
-
--no-open Do not open a browser (serve / preview / dev)
|
|
41
|
+
--no-open Do not open a browser (default / open / serve / preview / dev)
|
|
36
42
|
--help, -h Show help
|
|
37
43
|
|
|
38
|
-
|
|
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):
|
|
39
50
|
1. --board / TASKMARK_BOARD
|
|
40
51
|
2. TASKMARK_MASTER
|
|
41
52
|
3. TASKMARK_CWD → npm INIT_CWD → process.cwd()
|
|
42
53
|
(product root with ./taskmark/, or dedicated *-taskmark root)
|
|
54
|
+
4. Sibling <parent>-taskmark next to a product repo (multi-git)
|
|
43
55
|
|
|
44
56
|
Examples:
|
|
57
|
+
npx @taskmark/ui # global: setup / pick projects
|
|
58
|
+
cd my-app-taskmark && npx taskmark # local board in this folder
|
|
45
59
|
npm i @taskmark/ui --save && npx taskmark serve
|
|
60
|
+
npx taskmark open # force setup even inside a board folder
|
|
46
61
|
npx taskmark dev --board .
|
|
47
62
|
npx taskmark build --board .
|
|
48
63
|
npm run preview
|
|
49
|
-
npx taskmark
|
|
50
|
-
npx -p @taskmark/ui taskmark serve
|
|
51
|
-
TASKMARK_BOARD=/path/to/my-app/taskmark npx taskmark serve
|
|
64
|
+
TASKMARK_BOARD=/path/to/board npx taskmark serve
|
|
52
65
|
`)
|
|
53
66
|
}
|
|
54
67
|
|
|
@@ -59,20 +72,24 @@ function parseArgs(argv) {
|
|
|
59
72
|
board: null,
|
|
60
73
|
out: null,
|
|
61
74
|
open: true,
|
|
75
|
+
workspace: false,
|
|
62
76
|
help: false,
|
|
63
77
|
}
|
|
64
78
|
const rest = [...argv]
|
|
65
|
-
if (rest.length === 0
|
|
66
|
-
args.
|
|
79
|
+
if (rest.length === 0) {
|
|
80
|
+
args.command = "auto"
|
|
67
81
|
return args
|
|
68
82
|
}
|
|
69
|
-
|
|
83
|
+
// Bare `taskmark` with only flags → smart default (local board or setup).
|
|
84
|
+
args.command = rest[0].startsWith("-") ? "auto" : rest.shift()
|
|
70
85
|
while (rest.length) {
|
|
71
86
|
const token = rest.shift()
|
|
72
87
|
if (token === "--help" || token === "-h") {
|
|
73
88
|
args.help = true
|
|
74
89
|
} else if (token === "--no-open") {
|
|
75
90
|
args.open = false
|
|
91
|
+
} else if (token === "--workspace" || token === "-w") {
|
|
92
|
+
args.workspace = true
|
|
76
93
|
} else if (token === "--port" || token === "-p") {
|
|
77
94
|
args.port = rest.shift()
|
|
78
95
|
} else if (token === "--board") {
|
|
@@ -122,6 +139,19 @@ function resolveNextBin() {
|
|
|
122
139
|
}
|
|
123
140
|
}
|
|
124
141
|
|
|
142
|
+
/**
|
|
143
|
+
* A `taskmark build` writes an `output: export` build. It has a server/ dir but
|
|
144
|
+
* serves prerendered HTML with the static runtime baked in, so `serve` must not
|
|
145
|
+
* reuse it.
|
|
146
|
+
*/
|
|
147
|
+
function isStaticExportBuild(dir) {
|
|
148
|
+
return fs.existsSync(path.join(dir, "export-detail.json"))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function hasServerBuild(dir) {
|
|
152
|
+
return fs.existsSync(path.join(dir, "server")) && !isStaticExportBuild(dir)
|
|
153
|
+
}
|
|
154
|
+
|
|
125
155
|
/**
|
|
126
156
|
* Ensure packageRoot/.next points at a usable production build.
|
|
127
157
|
* Published packages ship dist/prod-next (npm cannot pack nested node_modules
|
|
@@ -130,8 +160,8 @@ function resolveNextBin() {
|
|
|
130
160
|
function ensureProdNext() {
|
|
131
161
|
const nextDir = path.join(packageRoot, ".next")
|
|
132
162
|
const shipped = path.join(packageRoot, "dist", "prod-next")
|
|
133
|
-
const hasLocalServer =
|
|
134
|
-
const hasShippedServer =
|
|
163
|
+
const hasLocalServer = hasServerBuild(nextDir)
|
|
164
|
+
const hasShippedServer = hasServerBuild(shipped)
|
|
135
165
|
|
|
136
166
|
if (hasLocalServer) return nextDir
|
|
137
167
|
if (!hasShippedServer) {
|
|
@@ -219,6 +249,46 @@ function waitForServer(port, timeoutMs = 60_000) {
|
|
|
219
249
|
})
|
|
220
250
|
}
|
|
221
251
|
|
|
252
|
+
function callerCwd() {
|
|
253
|
+
return process.env.INIT_CWD || process.cwd()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isWorkspaceRun(args) {
|
|
257
|
+
return args.command === "open" || args.workspace === true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Decide bound vs workspace for the default (`auto`) command.
|
|
262
|
+
* Returns a serve-compatible args object with workspace forced when unbound.
|
|
263
|
+
*/
|
|
264
|
+
function resolveAutoArgs(args) {
|
|
265
|
+
if (args.workspace) {
|
|
266
|
+
return { ...args, command: "open", workspace: true }
|
|
267
|
+
}
|
|
268
|
+
if (args.board) {
|
|
269
|
+
return { ...args, command: "serve", workspace: false }
|
|
270
|
+
}
|
|
271
|
+
const resolved = resolveServeBoard(null)
|
|
272
|
+
if (resolved) {
|
|
273
|
+
return { ...args, command: "serve", workspace: false }
|
|
274
|
+
}
|
|
275
|
+
return { ...args, command: "open", workspace: true }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Workspace mode must out-rank an inherited board bind, so blank those vars for
|
|
280
|
+
* the child (the UI reads empty as unset) instead of only adding the flag.
|
|
281
|
+
*/
|
|
282
|
+
function workspaceEnv() {
|
|
283
|
+
return {
|
|
284
|
+
TASKMARK_WORKSPACE: "1",
|
|
285
|
+
TASKMARK_WORKSPACE_ROOT: callerCwd(),
|
|
286
|
+
TASKMARK_BOARD: "",
|
|
287
|
+
TASKMARK_MASTER: "",
|
|
288
|
+
TASKMARK_CWD: "",
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
222
292
|
function resolveBoardOrExit(boardArg, command) {
|
|
223
293
|
const resolved = resolveServeBoard(boardArg)
|
|
224
294
|
if (!resolved) {
|
|
@@ -228,10 +298,13 @@ Looked for:
|
|
|
228
298
|
- --board / TASKMARK_BOARD
|
|
229
299
|
- TASKMARK_MASTER
|
|
230
300
|
- cwd layouts: nested ./taskmark/ or dedicated *-taskmark root
|
|
301
|
+
- sibling <parent>-taskmark beside a product repo
|
|
231
302
|
|
|
232
303
|
Fix: cd into a product repo (with ./taskmark/) or a board root, or set TASKMARK_BOARD.
|
|
233
304
|
Then re-run: npx taskmark ${command}
|
|
234
|
-
(or: npx -p @taskmark/ui taskmark ${command})
|
|
305
|
+
(or: npx -p @taskmark/ui taskmark ${command})
|
|
306
|
+
|
|
307
|
+
To pick a project in the browser instead: npx taskmark`)
|
|
235
308
|
process.exit(1)
|
|
236
309
|
}
|
|
237
310
|
return resolved
|
|
@@ -253,7 +326,8 @@ function attachChildLifecycle(child, onShutdown) {
|
|
|
253
326
|
}
|
|
254
327
|
|
|
255
328
|
async function serve(args) {
|
|
256
|
-
const
|
|
329
|
+
const workspace = isWorkspaceRun(args)
|
|
330
|
+
const resolved = workspace ? null : resolveBoardOrExit(args.board, "serve")
|
|
257
331
|
const port = resolvePort(args.port)
|
|
258
332
|
const url = `http://localhost:${port}`
|
|
259
333
|
const nextBin = resolveNextBin()
|
|
@@ -266,7 +340,12 @@ async function serve(args) {
|
|
|
266
340
|
}
|
|
267
341
|
|
|
268
342
|
console.log(`Taskmark UI`)
|
|
269
|
-
|
|
343
|
+
if (workspace) {
|
|
344
|
+
console.log(` mode: workspace (choose projects in the browser)`)
|
|
345
|
+
console.log(` from: ${callerCwd()}`)
|
|
346
|
+
} else {
|
|
347
|
+
console.log(` board: ${resolved.boardPath} (${resolved.source})`)
|
|
348
|
+
}
|
|
270
349
|
console.log(` listen: ${url}`)
|
|
271
350
|
|
|
272
351
|
const child = spawn(
|
|
@@ -278,7 +357,9 @@ async function serve(args) {
|
|
|
278
357
|
...process.env,
|
|
279
358
|
PORT: String(port),
|
|
280
359
|
HOSTNAME: "0.0.0.0",
|
|
281
|
-
|
|
360
|
+
...(workspace
|
|
361
|
+
? workspaceEnv()
|
|
362
|
+
: { TASKMARK_BOARD: resolved.boardPath }),
|
|
282
363
|
NODE_ENV: "production",
|
|
283
364
|
},
|
|
284
365
|
stdio: "inherit",
|
|
@@ -308,15 +389,25 @@ async function serve(args) {
|
|
|
308
389
|
}
|
|
309
390
|
|
|
310
391
|
async function dev(args) {
|
|
311
|
-
const
|
|
392
|
+
const workspace = isWorkspaceRun(args)
|
|
393
|
+
const resolved = workspace ? null : resolveBoardOrExit(args.board, "dev")
|
|
312
394
|
const port = resolvePort(args.port)
|
|
313
395
|
const url = `http://localhost:${port}`
|
|
314
396
|
const nextBin = resolveNextBin()
|
|
315
397
|
|
|
316
398
|
console.log(`Taskmark UI (dev)`)
|
|
317
|
-
|
|
399
|
+
if (workspace) {
|
|
400
|
+
console.log(` mode: workspace (choose projects in the browser)`)
|
|
401
|
+
console.log(` from: ${callerCwd()}`)
|
|
402
|
+
} else {
|
|
403
|
+
console.log(` board: ${resolved.boardPath} (${resolved.source})`)
|
|
404
|
+
}
|
|
318
405
|
console.log(` listen: ${url}`)
|
|
319
|
-
console.log(
|
|
406
|
+
console.log(
|
|
407
|
+
workspace
|
|
408
|
+
? ` watch: off (no single board bound)`
|
|
409
|
+
: ` watch: **/*.md under board`
|
|
410
|
+
)
|
|
320
411
|
|
|
321
412
|
const child = spawn(
|
|
322
413
|
process.execPath,
|
|
@@ -326,8 +417,12 @@ async function dev(args) {
|
|
|
326
417
|
env: {
|
|
327
418
|
...process.env,
|
|
328
419
|
PORT: String(port),
|
|
329
|
-
|
|
330
|
-
|
|
420
|
+
...(workspace
|
|
421
|
+
? workspaceEnv()
|
|
422
|
+
: {
|
|
423
|
+
TASKMARK_BOARD: resolved.boardPath,
|
|
424
|
+
TASKMARK_CWD: resolved.boardPath,
|
|
425
|
+
}),
|
|
331
426
|
NODE_ENV: "development",
|
|
332
427
|
},
|
|
333
428
|
stdio: "inherit",
|
|
@@ -335,17 +430,21 @@ async function dev(args) {
|
|
|
335
430
|
)
|
|
336
431
|
|
|
337
432
|
let debounce = null
|
|
338
|
-
const stopWatch =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
433
|
+
const stopWatch = workspace
|
|
434
|
+
? () => {}
|
|
435
|
+
: watchBoardMarkdown(resolved.boardPath, () => {
|
|
436
|
+
if (debounce) clearTimeout(debounce)
|
|
437
|
+
debounce = setTimeout(() => {
|
|
438
|
+
try {
|
|
439
|
+
touchDevReloadToken()
|
|
440
|
+
console.log(`[taskmark] board markdown changed — refreshing`)
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.warn(
|
|
443
|
+
`[taskmark] reload touch failed: ${err?.message || err}`
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
}, 200)
|
|
447
|
+
})
|
|
349
448
|
|
|
350
449
|
const shutdown = attachChildLifecycle(child, () => {
|
|
351
450
|
stopWatch()
|
|
@@ -427,7 +526,30 @@ async function main() {
|
|
|
427
526
|
printHelp()
|
|
428
527
|
process.exit(args.help || !args.command ? 0 : 1)
|
|
429
528
|
}
|
|
430
|
-
|
|
529
|
+
const workspaceCommands = new Set(["auto", "open", "serve", "dev"])
|
|
530
|
+
if (args.workspace && !workspaceCommands.has(args.command)) {
|
|
531
|
+
console.error(`--workspace applies to the default command, open, serve, and dev.
|
|
532
|
+
|
|
533
|
+
Use: npx taskmark (or: npx taskmark open)`)
|
|
534
|
+
process.exit(1)
|
|
535
|
+
}
|
|
536
|
+
if ((isWorkspaceRun(args) || (args.command === "auto" && args.workspace)) && args.board) {
|
|
537
|
+
console.error(`--board and workspace mode are mutually exclusive.
|
|
538
|
+
|
|
539
|
+
Bind one board: npx taskmark serve --board ${args.board}
|
|
540
|
+
Pick projects in UI: npx taskmark open`)
|
|
541
|
+
process.exit(1)
|
|
542
|
+
}
|
|
543
|
+
if (args.command === "auto") {
|
|
544
|
+
try {
|
|
545
|
+
await serve(resolveAutoArgs(args))
|
|
546
|
+
} catch (err) {
|
|
547
|
+
console.error(String(err?.message || err))
|
|
548
|
+
process.exit(1)
|
|
549
|
+
}
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
if (args.command === "open" || args.command === "serve") {
|
|
431
553
|
try {
|
|
432
554
|
await serve(args)
|
|
433
555
|
} catch (err) {
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Suspense } from "react"
|
|
2
|
+
import Link from "next/link"
|
|
3
|
+
import { redirect } from "next/navigation"
|
|
4
|
+
|
|
5
|
+
import { AppBar } from "@/components/board/app-bar"
|
|
6
|
+
import { EpicList } from "@/components/board/epic-list"
|
|
7
|
+
import { ListViewSwitcher } from "@/components/board/list-view-switcher"
|
|
8
|
+
import { OverallWorkItemsList } from "@/components/board/overall-work-items-list"
|
|
9
|
+
import { ProjectStatusMetricsStrip } from "@/components/board/project-status-metrics-strip"
|
|
10
|
+
import { StaticBoardApp } from "@/components/board/static-board-app"
|
|
11
|
+
import { TaskList } from "@/components/board/task-list"
|
|
12
|
+
import { WorkItemsList } from "@/components/board/work-items-list"
|
|
13
|
+
import { WorkItemSheetProvider } from "@/components/board/work-item-sheet"
|
|
14
|
+
import { resolveActiveProject } from "@/lib/taskmark/active-project"
|
|
15
|
+
import {
|
|
16
|
+
getActiveProjectCookie,
|
|
17
|
+
getHideCompletedCookies,
|
|
18
|
+
getMasterFoldersCookie,
|
|
19
|
+
} from "@/lib/taskmark/cookies"
|
|
20
|
+
import {
|
|
21
|
+
LIST_VIEW_LABELS,
|
|
22
|
+
boardHref,
|
|
23
|
+
parseListViewMode,
|
|
24
|
+
type ListViewMode,
|
|
25
|
+
} from "@/lib/taskmark/list-view-mode"
|
|
26
|
+
import { DEV_RELOAD_TOKEN } from "@/lib/taskmark/dev-reload-token"
|
|
27
|
+
import { loadSnapshotFromPublic } from "@/lib/taskmark/load-snapshot-server"
|
|
28
|
+
import { parseEpicsForProject } from "@/lib/taskmark/parse-epics"
|
|
29
|
+
import {
|
|
30
|
+
parseWorkItemsForEpic,
|
|
31
|
+
parseWorkItemsViewForProject,
|
|
32
|
+
} from "@/lib/taskmark/parse-flat-lists"
|
|
33
|
+
import { parseItemsForStory } from "@/lib/taskmark/parse-items"
|
|
34
|
+
import {
|
|
35
|
+
collectCompletedLeafPointSamples,
|
|
36
|
+
collectMetricLeaves,
|
|
37
|
+
computeProjectStatusMetrics,
|
|
38
|
+
} from "@/lib/taskmark/project-metrics"
|
|
39
|
+
import { isStaticRuntime } from "@/lib/taskmark/static-mode"
|
|
40
|
+
import { loadWorkspace } from "@/lib/taskmark/workspace"
|
|
41
|
+
|
|
42
|
+
// Keep this import so `taskmark dev` can touch the token module and refresh RSC.
|
|
43
|
+
void DEV_RELOAD_TOKEN
|
|
44
|
+
|
|
45
|
+
type BoardScreenProps = {
|
|
46
|
+
searchParams: Promise<{
|
|
47
|
+
view?: string | string[]
|
|
48
|
+
epic?: string | string[]
|
|
49
|
+
story?: string | string[]
|
|
50
|
+
item?: string | string[]
|
|
51
|
+
}>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function paramValue(value: string | string[] | undefined): string | null {
|
|
56
|
+
if (typeof value === "string" && value.trim()) return value.trim()
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function headingForView(view: ListViewMode): string {
|
|
61
|
+
if (view === "overall") return "Epics"
|
|
62
|
+
return LIST_VIEW_LABELS[view]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function BoardScreen({ searchParams }: BoardScreenProps) {
|
|
66
|
+
if (isStaticRuntime()) {
|
|
67
|
+
const snapshot = loadSnapshotFromPublic()
|
|
68
|
+
return (
|
|
69
|
+
<Suspense fallback={<div className="p-8 text-sm text-muted-foreground">Loading board…</div>}>
|
|
70
|
+
<StaticBoardApp snapshot={snapshot} />
|
|
71
|
+
</Suspense>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const masters = await getMasterFoldersCookie()
|
|
76
|
+
const workspace = loadWorkspace(masters)
|
|
77
|
+
if (workspace.projects.length === 0) {
|
|
78
|
+
redirect("/setup")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const savedActiveId = await getActiveProjectCookie()
|
|
82
|
+
const hideCompletedPrefs = await getHideCompletedCookies()
|
|
83
|
+
const activeProject = resolveActiveProject(
|
|
84
|
+
workspace.projects,
|
|
85
|
+
// Autoconfig: prefer the bound board; ignore stale active-project cookies.
|
|
86
|
+
workspace.autoconfig ? null : savedActiveId
|
|
87
|
+
)
|
|
88
|
+
if (!activeProject) {
|
|
89
|
+
redirect("/setup")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const params = await searchParams
|
|
93
|
+
const activeView = parseListViewMode(params.view)
|
|
94
|
+
const selectedEpicId =
|
|
95
|
+
activeView === "overall" ? paramValue(params.epic) : null
|
|
96
|
+
const selectedStoryId =
|
|
97
|
+
activeView === "overall" && selectedEpicId
|
|
98
|
+
? paramValue(params.story)
|
|
99
|
+
: null
|
|
100
|
+
const selectedItemId = paramValue(params.item)
|
|
101
|
+
|
|
102
|
+
const list = parseEpicsForProject(activeProject)
|
|
103
|
+
const epicCount = list.epics.length
|
|
104
|
+
const errorCount = list.errors.length
|
|
105
|
+
|
|
106
|
+
const selectedEpic = selectedEpicId
|
|
107
|
+
? list.epics.find((e) => e.id === selectedEpicId) ?? null
|
|
108
|
+
: null
|
|
109
|
+
|
|
110
|
+
const epicWorkItems =
|
|
111
|
+
activeView === "overall" && selectedEpicId != null
|
|
112
|
+
? parseWorkItemsForEpic(
|
|
113
|
+
activeProject,
|
|
114
|
+
selectedEpicId,
|
|
115
|
+
selectedEpic?.title ?? null
|
|
116
|
+
)
|
|
117
|
+
: null
|
|
118
|
+
|
|
119
|
+
const selectedStory =
|
|
120
|
+
epicWorkItems && selectedStoryId
|
|
121
|
+
? (epicWorkItems.rows.find(
|
|
122
|
+
(row) => row.kind === "story" && row.id === selectedStoryId
|
|
123
|
+
) ?? null)
|
|
124
|
+
: null
|
|
125
|
+
|
|
126
|
+
const itemList =
|
|
127
|
+
activeView === "overall" &&
|
|
128
|
+
selectedEpicId != null &&
|
|
129
|
+
selectedStory != null
|
|
130
|
+
? parseItemsForStory(activeProject, selectedEpicId, selectedStory.id)
|
|
131
|
+
: null
|
|
132
|
+
|
|
133
|
+
const workItemsList =
|
|
134
|
+
activeView === "overall" || activeView === "workitems"
|
|
135
|
+
? parseWorkItemsViewForProject(activeProject)
|
|
136
|
+
: null
|
|
137
|
+
|
|
138
|
+
/** Match Current Speed exactly: completed task/bug leaf points only. */
|
|
139
|
+
const countableCompletions = collectCompletedLeafPointSamples(
|
|
140
|
+
collectMetricLeaves(activeProject)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const statusMetrics = computeProjectStatusMetrics(activeProject)
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<WorkItemSheetProvider>
|
|
147
|
+
<div className="min-h-svh bg-[linear-gradient(180deg,_#fff_0%,_#f7f4ff_100%)]">
|
|
148
|
+
<AppBar
|
|
149
|
+
projects={workspace.projects}
|
|
150
|
+
activeProjectId={activeProject.id}
|
|
151
|
+
autoconfig={workspace.autoconfig}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6 px-4 py-8">
|
|
155
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
156
|
+
<div>
|
|
157
|
+
<h1 className="font-head text-3xl tracking-tight">
|
|
158
|
+
{headingForView(activeView)}
|
|
159
|
+
</h1>
|
|
160
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
161
|
+
{activeProject.name}
|
|
162
|
+
{activeView === "overall"
|
|
163
|
+
? ` · ${epicCount} epic${epicCount === 1 ? "" : "s"}`
|
|
164
|
+
: ""}
|
|
165
|
+
{activeView === "overall" && errorCount > 0
|
|
166
|
+
? ` · ${errorCount} parse issue${errorCount === 1 ? "" : "s"}`
|
|
167
|
+
: ""}
|
|
168
|
+
{activeView === "workitems" && workItemsList
|
|
169
|
+
? ` · ${workItemsList.rows.length} item${workItemsList.rows.length === 1 ? "" : "s"}`
|
|
170
|
+
: ""}
|
|
171
|
+
{activeView === "overall" && selectedEpic
|
|
172
|
+
? ` · selected ${selectedEpic.id}`
|
|
173
|
+
: activeView === "overall" && selectedEpicId
|
|
174
|
+
? ` · 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
|
+
</p>
|
|
184
|
+
</div>
|
|
185
|
+
<ListViewSwitcher
|
|
186
|
+
activeView={activeView}
|
|
187
|
+
selectedEpicId={selectedEpicId}
|
|
188
|
+
selectedStoryId={selectedStory?.id ?? null}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<ProjectStatusMetricsStrip metrics={statusMetrics} />
|
|
193
|
+
|
|
194
|
+
{activeView === "overall" ? (
|
|
195
|
+
<>
|
|
196
|
+
<EpicList
|
|
197
|
+
lists={[list]}
|
|
198
|
+
selectedEpicId={selectedEpicId}
|
|
199
|
+
countableCompletions={countableCompletions}
|
|
200
|
+
initialHideCompleted={hideCompletedPrefs.epics}
|
|
201
|
+
/>
|
|
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
|
+
</>
|
|
272
|
+
) : null}
|
|
273
|
+
|
|
274
|
+
{activeView === "workitems" && workItemsList ? (
|
|
275
|
+
<WorkItemsList
|
|
276
|
+
list={workItemsList}
|
|
277
|
+
countableCompletions={countableCompletions}
|
|
278
|
+
initialHideCompleted={hideCompletedPrefs.workItems}
|
|
279
|
+
/>
|
|
280
|
+
) : null}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</WorkItemSheetProvider>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
@@ -263,7 +263,7 @@ function ProjectEpicCard({
|
|
|
263
263
|
<TableCell className="font-mono text-xs">
|
|
264
264
|
<IdCreatedTooltip id={epic.id} created={epic.created}>
|
|
265
265
|
<Link
|
|
266
|
-
href={
|
|
266
|
+
href={`/?epic=${encodeURIComponent(epic.id)}`}
|
|
267
267
|
className="underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
268
268
|
aria-current={selected ? "true" : undefined}
|
|
269
269
|
>
|
|
@@ -273,7 +273,7 @@ function ProjectEpicCard({
|
|
|
273
273
|
</TableCell>
|
|
274
274
|
<TableCell className="max-w-[18rem] whitespace-normal font-medium">
|
|
275
275
|
<Link
|
|
276
|
-
href={
|
|
276
|
+
href={`/?epic=${encodeURIComponent(epic.id)}`}
|
|
277
277
|
className="underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
278
278
|
>
|
|
279
279
|
{epic.title}
|
|
@@ -243,7 +243,7 @@ export function OverallWorkItemsList({
|
|
|
243
243
|
const isStory = row.kind === "story"
|
|
244
244
|
const selected = isStory && selectedStoryId === row.id
|
|
245
245
|
const href = isStory
|
|
246
|
-
?
|
|
246
|
+
? `/?epic=${encodeURIComponent(epicId)}&story=${encodeURIComponent(row.id)}`
|
|
247
247
|
: null
|
|
248
248
|
const sheetKind = isStory ? "story" : "item"
|
|
249
249
|
return (
|
|
@@ -244,7 +244,7 @@ export function StoryList({
|
|
|
244
244
|
<TableBody>
|
|
245
245
|
{pageRows.map((story) => {
|
|
246
246
|
const selected = selectedStoryId === story.id
|
|
247
|
-
const href =
|
|
247
|
+
const href = `/?epic=${encodeURIComponent(epicId)}&story=${encodeURIComponent(story.id)}`
|
|
248
248
|
return (
|
|
249
249
|
<TableRow
|
|
250
250
|
key={`${project.id}:${story.id}:${story.filePath}`}
|
package/dist/prod-next/BUILD_ID
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
yyRnYAFQrJjF5_b5ZIeqh
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
],
|
|
5
5
|
"devFiles": [],
|
|
6
6
|
"lowPriorityFiles": [
|
|
7
|
-
"static/
|
|
8
|
-
"static/
|
|
7
|
+
"static/yyRnYAFQrJjF5_b5ZIeqh/_buildManifest.js",
|
|
8
|
+
"static/yyRnYAFQrJjF5_b5ZIeqh/_ssgManifest.js"
|
|
9
9
|
],
|
|
10
10
|
"rootMainFiles": [
|
|
11
|
-
"static/chunks/webpack-
|
|
11
|
+
"static/chunks/webpack-d33b8a6298c94a64.js",
|
|
12
12
|
"static/chunks/4bd1b696-deb4a0a1da1923b0.js",
|
|
13
13
|
"static/chunks/794-25668765560cb197.js",
|
|
14
14
|
"static/chunks/main-app-bfb36455d24b3e5a.js"
|