@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/README.md
CHANGED
|
@@ -2,33 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
Install in a Taskmark board folder (or product root with `./taskmark/`) and open the board in the browser.
|
|
4
4
|
|
|
5
|
+
## Two modes
|
|
6
|
+
|
|
7
|
+
| Mode | When | Behaviour |
|
|
8
|
+
|------|------|-----------|
|
|
9
|
+
| **Bound** | Board found in/near cwd, or `serve` / `dev` | Opens that one board. No setup wizard, no project picker. |
|
|
10
|
+
| **Workspace** | No local board, or `open` / `--workspace` | Standalone setup wizard + app-bar project picker. |
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Global / any folder without a board → setup & pick projects
|
|
14
|
+
npx @taskmark/ui
|
|
15
|
+
# or: npx -p @taskmark/ui taskmark
|
|
16
|
+
|
|
17
|
+
# Inside a board folder (or product with ./taskmark/) → that board only
|
|
18
|
+
cd my-app-taskmark && npx taskmark
|
|
19
|
+
# or: npm i @taskmark/ui --save && npx taskmark serve
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`taskmark open` always forces the setup / picker, even when a board is nearby. Chosen master folders and the active project persist in cookies.
|
|
23
|
+
|
|
5
24
|
## Install and serve
|
|
6
25
|
|
|
7
26
|
```bash
|
|
8
27
|
# from nested ./taskmark/ or a dedicated *-taskmark board root
|
|
9
28
|
npm install @taskmark/ui --save
|
|
10
|
-
npx taskmark
|
|
29
|
+
npx taskmark
|
|
30
|
+
# or: npx taskmark serve
|
|
11
31
|
```
|
|
12
32
|
|
|
13
33
|
Opens [http://localhost:8275](http://localhost:8275) bound to **this** board.
|
|
14
34
|
|
|
35
|
+
### Development (live board reload)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx taskmark dev
|
|
39
|
+
# or: npm run dev / yarn dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Runs Next.js in development on port **8275** and refreshes the UI when board `.md` files change.
|
|
43
|
+
|
|
15
44
|
Without a local install (once published):
|
|
16
45
|
|
|
17
46
|
```bash
|
|
18
|
-
npx
|
|
47
|
+
npx @taskmark/ui
|
|
48
|
+
# or: npx -p @taskmark/ui taskmark
|
|
19
49
|
```
|
|
20
50
|
|
|
21
51
|
| Option | Meaning |
|
|
22
52
|
|--------|---------|
|
|
23
53
|
| `--port` / `-p` | Listen port (default **8275**; also `PORT` / `TASKMARK_PORT`) |
|
|
24
54
|
| `--board <path>` | Board or product root (sets `TASKMARK_BOARD`) |
|
|
55
|
+
| `--workspace` / `-w` | Force setup / multi-project mode; cannot be combined with `--board` |
|
|
25
56
|
| `--no-open` | Print the URL only |
|
|
26
57
|
|
|
27
|
-
Board resolution:
|
|
58
|
+
Board resolution (bound mode only):
|
|
28
59
|
|
|
29
60
|
1. `--board` / `TASKMARK_BOARD`
|
|
30
61
|
2. `TASKMARK_MASTER`
|
|
31
62
|
3. `TASKMARK_CWD` → npm `INIT_CWD` → `process.cwd()` — nested `./taskmark/` or flat `*-taskmark` root
|
|
63
|
+
4. Sibling `<parent>-taskmark` next to a product repo (multi-git workspaces)
|
|
32
64
|
|
|
33
65
|
### Nested vs flat boards
|
|
34
66
|
|
|
@@ -49,16 +81,17 @@ You do **not** need to clone `taskmark-frontend`.
|
|
|
49
81
|
"scripts": {
|
|
50
82
|
"start": "taskmark serve --no-open",
|
|
51
83
|
"serve": "taskmark serve",
|
|
84
|
+
"dev": "taskmark dev",
|
|
52
85
|
"build": "taskmark build --board . --out out",
|
|
53
86
|
"preview": "taskmark preview"
|
|
54
87
|
},
|
|
55
88
|
"dependencies": {
|
|
56
|
-
"@taskmark/ui": "^0.2.
|
|
89
|
+
"@taskmark/ui": "^0.2.5"
|
|
57
90
|
}
|
|
58
91
|
}
|
|
59
92
|
```
|
|
60
93
|
|
|
61
|
-
Then `npm install && npm run serve` (or `npm start`). For **Vercel**, use `npm run build` (static HTML in `out/`) — see the board-ui-stub `vercel.json`.
|
|
94
|
+
Then `npm install && npm run serve` (or `npm start`). Use `npm run dev` while editing board markdown. For **Vercel**, use `npm run build` (static HTML in `out/`) — see the board-ui-stub `vercel.json`.
|
|
62
95
|
|
|
63
96
|
### Static production build
|
|
64
97
|
|
|
@@ -77,15 +110,18 @@ Favicon, webmanifest, and Open Graph assets ship in `public/` and are included i
|
|
|
77
110
|
|
|
78
111
|
```bash
|
|
79
112
|
npm install
|
|
80
|
-
npm run build
|
|
81
|
-
npm run serve
|
|
82
|
-
npm run dev
|
|
113
|
+
npm run build # next standalone + dist/ for the CLI
|
|
114
|
+
npm run serve # production standalone against cwd/env board
|
|
115
|
+
npm run dev # taskmark dev (Next + markdown live reload) on :8275
|
|
116
|
+
npm run dev:workspace # same, but unbound — work on the setup wizard / picker
|
|
117
|
+
npm run open # prebuilt UI in workspace mode
|
|
83
118
|
```
|
|
84
119
|
|
|
85
|
-
|
|
120
|
+
`npm run dev` / `serve` always bind to a board (sibling `taskmark-taskmark` from this repo). Bare `node ./bin/taskmark.js` uses the smart default. Use `open` / `dev:workspace` to work on the setup wizard.
|
|
86
121
|
|
|
87
122
|
## Stack
|
|
88
123
|
|
|
89
|
-
- Next.js
|
|
124
|
+
- Next.js production server packaged for `taskmark serve` (`next start` + slim `dist/prod-next`)
|
|
125
|
+
- `taskmark dev` for live board markdown reload
|
|
90
126
|
- Tailwind CSS v4
|
|
91
127
|
- Local markdown boards only (no hosted sync)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
import { cwdCandidates } from "@/lib/taskmark/autoconfig"
|
|
5
|
+
import { resolveBoardAtPath } from "@/lib/taskmark/discover"
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic"
|
|
8
|
+
export const runtime = "nodejs"
|
|
9
|
+
|
|
10
|
+
function resolveBoardRoot(): string | null {
|
|
11
|
+
const env = process.env.TASKMARK_BOARD?.trim()
|
|
12
|
+
if (env) {
|
|
13
|
+
const board = resolveBoardAtPath(path.resolve(env))
|
|
14
|
+
if (board) return board
|
|
15
|
+
}
|
|
16
|
+
for (const candidate of cwdCandidates()) {
|
|
17
|
+
const board = resolveBoardAtPath(candidate)
|
|
18
|
+
if (board) return board
|
|
19
|
+
}
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function walkMarkdownFiles(root: string, out: string[] = []): string[] {
|
|
24
|
+
let entries: fs.Dirent[]
|
|
25
|
+
try {
|
|
26
|
+
entries = fs.readdirSync(root, { withFileTypes: true })
|
|
27
|
+
} catch {
|
|
28
|
+
return out
|
|
29
|
+
}
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "out") {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
const full = path.join(root, entry.name)
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
walkMarkdownFiles(full, out)
|
|
37
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
38
|
+
out.push(full)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function latestMarkdownMtime(boardRoot: string): number {
|
|
45
|
+
let latest = 0
|
|
46
|
+
for (const file of walkMarkdownFiles(boardRoot)) {
|
|
47
|
+
try {
|
|
48
|
+
const mtime = fs.statSync(file).mtimeMs
|
|
49
|
+
if (mtime > latest) latest = mtime
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore transient deletes
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return latest
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* SSE stream that emits `reload` when any board `.md` mtime changes.
|
|
59
|
+
* Used only by `taskmark dev` / Next development mode.
|
|
60
|
+
*/
|
|
61
|
+
export async function GET() {
|
|
62
|
+
if (process.env.NODE_ENV !== "development") {
|
|
63
|
+
return new Response("Not available", { status: 404 })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const boardRoot = resolveBoardRoot()
|
|
67
|
+
if (!boardRoot) {
|
|
68
|
+
return new Response("No board", { status: 404 })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const encoder = new TextEncoder()
|
|
72
|
+
let last = latestMarkdownMtime(boardRoot)
|
|
73
|
+
let interval: ReturnType<typeof setInterval> | null = null
|
|
74
|
+
|
|
75
|
+
const stream = new ReadableStream({
|
|
76
|
+
start(controller) {
|
|
77
|
+
const send = (event: string, data: string) => {
|
|
78
|
+
controller.enqueue(encoder.encode(`event: ${event}\ndata: ${data}\n\n`))
|
|
79
|
+
}
|
|
80
|
+
send("hello", String(Date.now()))
|
|
81
|
+
interval = setInterval(() => {
|
|
82
|
+
const next = latestMarkdownMtime(boardRoot)
|
|
83
|
+
if (next > last) {
|
|
84
|
+
last = next
|
|
85
|
+
send("reload", String(Date.now()))
|
|
86
|
+
} else {
|
|
87
|
+
send("ping", String(Date.now()))
|
|
88
|
+
}
|
|
89
|
+
}, 750)
|
|
90
|
+
},
|
|
91
|
+
cancel() {
|
|
92
|
+
if (interval) clearInterval(interval)
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return new Response(stream, {
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
99
|
+
"Cache-Control": "no-cache, no-transform",
|
|
100
|
+
Connection: "keep-alive",
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
}
|
package/app/board/page.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
parseListViewMode,
|
|
24
24
|
type ListViewMode,
|
|
25
25
|
} from "@/lib/taskmark/list-view-mode"
|
|
26
|
+
import { DEV_RELOAD_TOKEN } from "@/lib/taskmark/dev-reload-token"
|
|
26
27
|
import { loadSnapshotFromPublic } from "@/lib/taskmark/load-snapshot-server"
|
|
27
28
|
import { parseEpicsForProject } from "@/lib/taskmark/parse-epics"
|
|
28
29
|
import {
|
|
@@ -38,6 +39,11 @@ import {
|
|
|
38
39
|
import { isStaticRuntime } from "@/lib/taskmark/static-mode"
|
|
39
40
|
import { loadWorkspace } from "@/lib/taskmark/workspace"
|
|
40
41
|
|
|
42
|
+
export const dynamic = "force-dynamic"
|
|
43
|
+
|
|
44
|
+
// Keep this import so `taskmark dev` can touch the token module and refresh RSC.
|
|
45
|
+
void DEV_RELOAD_TOKEN
|
|
46
|
+
|
|
41
47
|
type BoardPageProps = {
|
|
42
48
|
searchParams: Promise<{
|
|
43
49
|
view?: string | string[]
|
|
@@ -143,7 +149,6 @@ export default async function BoardPage({ searchParams }: BoardPageProps) {
|
|
|
143
149
|
<AppBar
|
|
144
150
|
projects={workspace.projects}
|
|
145
151
|
activeProjectId={activeProject.id}
|
|
146
|
-
masterCount={workspace.masters.length}
|
|
147
152
|
autoconfig={workspace.autoconfig}
|
|
148
153
|
/>
|
|
149
154
|
|
|
@@ -177,9 +182,6 @@ export default async function BoardPage({ searchParams }: BoardPageProps) {
|
|
|
177
182
|
? ` · story ${selectedStoryId} not in list`
|
|
178
183
|
: ""}
|
|
179
184
|
</p>
|
|
180
|
-
<p className="mt-1 font-mono text-xs text-muted-foreground">
|
|
181
|
-
{activeProject.boardPath}
|
|
182
|
-
</p>
|
|
183
185
|
</div>
|
|
184
186
|
<ListViewSwitcher
|
|
185
187
|
activeView={activeView}
|
package/app/layout.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Metadata, Viewport } from "next"
|
|
2
2
|
import { Archivo_Black, Space_Grotesk } from "next/font/google"
|
|
3
|
+
import { BoardDevReloader } from "@/components/board/board-dev-reloader"
|
|
3
4
|
import { Toaster } from "@/components/ui/sonner"
|
|
4
5
|
import { SITE } from "@/lib/site"
|
|
5
6
|
import "./globals.css"
|
|
@@ -80,6 +81,7 @@ export default function RootLayout({
|
|
|
80
81
|
className={`${archivoBlack.variable} ${spaceGrotesk.variable} font-sans antialiased`}
|
|
81
82
|
>
|
|
82
83
|
<Toaster />
|
|
84
|
+
<BoardDevReloader />
|
|
83
85
|
{children}
|
|
84
86
|
</body>
|
|
85
87
|
</html>
|
package/app/setup/page.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { SetupWizard } from "@/components/setup/setup-wizard"
|
|
|
6
6
|
import { resolveAutoconfigWorkspace } from "@/lib/taskmark/autoconfig"
|
|
7
7
|
import { getMasterFoldersCookie } from "@/lib/taskmark/cookies"
|
|
8
8
|
import { isStaticRuntime } from "@/lib/taskmark/static-mode"
|
|
9
|
+
import { workspaceStartPath } from "@/lib/taskmark/workspace-mode"
|
|
9
10
|
|
|
10
11
|
type SetupPageProps = {
|
|
11
12
|
searchParams?: Promise<{ mode?: string }>
|
|
@@ -26,6 +27,8 @@ export default async function SetupPage({ searchParams }: SetupPageProps) {
|
|
|
26
27
|
const masters = await getMasterFoldersCookie()
|
|
27
28
|
const hasProjects = masters.length > 0
|
|
28
29
|
const adding = params.mode === "add" && hasProjects
|
|
30
|
+
// First run: offer the directory `taskmark` was launched from as the master.
|
|
31
|
+
const suggestedPath = adding || hasProjects ? "" : workspaceStartPath()
|
|
29
32
|
|
|
30
33
|
return (
|
|
31
34
|
<div className="relative flex min-h-svh flex-col items-center justify-center px-4 py-16">
|
|
@@ -50,7 +53,7 @@ export default async function SetupPage({ searchParams }: SetupPageProps) {
|
|
|
50
53
|
</p>
|
|
51
54
|
</div>
|
|
52
55
|
<SetupWizard
|
|
53
|
-
initialPath=
|
|
56
|
+
initialPath={suggestedPath}
|
|
54
57
|
adding={adding}
|
|
55
58
|
hasExistingProjects={hasProjects}
|
|
56
59
|
/>
|
|
@@ -48,6 +48,50 @@ export function resolveBoardAtPath(candidatePath) {
|
|
|
48
48
|
return null
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Multi-git layout: product repo beside a dedicated `<common>-taskmark` board.
|
|
53
|
+
* Prefer `<parentDirBasename>-taskmark`, else a unique `*-taskmark` sibling,
|
|
54
|
+
* else a sibling whose board prefix matches the product repo name.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveSiblingDedicatedBoard(fromDir) {
|
|
57
|
+
const resolved = path.resolve(fromDir)
|
|
58
|
+
const parent = path.dirname(resolved)
|
|
59
|
+
const parentName = path.basename(parent)
|
|
60
|
+
const preferred = path.join(parent, `${parentName}-taskmark`)
|
|
61
|
+
if (
|
|
62
|
+
isDedicatedBoardRepoName(path.basename(preferred)) &&
|
|
63
|
+
hasBoardMarkers(preferred)
|
|
64
|
+
) {
|
|
65
|
+
return preferred
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let entries
|
|
69
|
+
try {
|
|
70
|
+
entries = fs.readdirSync(parent, { withFileTypes: true })
|
|
71
|
+
} catch {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const boards = []
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (!entry.isDirectory() || !isDedicatedBoardRepoName(entry.name)) continue
|
|
78
|
+
const full = path.join(parent, entry.name)
|
|
79
|
+
if (hasBoardMarkers(full)) boards.push(full)
|
|
80
|
+
}
|
|
81
|
+
if (boards.length === 0) return null
|
|
82
|
+
if (boards.length === 1) return boards[0]
|
|
83
|
+
|
|
84
|
+
const cwdBase = path.basename(resolved)
|
|
85
|
+
const parts = cwdBase.split("-").filter(Boolean)
|
|
86
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
87
|
+
const prefix = parts.slice(0, i).join("-")
|
|
88
|
+
const hit = boards.find((b) => path.basename(b) === `${prefix}-taskmark`)
|
|
89
|
+
if (hit) return hit
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
51
95
|
function cwdCandidates() {
|
|
52
96
|
const out = []
|
|
53
97
|
const seen = new Set()
|
|
@@ -74,7 +118,7 @@ function cwdCandidates() {
|
|
|
74
118
|
|
|
75
119
|
/**
|
|
76
120
|
* Same precedence as the Next app autoconfig:
|
|
77
|
-
* TASKMARK_BOARD → TASKMARK_MASTER (as board-or-master) → cwd.
|
|
121
|
+
* TASKMARK_BOARD → TASKMARK_MASTER (as board-or-master) → cwd → sibling *-taskmark.
|
|
78
122
|
* @returns {{ boardPath: string, source: string } | null}
|
|
79
123
|
*/
|
|
80
124
|
export function resolveServeBoard(explicitBoard) {
|
|
@@ -104,5 +148,10 @@ export function resolveServeBoard(explicitBoard) {
|
|
|
104
148
|
if (board) return { boardPath: board, source: "cwd" }
|
|
105
149
|
}
|
|
106
150
|
|
|
151
|
+
for (const candidate of cwdCandidates()) {
|
|
152
|
+
const sibling = resolveSiblingDedicatedBoard(candidate)
|
|
153
|
+
if (sibling) return { boardPath: sibling, source: "sibling" }
|
|
154
|
+
}
|
|
155
|
+
|
|
107
156
|
return null
|
|
108
157
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
const IGNORE_DIRS = new Set([
|
|
5
|
+
"node_modules",
|
|
6
|
+
".git",
|
|
7
|
+
"out",
|
|
8
|
+
".next",
|
|
9
|
+
"dist",
|
|
10
|
+
".taskmark-ui-build",
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
function shouldIgnoreDir(name) {
|
|
14
|
+
return IGNORE_DIRS.has(name) || name.startsWith(".")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function latestMarkdownMtime(boardRoot) {
|
|
18
|
+
let latest = 0
|
|
19
|
+
const walk = (dir) => {
|
|
20
|
+
let entries
|
|
21
|
+
try {
|
|
22
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
23
|
+
} catch {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const full = path.join(dir, entry.name)
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
if (!shouldIgnoreDir(entry.name)) walk(full)
|
|
30
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
31
|
+
try {
|
|
32
|
+
latest = Math.max(latest, fs.statSync(full).mtimeMs)
|
|
33
|
+
} catch {
|
|
34
|
+
// ignore races
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
walk(boardRoot)
|
|
40
|
+
return latest
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Poll a board directory for `.md` mtime changes.
|
|
45
|
+
* Uses polling (not recursive fs.watch) to avoid EMFILE on large boards.
|
|
46
|
+
* Returns a stop function.
|
|
47
|
+
*/
|
|
48
|
+
export function watchBoardMarkdown(boardRoot, onChange) {
|
|
49
|
+
let stopped = false
|
|
50
|
+
let lastStamp = latestMarkdownMtime(boardRoot)
|
|
51
|
+
|
|
52
|
+
const interval = setInterval(() => {
|
|
53
|
+
if (stopped) return
|
|
54
|
+
const next = latestMarkdownMtime(boardRoot)
|
|
55
|
+
if (next > lastStamp) {
|
|
56
|
+
lastStamp = next
|
|
57
|
+
onChange()
|
|
58
|
+
}
|
|
59
|
+
}, 750)
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
stopped = true
|
|
63
|
+
clearInterval(interval)
|
|
64
|
+
}
|
|
65
|
+
}
|