@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.
Files changed (215) hide show
  1. package/README.md +46 -10
  2. package/app/api/taskmark-reload/route.ts +103 -0
  3. package/app/board/page.tsx +6 -4
  4. package/app/layout.tsx +2 -0
  5. package/app/setup/page.tsx +4 -1
  6. package/bin/lib/resolve-board.mjs +50 -1
  7. package/bin/lib/watch-board-markdown.mjs +65 -0
  8. package/bin/taskmark.js +310 -67
  9. package/components/board/app-bar.tsx +31 -34
  10. package/components/board/board-dev-reloader.tsx +49 -0
  11. package/components/board/epic-list.tsx +2 -4
  12. package/components/board/overall-work-items-list.tsx +2 -1
  13. package/components/board/static-board-app.tsx +0 -1
  14. package/components/board/story-list.tsx +2 -1
  15. package/components/board/task-list.tsx +2 -1
  16. package/components/board/work-item-sheet.tsx +15 -2
  17. package/components/board/work-items-list.tsx +2 -2
  18. package/components/setup/setup-wizard.tsx +2 -6
  19. package/dist/prod-next/BUILD_ID +1 -0
  20. package/dist/{standalone/.next → prod-next}/app-path-routes-manifest.json +3 -2
  21. package/dist/{standalone/.next → prod-next}/build-manifest.json +3 -3
  22. package/dist/prod-next/export-marker.json +6 -0
  23. package/dist/prod-next/images-manifest.json +68 -0
  24. package/dist/prod-next/next-minimal-server.js.nft.json +1 -0
  25. package/dist/prod-next/next-server.js.nft.json +1 -0
  26. package/dist/{standalone/.next → prod-next}/prerender-manifest.json +27 -3
  27. package/dist/{standalone/.next → prod-next}/react-loadable-manifest.json +2 -2
  28. package/dist/prod-next/required-server-files.js +337 -0
  29. package/dist/{standalone/.next → prod-next}/required-server-files.json +0 -7
  30. package/dist/{standalone/.next → prod-next}/routes-manifest.json +6 -0
  31. package/dist/prod-next/server/app/_global-error/page.js +32 -0
  32. package/dist/prod-next/server/app/_global-error/page.js.nft.json +1 -0
  33. package/dist/prod-next/server/app/_global-error/page_client-reference-manifest.js +1 -0
  34. package/dist/{standalone/.next → prod-next}/server/app/_global-error.html +1 -1
  35. package/dist/{standalone/.next → prod-next}/server/app/_global-error.rsc +1 -1
  36. package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_full.segment.rsc +1 -1
  37. package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  38. package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  39. package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_head.segment.rsc +1 -1
  40. package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_index.segment.rsc +1 -1
  41. package/dist/{standalone/.next → prod-next}/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  42. package/dist/prod-next/server/app/_not-found/page.js +2 -0
  43. package/dist/prod-next/server/app/_not-found/page.js.nft.json +1 -0
  44. package/dist/prod-next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  45. package/dist/prod-next/server/app/_not-found.html +1 -0
  46. package/dist/prod-next/server/app/_not-found.rsc +21 -0
  47. package/dist/prod-next/server/app/_not-found.segments/_full.segment.rsc +21 -0
  48. package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_head.segment.rsc +1 -1
  49. package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_index.segment.rsc +5 -4
  50. package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  51. package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  52. package/dist/{standalone/.next → prod-next}/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  53. package/dist/prod-next/server/app/api/taskmark-reload/route.js +1 -0
  54. package/dist/prod-next/server/app/api/taskmark-reload/route.js.nft.json +1 -0
  55. package/dist/prod-next/server/app/api/taskmark-reload/route_client-reference-manifest.js +1 -0
  56. package/dist/{standalone/.next → prod-next}/server/app/board/page.js +14 -14
  57. package/dist/prod-next/server/app/board/page.js.nft.json +1 -0
  58. package/dist/prod-next/server/app/board/page_client-reference-manifest.js +1 -0
  59. package/dist/{standalone/.next → prod-next}/server/app/favicon.ico/route.js +1 -1
  60. package/dist/prod-next/server/app/favicon.ico/route.js.nft.json +1 -0
  61. package/dist/prod-next/server/app/page.js +2 -0
  62. package/dist/prod-next/server/app/page.js.nft.json +1 -0
  63. package/dist/prod-next/server/app/page_client-reference-manifest.js +1 -0
  64. package/dist/prod-next/server/app/setup/page.js +2 -0
  65. package/dist/prod-next/server/app/setup/page.js.nft.json +1 -0
  66. package/dist/prod-next/server/app/setup/page_client-reference-manifest.js +1 -0
  67. package/dist/prod-next/server/app/setup.html +1 -0
  68. package/dist/prod-next/server/app/setup.meta +17 -0
  69. package/dist/{standalone/.next/server/app/_not-found.rsc → prod-next/server/app/setup.rsc} +17 -15
  70. package/dist/{standalone/.next/server/app/_not-found.segments → prod-next/server/app/setup.segments}/_full.segment.rsc +17 -15
  71. package/dist/prod-next/server/app/setup.segments/_head.segment.rsc +6 -0
  72. package/dist/prod-next/server/app/setup.segments/_index.segment.rsc +8 -0
  73. package/dist/prod-next/server/app/setup.segments/_tree.segment.rsc +5 -0
  74. package/dist/prod-next/server/app/setup.segments/setup/__PAGE__.segment.rsc +6 -0
  75. package/dist/prod-next/server/app/setup.segments/setup.segment.rsc +5 -0
  76. package/dist/{standalone/.next → prod-next}/server/app-paths-manifest.json +3 -2
  77. package/dist/{standalone/.next → prod-next}/server/chunks/319.js +1 -1
  78. package/dist/{standalone/.next → prod-next}/server/chunks/490.js +2 -2
  79. package/dist/prod-next/server/chunks/57.js +19 -0
  80. package/dist/prod-next/server/chunks/616.js +1 -0
  81. package/dist/prod-next/server/chunks/708.js +1 -0
  82. package/dist/prod-next/server/chunks/724.js +1 -0
  83. package/dist/{standalone/.next/server/chunks/445.js → prod-next/server/chunks/741.js} +2 -2
  84. package/dist/prod-next/server/functions-config-manifest.json +6 -0
  85. package/dist/prod-next/server/interception-route-rewrite-manifest.js +1 -0
  86. package/dist/{standalone/.next → prod-next}/server/middleware-build-manifest.js +1 -1
  87. package/dist/prod-next/server/middleware-react-loadable-manifest.js +1 -0
  88. package/dist/prod-next/server/pages/404.html +1 -0
  89. package/dist/{standalone/.next → prod-next}/server/pages/500.html +1 -1
  90. package/dist/prod-next/server/server-reference-manifest.js +1 -0
  91. package/dist/prod-next/server/server-reference-manifest.json +1 -0
  92. package/dist/prod-next/static/chunks/51.e9139a9ea6eb36b9.js +1 -0
  93. package/dist/{standalone/.next/static/chunks/781-f9f201ddc8c8591a.js → prod-next/static/chunks/613-6f794154d28bb75a.js} +1 -1
  94. package/dist/prod-next/static/chunks/app/_global-error/page-d34451c04262dd7d.js +1 -0
  95. package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-d34451c04262dd7d.js +1 -0
  96. package/dist/prod-next/static/chunks/app/board/page-cd1d9be0c0127225.js +1 -0
  97. package/dist/prod-next/static/chunks/app/layout-856e8a282e69e7d0.js +1 -0
  98. package/dist/prod-next/static/chunks/app/page-d34451c04262dd7d.js +1 -0
  99. package/dist/prod-next/static/chunks/app/setup/page-c3003359e8becc84.js +1 -0
  100. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-d34451c04262dd7d.js +1 -0
  101. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-d34451c04262dd7d.js +1 -0
  102. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-d34451c04262dd7d.js +1 -0
  103. package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-d34451c04262dd7d.js +1 -0
  104. package/dist/{standalone/.next/static/chunks/webpack-b59eaf276b7fb38f.js → prod-next/static/chunks/webpack-bfe8992962004e2b.js} +1 -1
  105. package/dist/prod-next/static/sAWr5ATVuqk6Wqs18kAWs/_buildManifest.js +1 -0
  106. package/dist/public/taskmark-snapshot.json +1 -0
  107. package/lib/display-path.ts +6 -0
  108. package/lib/taskmark/autoconfig.ts +54 -3
  109. package/lib/taskmark/dev-reload-token.ts +5 -0
  110. package/lib/taskmark/discover.ts +2 -1
  111. package/lib/taskmark/project-label.ts +3 -2
  112. package/lib/taskmark/validate.ts +4 -4
  113. package/lib/taskmark/workspace-mode.ts +24 -0
  114. package/lib/taskmark/workspace.ts +5 -2
  115. package/next.config.ts +3 -8
  116. package/package.json +7 -4
  117. package/public/taskmark-snapshot.json +1 -1
  118. package/scripts/build-static.mjs +10 -1
  119. package/scripts/prepare-standalone.mjs +53 -20
  120. package/dist/standalone/.next/BUILD_ID +0 -1
  121. package/dist/standalone/.next/server/app/_global-error/page.js +0 -32
  122. package/dist/standalone/.next/server/app/_global-error/page.js.nft.json +0 -1
  123. package/dist/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +0 -1
  124. package/dist/standalone/.next/server/app/_not-found/page.js +0 -2
  125. package/dist/standalone/.next/server/app/_not-found/page.js.nft.json +0 -1
  126. package/dist/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  127. package/dist/standalone/.next/server/app/_not-found.html +0 -1
  128. package/dist/standalone/.next/server/app/board/page.js.nft.json +0 -1
  129. package/dist/standalone/.next/server/app/board/page_client-reference-manifest.js +0 -1
  130. package/dist/standalone/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  131. package/dist/standalone/.next/server/app/page.js +0 -2
  132. package/dist/standalone/.next/server/app/page.js.nft.json +0 -1
  133. package/dist/standalone/.next/server/app/page_client-reference-manifest.js +0 -1
  134. package/dist/standalone/.next/server/app/setup/page.js +0 -2
  135. package/dist/standalone/.next/server/app/setup/page.js.nft.json +0 -1
  136. package/dist/standalone/.next/server/app/setup/page_client-reference-manifest.js +0 -1
  137. package/dist/standalone/.next/server/chunks/164.js +0 -1
  138. package/dist/standalone/.next/server/chunks/57.js +0 -19
  139. package/dist/standalone/.next/server/chunks/570.js +0 -1
  140. package/dist/standalone/.next/server/chunks/666.js +0 -1
  141. package/dist/standalone/.next/server/functions-config-manifest.json +0 -4
  142. package/dist/standalone/.next/server/middleware-react-loadable-manifest.js +0 -1
  143. package/dist/standalone/.next/server/pages/404.html +0 -1
  144. package/dist/standalone/.next/server/server-reference-manifest.js +0 -1
  145. package/dist/standalone/.next/server/server-reference-manifest.json +0 -1
  146. package/dist/standalone/.next/static/chunks/211.a43663747d668728.js +0 -1
  147. package/dist/standalone/.next/static/chunks/app/_global-error/page-82d456ade9f47021.js +0 -1
  148. package/dist/standalone/.next/static/chunks/app/board/page-6db8dec814516c28.js +0 -1
  149. package/dist/standalone/.next/static/chunks/app/layout-3745e06d87691845.js +0 -1
  150. package/dist/standalone/.next/static/chunks/app/page-82d456ade9f47021.js +0 -1
  151. package/dist/standalone/.next/static/chunks/app/setup/page-8a54c6f26633bdca.js +0 -1
  152. package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-82d456ade9f47021.js +0 -1
  153. package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-82d456ade9f47021.js +0 -1
  154. package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-82d456ade9f47021.js +0 -1
  155. package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-82d456ade9f47021.js +0 -1
  156. package/dist/standalone/.next/static/jm3kxf8c6qGe0cYKCktaO/_buildManifest.js +0 -1
  157. package/dist/standalone/node_modules/next/dist/compiled/jest-worker/processChild.js +0 -1
  158. package/dist/standalone/node_modules/next/dist/compiled/jest-worker/threadChild.js +0 -1
  159. package/dist/standalone/node_modules/next/dist/server/lib/start-server.js +0 -494
  160. package/dist/standalone/node_modules/next/dist/server/next-server.js +0 -1429
  161. package/dist/standalone/node_modules/next/dist/server/next.js +0 -455
  162. package/dist/standalone/node_modules/next/dist/server/require-hook.js +0 -82
  163. package/dist/standalone/node_modules/next/dist/server/route-modules/app-page/module.compiled.js +0 -36
  164. package/dist/standalone/node_modules/next/dist/server/route-modules/pages/module.compiled.js +0 -20
  165. package/dist/standalone/node_modules/styled-jsx/index.js +0 -1
  166. package/dist/standalone/node_modules/styled-jsx/package.json +0 -132
  167. package/dist/standalone/node_modules/styled-jsx/style.js +0 -1
  168. package/dist/standalone/package.json +0 -77
  169. package/dist/standalone/public/taskmark-snapshot.json +0 -1
  170. package/dist/standalone/server.js +0 -44
  171. /package/dist/{standalone/.next → prod-next}/package.json +0 -0
  172. /package/dist/{standalone/.next → prod-next}/server/app/_global-error.meta +0 -0
  173. /package/dist/{standalone/.next → prod-next}/server/app/_not-found.meta +0 -0
  174. /package/dist/{standalone/.next → prod-next}/server/app/favicon.ico.body +0 -0
  175. /package/dist/{standalone/.next → prod-next}/server/app/favicon.ico.meta +0 -0
  176. /package/dist/{standalone/.next → prod-next}/server/chunks/318.js +0 -0
  177. /package/dist/{standalone/.next → prod-next}/server/middleware-manifest.json +0 -0
  178. /package/dist/{standalone/.next → prod-next}/server/next-font-manifest.js +0 -0
  179. /package/dist/{standalone/.next → prod-next}/server/next-font-manifest.json +0 -0
  180. /package/dist/{standalone/.next → prod-next}/server/pages-manifest.json +0 -0
  181. /package/dist/{standalone/.next → prod-next}/server/prefetch-hints.json +0 -0
  182. /package/dist/{standalone/.next → prod-next}/server/webpack-runtime.js +0 -0
  183. /package/dist/{standalone/.next → prod-next}/static/chunks/4bd1b696-deb4a0a1da1923b0.js +0 -0
  184. /package/dist/{standalone/.next → prod-next}/static/chunks/669-8103441c67b185da.js +0 -0
  185. /package/dist/{standalone/.next → prod-next}/static/chunks/701-df3ee5f1cf837b1a.js +0 -0
  186. /package/dist/{standalone/.next → prod-next}/static/chunks/794-25668765560cb197.js +0 -0
  187. /package/dist/{standalone/.next → prod-next}/static/chunks/app/_not-found/page-0b3785af737e7968.js +0 -0
  188. /package/dist/{standalone/.next → prod-next}/static/chunks/framework-b29f9083d3cda3b2.js +0 -0
  189. /package/dist/{standalone/.next → prod-next}/static/chunks/main-app-bfb36455d24b3e5a.js +0 -0
  190. /package/dist/{standalone/.next → prod-next}/static/chunks/main-bb33c46a9c25dee8.js +0 -0
  191. /package/dist/{standalone/.next → prod-next}/static/chunks/next/dist/client/components/builtin/global-error-dc53ab29e0491fe4.js +0 -0
  192. /package/dist/{standalone/.next → prod-next}/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  193. /package/dist/{standalone/.next → prod-next}/static/css/2e6ec4c80894e3c0.css +0 -0
  194. /package/dist/{standalone/.next → prod-next}/static/css/c593627ceef17a4b.css +0 -0
  195. /package/dist/{standalone/.next → prod-next}/static/media/36966cca54120369-s.p.woff2 +0 -0
  196. /package/dist/{standalone/.next → prod-next}/static/media/982ceffe7b733b3b-s.p.woff2 +0 -0
  197. /package/dist/{standalone/.next → prod-next}/static/media/b7387a63dd068245-s.woff2 +0 -0
  198. /package/dist/{standalone/.next → prod-next}/static/media/b8bfb68ab9ab617c-s.woff2 +0 -0
  199. /package/dist/{standalone/.next → prod-next}/static/media/e1aab0933260df4d-s.woff2 +0 -0
  200. /package/dist/{standalone/.next/static/jm3kxf8c6qGe0cYKCktaO → prod-next/static/sAWr5ATVuqk6Wqs18kAWs}/_ssgManifest.js +0 -0
  201. /package/dist/{standalone/public → public}/apple-touch-icon.png +0 -0
  202. /package/dist/{standalone/public → public}/favicon-96x96.png +0 -0
  203. /package/dist/{standalone/public → public}/favicon.ico +0 -0
  204. /package/dist/{standalone/public → public}/favicon.svg +0 -0
  205. /package/dist/{standalone/public → public}/file.svg +0 -0
  206. /package/dist/{standalone/public → public}/globe.svg +0 -0
  207. /package/dist/{standalone/public → public}/next.svg +0 -0
  208. /package/dist/{standalone/public → public}/og/og-default.png +0 -0
  209. /package/dist/{standalone/public → public}/site.webmanifest +0 -0
  210. /package/dist/{standalone/public → public}/tm_dark.png +0 -0
  211. /package/dist/{standalone/public → public}/tm_light.png +0 -0
  212. /package/dist/{standalone/public → public}/vercel.svg +0 -0
  213. /package/dist/{standalone/public → public}/web-app-manifest-192x192.png +0 -0
  214. /package/dist/{standalone/public → public}/web-app-manifest-512x512.png +0 -0
  215. /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 serve
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 -p @taskmark/ui taskmark serve
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.4"
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 # next standalone + dist/ for the CLI
81
- npm run serve # local bin against cwd/env board
82
- npm run dev # Next dev server on :3000 (setup wizard unless auto-bound)
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
- Zero-config for `npm run dev`: `TASKMARK_BOARD` / `TASKMARK_MASTER` / cwd (see above). Cookie multi-master setup still works when unbound.
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 standalone server packaged for `taskmark serve`
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
+ }
@@ -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>
@@ -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
+ }