taskops-cli 0.4.1__tar.gz → 0.4.2__tar.gz

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 (279) hide show
  1. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ARCHITECTURE.md +26 -1
  2. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/CHANGELOG.md +34 -0
  3. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/PKG-INFO +1 -1
  4. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_version.py +1 -1
  5. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/serving.py +9 -3
  6. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/types.py +12 -0
  7. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/upstream.py +59 -7
  8. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/schema.py +1 -1
  9. taskops_cli-0.4.2/src/taskops/ui/app.js +61 -0
  10. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_facts.py +4 -4
  11. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/activity.py +7 -1
  12. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_topology.py +90 -0
  13. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_verbs.py +29 -1
  14. taskops_cli-0.4.2/ui/smoke/sections/all-chapters-scope.tsx +99 -0
  15. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/App.tsx +24 -2
  16. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/MilestonePicker.tsx +28 -9
  17. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/useBoard.ts +6 -3
  18. taskops_cli-0.4.1/src/taskops/ui/app.js +0 -61
  19. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.claude/settings.json +0 -0
  20. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.gitattributes +0 -0
  21. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.github/workflows/ci.yml +0 -0
  22. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.github/workflows/workflow.yml +0 -0
  23. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.gitignore +0 -0
  24. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.mcp.json +0 -0
  25. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/cache.sqlite +0 -0
  26. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/cache.sqlite-shm +0 -0
  27. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/cache.sqlite-wal +0 -0
  28. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/events.jsonl +0 -0
  29. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/live.sqlite +0 -0
  30. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/live.sqlite-shm +0 -0
  31. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/live.sqlite-wal +0 -0
  32. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board.json +0 -0
  33. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/reports/lifecycle-chapter.html +0 -0
  34. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/reports/reports-chapter.html +0 -0
  35. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/ui.lock +0 -0
  36. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/CLAUDE.md +0 -0
  37. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/README.md +0 -0
  38. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/pyproject.toml +0 -0
  39. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/__init__.py +0 -0
  40. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_clock.py +0 -0
  41. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_errors.py +0 -0
  42. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_ids.py +0 -0
  43. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_json.py +0 -0
  44. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_locate.py +0 -0
  45. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_wire.py +0 -0
  46. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/board.py +0 -0
  47. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/__init__.py +0 -0
  48. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/__main__.py +0 -0
  49. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/admin.py +0 -0
  50. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/claude.py +0 -0
  51. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/commands.py +0 -0
  52. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/enrol.py +0 -0
  53. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/github.py +0 -0
  54. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/grants.py +0 -0
  55. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/hooks.py +0 -0
  56. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/main.py +0 -0
  57. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/operate.py +0 -0
  58. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/paging.py +0 -0
  59. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/parser.py +0 -0
  60. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/pull.py +0 -0
  61. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/push.py +0 -0
  62. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/remote.py +0 -0
  63. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/rm.py +0 -0
  64. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/team.py +0 -0
  65. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/watch.py +0 -0
  66. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/window.py +0 -0
  67. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/wording.py +0 -0
  68. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/__init__.py +0 -0
  69. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/actors.py +0 -0
  70. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/challenge.py +0 -0
  71. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/chapters.py +0 -0
  72. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/event.py +0 -0
  73. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/forge.py +0 -0
  74. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/graph.py +0 -0
  75. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/holding.py +0 -0
  76. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/hours.py +0 -0
  77. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/kinds.py +0 -0
  78. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/machine.py +0 -0
  79. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/mentions.py +0 -0
  80. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/replay.py +0 -0
  81. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/reports.py +0 -0
  82. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/review.py +0 -0
  83. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/scope.py +0 -0
  84. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/seams.py +0 -0
  85. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/__init__.py +0 -0
  86. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/bind.py +0 -0
  87. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/catchup.py +0 -0
  88. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/claudefiles.py +0 -0
  89. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/diff.py +0 -0
  90. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/install.py +0 -0
  91. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/landing.py +0 -0
  92. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/patch.py +0 -0
  93. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/remote.py +0 -0
  94. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/run.py +0 -0
  95. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/sig.py +0 -0
  96. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/trailer.py +0 -0
  97. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/trees.py +0 -0
  98. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/__init__.py +0 -0
  99. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/admin.py +0 -0
  100. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/auth.py +0 -0
  101. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/feed.py +0 -0
  102. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/gitdoor.py +0 -0
  103. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/grants.py +0 -0
  104. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/handler.py +0 -0
  105. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/ingest.py +0 -0
  106. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/login.py +0 -0
  107. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/members.py +0 -0
  108. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/mounts.py +0 -0
  109. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/removal.py +0 -0
  110. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/rpc.py +0 -0
  111. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/scoped.py +0 -0
  112. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/server.py +0 -0
  113. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/static.py +0 -0
  114. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/watcher.py +0 -0
  115. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/identity.py +0 -0
  116. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/__init__.py +0 -0
  117. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/__main__.py +0 -0
  118. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/activity.py +0 -0
  119. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/before.py +0 -0
  120. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/boards.py +0 -0
  121. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/boardview.py +0 -0
  122. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/brief.py +0 -0
  123. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/chapter.py +0 -0
  124. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/dossier.py +0 -0
  125. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/fields.py +0 -0
  126. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/gitmoves.py +0 -0
  127. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/hello.py +0 -0
  128. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/integrate.py +0 -0
  129. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/orders.py +0 -0
  130. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/render.py +0 -0
  131. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/server.py +0 -0
  132. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/thread.py +0 -0
  133. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/tools.py +0 -0
  134. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/py.typed +0 -0
  135. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/session.py +0 -0
  136. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/__init__.py +0 -0
  137. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/cache.py +0 -0
  138. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/creds.py +0 -0
  139. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/handover.py +0 -0
  140. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/live.py +0 -0
  141. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/log.py +0 -0
  142. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/pubkeys.py +0 -0
  143. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/reviews.py +0 -0
  144. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/server.py +0 -0
  145. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/stores.py +0 -0
  146. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/ui/index.html +0 -0
  147. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/ui/style.css +0 -0
  148. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/__init__.py +0 -0
  149. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_args.py +0 -0
  150. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_cards.py +0 -0
  151. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_chapter.py +0 -0
  152. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_context.py +0 -0
  153. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_mentions.py +0 -0
  154. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_rows.py +0 -0
  155. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_stories.py +0 -0
  156. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_waiting.py +0 -0
  157. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_windows.py +0 -0
  158. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/assign.py +0 -0
  159. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/card.py +0 -0
  160. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/events.py +0 -0
  161. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/filed.py +0 -0
  162. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/plan.py +0 -0
  163. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/project.py +0 -0
  164. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/pulse.py +0 -0
  165. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/record.py +0 -0
  166. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/report.py +0 -0
  167. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/review.py +0 -0
  168. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/take.py +0 -0
  169. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/update.py +0 -0
  170. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/__init__.py +0 -0
  171. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/conftest.py +0 -0
  172. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_architecture.py +0 -0
  173. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_claude.py +0 -0
  174. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_core.py +0 -0
  175. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_git.py +0 -0
  176. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_mcp.py +0 -0
  177. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_report.py +0 -0
  178. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_store.py +0 -0
  179. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_ui.py +0 -0
  180. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_window.py +0 -0
  181. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/ui_harness.js +0 -0
  182. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/build.mjs +0 -0
  183. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/index.html +0 -0
  184. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/package-lock.json +0 -0
  185. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/package.json +0 -0
  186. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/fixture.json +0 -0
  187. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/main.tsx +0 -0
  188. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/run.mjs +0 -0
  189. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/actors-window-filter.tsx +0 -0
  190. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/actors.tsx +0 -0
  191. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/board-columns.tsx +0 -0
  192. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/board-flow-view.tsx +0 -0
  193. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/board-motion-and-avatars.tsx +0 -0
  194. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapter-completion-derivation.tsx +0 -0
  195. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapter-landed.tsx +0 -0
  196. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapter-story-grid.tsx +0 -0
  197. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapters-open.tsx +0 -0
  198. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/comment-box.tsx +0 -0
  199. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/comment-toasts-model.tsx +0 -0
  200. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/comment-toasts-stack.tsx +0 -0
  201. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/diff-page.tsx +0 -0
  202. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/dossier.tsx +0 -0
  203. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/event-stream.tsx +0 -0
  204. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/feed-reconnect.tsx +0 -0
  205. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/forge-links.tsx +0 -0
  206. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/forge-opens-the-board.tsx +0 -0
  207. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/git-diff.tsx +0 -0
  208. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/monitor-panes.tsx +0 -0
  209. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/overlay-escape.tsx +0 -0
  210. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/report-markdown.tsx +0 -0
  211. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/report-sandbox.tsx +0 -0
  212. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/reviewing-skew.tsx +0 -0
  213. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/section.ts +0 -0
  214. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/swarm-topology.tsx +0 -0
  215. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/thread-closing-note.tsx +0 -0
  216. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/worktrees-index.tsx +0 -0
  217. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections.mjs +0 -0
  218. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/client.ts +0 -0
  219. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/completed.ts +0 -0
  220. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/actors/Daysheet.tsx +0 -0
  221. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/actors/DevPanel.tsx +0 -0
  222. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/CardTile.tsx +0 -0
  223. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/Column.tsx +0 -0
  224. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/ViewToggle.tsx +0 -0
  225. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/flip.ts +0 -0
  226. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/useFlip.ts +0 -0
  227. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/CommentBox.tsx +0 -0
  228. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Drawer.tsx +0 -0
  229. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Patch.tsx +0 -0
  230. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Sections.tsx +0 -0
  231. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Thread.tsx +0 -0
  232. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/split.ts +0 -0
  233. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/tokens.ts +0 -0
  234. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/AvatarStack.tsx +0 -0
  235. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/Header.tsx +0 -0
  236. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/KpiRail.tsx +0 -0
  237. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/TabNav.tsx +0 -0
  238. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/flow/FlowView.tsx +0 -0
  239. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/flow/layout.ts +0 -0
  240. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Chapter.tsx +0 -0
  241. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/DependencyChain.tsx +0 -0
  242. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/EditSurface.tsx +0 -0
  243. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/EventStream.tsx +0 -0
  244. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/LeaseHealth.tsx +0 -0
  245. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/LiveLeases.tsx +0 -0
  246. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Mentions.tsx +0 -0
  247. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Pane.tsx +0 -0
  248. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Swarm.tsx +0 -0
  249. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Throughput.tsx +0 -0
  250. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/panels.ts +0 -0
  251. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/reports/ReportFrame.tsx +0 -0
  252. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Avatar.tsx +0 -0
  253. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Markdown.tsx +0 -0
  254. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Overlay.tsx +0 -0
  255. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Segmented.tsx +0 -0
  256. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/overlayStack.ts +0 -0
  257. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/story/ChapterStory.tsx +0 -0
  258. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/story/stats.ts +0 -0
  259. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/toasts/ToastStack.tsx +0 -0
  260. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/toasts/model.ts +0 -0
  261. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/toasts/useToasts.ts +0 -0
  262. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/format.ts +0 -0
  263. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/hoursWindow.ts +0 -0
  264. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/links.tsx +0 -0
  265. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/main.tsx +0 -0
  266. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/markdown.ts +0 -0
  267. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Actors.tsx +0 -0
  268. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Board.tsx +0 -0
  269. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Monitor.tsx +0 -0
  270. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Reports.tsx +0 -0
  271. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/WorktreeDiff.tsx +0 -0
  272. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Worktrees.tsx +0 -0
  273. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/theme/theme.ts +0 -0
  274. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/theme/tokens.css +0 -0
  275. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/types.ts +0 -0
  276. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/useEvents.ts +0 -0
  277. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/styles/index.css +0 -0
  278. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/tsconfig.json +0 -0
  279. {taskops_cli-0.4.1 → taskops_cli-0.4.2}/uv.lock +0 -0
@@ -1361,7 +1361,17 @@ files that answer it:
1361
1361
  * **the card dossier drawer**, opening over Monitor and Board through `App`'s
1362
1362
  `openCard` — it renders the acceptance criteria no v1 screen ever drew, and
1363
1363
  carries the UI's ONE write, the comment box with its mention picker
1364
- . The header's milestone picker scopes every tab at once.
1364
+ . The header's milestone picker scopes every tab at once, and **"all
1365
+ chapters" is an ARGUMENT** — `milestone=*` (`core/types.py::EVERYTHING`), not
1366
+ the absence of one. It sent nothing for a wave, which the server reads as
1367
+ "resolve the scope yourself" and resolves to the single open chapter: the page
1368
+ arrived narrowed to that chapter with the ✓ sitting on "all chapters" the whole
1369
+ time, and clicking the option changed no argument (2026-08-18). Absence still
1370
+ means exactly that — it is the dashboard's OPENING state, and `App::scopeOf`
1371
+ resolves it against the chapter the answer came back scoped to, so the ✓ marks
1372
+ the scope the page is actually drawn at. `verbs/activity.py` refuses `*` in the
1373
+ words of its own doors: a story is one chapter's cards by definition.
1374
+ `ui/smoke/sections/all-chapters-scope.tsx`.
1365
1375
 
1366
1376
  * **the comment toasts**, a stack bottom-right over every tab
1367
1377
  (`ui/src/components/toasts/`), because the UI's one write was invisible to
@@ -1659,6 +1669,21 @@ local process**; the browser holds only the token `taskops ui` mints into
1659
1669
  `ui.json`, which is local and revocable. `tests/test_topology.py` searches every
1660
1670
  served byte, and their headers, for the team token.
1661
1671
 
1672
+ **A SESSION RUNS OUT AND THE WINDOW DOES NOT** (2026-08-18). A host session is
1673
+ twelve hours (`http/login.py::SESSION_TTL`), and a window is a thing you leave
1674
+ open: `_upstream` read the token lying in `remote.json` and `Upstream` held it
1675
+ for the life of the process, so the morning after, every read answered "that
1676
+ credential expired" — and the habit that taught was `taskops join`, a human
1677
+ re-pasting a credential the machine can mint itself, which is the exact thing
1678
+ the session chapter exists to end. Two halves, both fixed: the command opens
1679
+ with `session.fresh`, like every other command has since that chapter landed,
1680
+ and the window is handed `session.refresher` so it can sign in AGAIN mid-flight
1681
+ — `board.py::RemoteBoard.call`'s retry, narrow for the same reason (the server
1682
+ said `EXPIRED`, this process holds a key). A window with no way back in — a
1683
+ standing bearer, a public viewer — is unchanged: `refresh` is None and nobody
1684
+ replaces a token behind its owner's back (`session.py`). `Upstream` still knows
1685
+ nothing about keys or files: it is handed a callable.
1686
+
1662
1687
  **A refusal from the remote arrives in the SERVER's own words**, status and all:
1663
1688
  `upstream.py` returns `(status, bytes)` and re-wraps only a body that is not an
1664
1689
  envelope. A board that says "held by agent:berna/w1" with a 409 must not reach
@@ -3,6 +3,40 @@
3
3
  The source of truth for release notes — GitHub Releases are extracted from
4
4
  here, never written twice.
5
5
 
6
+ ## 0.4.2 — the filter that filtered nothing, and the window that stopped signing in
7
+
8
+ - **"All chapters" is an ARGUMENT now, and it really shows every chapter.** The
9
+ option sent no `milestone=` at all — and an absent milestone never meant "the
10
+ whole board": it means "server, resolve the scope yourself", which resolves to
11
+ the single open chapter when there is exactly one. So on a board with one
12
+ chapter open and seven landed (Berna's, reported 2026-08-18) the page came back
13
+ narrowed to that chapter, the menu drew its ✓ beside a scope that had never
14
+ been in effect, and clicking the option changed no argument at all — the only
15
+ way to see other work was to name every chapter in turn. Board-wide is now
16
+ asked for by name (`milestone=*`, `core/types.py::EVERYTHING`). Absence still
17
+ means exactly what it meant, so no agent and no existing caller sees any
18
+ change: it is the dashboard's OPENING state, and the ✓ now falls on the chapter
19
+ the server actually resolved, so the pill can no longer claim a scope the page
20
+ is not drawn at. `taskops_activity` refuses `*` in the words of its own doors —
21
+ a story is one chapter's cards by definition — where it used to answer
22
+ `milestone * does not exist`, a lookup's answer to a question that was never a
23
+ lookup.
24
+ - **A window whose session ran out signs in again instead of teaching `taskops
25
+ join`.** A host session is twelve hours and a window is a thing you leave open.
26
+ Two halves of one hole, both on the `taskops ui` path and nowhere else: the
27
+ command read the token lying in `remote.json` instead of `session.fresh` —
28
+ which every other command has taken since sessions landed — so a window opened
29
+ on yesterday's session was dead on arrival; and the forwarder held the token it
30
+ was built with for the life of the process, so a window that was open when the
31
+ session expired answered "that credential expired" to every read. Neither path
32
+ could come back, so the habit it taught was re-joining: a human pasting a
33
+ credential the machine can mint itself from the key it already has. The retry
34
+ is `RemoteBoard.call`'s and just as narrow — the SERVER said EXPIRED, and this
35
+ process holds a key — and it is a callable passed in, so the forwarder still
36
+ knows nothing about keys, files or logins. A window with no way back in (a
37
+ standing bearer token, a public viewer) is unchanged: nobody replaces a token
38
+ behind its owner's back.
39
+
6
40
  ## 0.4.1 — the board comes alive: the forge enrols the team, hours stop lying, comments become visible
7
41
 
8
42
  - **A comment now SAYS so, on every open dashboard.** The UI's one write was
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: taskops-cli
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: A shared work board for teams of coding agents, with a human who decides.
5
5
  Project-URL: Repository, https://github.com/bernatch22/taskops
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  __title__ = "taskops"
4
- __version__ = "0.4.1" # single source of truth; pyproject reads it dynamically
4
+ __version__ = "0.4.2" # single source of truth; pyproject reads it dynamically
@@ -25,7 +25,7 @@ if TYPE_CHECKING: # a NAME for the annotation, never an import at start-up
25
25
  from ..http.upstream import Upstream
26
26
 
27
27
  from . import window
28
- from .. import _clock
28
+ from .. import _clock, session
29
29
  from .._json import as_object
30
30
  from ..board import DIR, find_root, is_project, read_config
31
31
  from .._errors import TaskopsError
@@ -133,7 +133,13 @@ def _upstream(root: Path) -> Upstream | None:
133
133
  url = str(config.get("url", ""))
134
134
  if not url:
135
135
  return None
136
- token = str(config.get("token", ""))
136
+ # The SESSION, refreshed here if it has run out and refreshable again from
137
+ # inside the window if it runs out while somebody is reading (2026-08-18,
138
+ # `http/upstream.py`): a host session is twelve hours and this window is
139
+ # meant to be left open. `fresh` falls back to the cached token whenever it
140
+ # cannot sign in, and `refresher` is None for a standing bearer — neither
141
+ # replaces a token nobody may replace, which is `session.py`'s own rule.
142
+ token = str(session.fresh(root, config, _clock.now()))
137
143
  if not token and config.get("readonly"):
138
144
  # A viewer's window (`commands.py::_watch`): an Upstream with no bearer.
139
145
  # `Mounts.public` reads exactly this — no token means this window is
@@ -145,7 +151,7 @@ def _upstream(root: Path) -> Upstream | None:
145
151
  f"{root / DIR}/board.json points at {url} but there is no credential in "
146
152
  "remote.json — run: taskops join <url with ?token= or ?invite=>"
147
153
  )
148
- return Remote(url, token)
154
+ return Remote(url, token, refresh=session.refresher(root, config))
149
155
 
150
156
 
151
157
  def _open(url: str) -> int:
@@ -161,6 +161,18 @@ MILESTONE_STATUSES = ("open", "done", "dropped")
161
161
  CLOSED = ("done", "dropped")
162
162
  LEASE_TTL = 900.0 # 15 min; every MCP call renews it
163
163
  PROJECT = "project" # the `task` of board-level events
164
+ EVERYTHING = "*"
165
+ """`milestone=*`: the WHOLE board, said out loud — no chapter in scope.
166
+
167
+ An ABSENT milestone means "resolve it yourself" (`verbs/_facts.py::in_scope`),
168
+ which with exactly one open chapter IS that chapter. Right for an agent, and it
169
+ left the dashboard's "all chapters" unreachable: it sent no `milestone=`, the
170
+ board came back narrowed anyway, and the picker drew a ✓ beside a scope that had
171
+ never been in effect and that clicking could not change (2026-08-18). Absent and
172
+ "all" are two questions; they now have two arguments, and nothing that omitted
173
+ it sees any change. `verbs/activity.py` is where the two genuinely part — it
174
+ REQUIRES a chapter, and answered `milestone * does not exist`, a lookup's answer
175
+ to a question that was never a lookup."""
164
176
 
165
177
 
166
178
  # Fields of a Card that `edited` may target. Anything else is a BadRequest, so
@@ -28,6 +28,20 @@ it, because then the reader is debugging this host instead of reading the board.
28
28
  Only a server that could not be reached at all is spoken for here, and it says
29
29
  so in the same envelope shape everything else uses.
30
30
 
31
+ **A SESSION RUNS OUT, and the window must not** (2026-08-18). A host session is
32
+ `login.py::SESSION_TTL` — twelve hours — and this class held the token it was
33
+ built with for the life of the process, so a window left open overnight woke up
34
+ answering "that credential expired" to every read, and the habit it taught was
35
+ `taskops join`: re-pasting a credential the machine can mint by itself, which is
36
+ exactly what the session chapter exists to end. The retry here is
37
+ `board.py::RemoteBoard.call`'s, narrow for the same reason — the SERVER said
38
+ EXPIRED, and this process was handed a way to sign in — and it is a callable
39
+ passed in rather than a key read here, so this module still knows nothing about
40
+ keys, files or logins (`cli/serving.py` builds it out of `session.refresher`).
41
+ A window with no way to refresh (a standing bearer token, a public viewer) is
42
+ unchanged: `refresh` is None and the refusal travels to the page as it always
43
+ did.
44
+
31
45
  **The feed is a poll, not a relay** (`feed.py`: a message is a SIGNAL, not a
32
46
  payload). `mounts.py` asks `head()` on an interval while somebody is connected
33
47
  and pokes the page when the number moves; the page then refetches through the
@@ -39,10 +53,12 @@ something the board did not say.
39
53
  from __future__ import annotations
40
54
 
41
55
  import json
56
+ from typing import Callable
42
57
  from urllib.error import URLError, HTTPError
43
58
  from urllib.request import Request, urlopen
44
59
 
45
60
  from .._json import as_object
61
+ from ..store.creds import EXPIRED
46
62
 
47
63
  TIMEOUT = 20.0
48
64
  """Seconds to wait on the remote for one /rpc. The same budget `board.py` gives
@@ -53,16 +69,38 @@ waiting on, not a background job."""
53
69
  class Upstream:
54
70
  """One remote board, addressed by url, spoken to with one credential."""
55
71
 
56
- def __init__(self, url: str, token: str, timeout: float = TIMEOUT) -> None:
72
+ def __init__(
73
+ self,
74
+ url: str,
75
+ token: str,
76
+ timeout: float = TIMEOUT,
77
+ refresh: Callable[[], str] | None = None,
78
+ ) -> None:
57
79
  self.url = url.rstrip("/")
58
80
  self.token = token
59
81
  self.timeout = timeout
82
+ # How to mint ANOTHER session when this one runs out, or None when
83
+ # nobody may replace this token behind its owner's back — the docstring
84
+ # above argues both halves.
85
+ self.refresh = refresh
60
86
 
61
87
  def rpc(self, body: bytes) -> tuple[int, bytes]:
62
- """(status, bytes) — the remote's own answer, untouched.
88
+ """(status, bytes) — the remote's own answer, untouched, with ONE retry
89
+ for the one case a retry can fix: the session ran out and this window
90
+ knows how to sign in again.
63
91
 
64
92
  Returns rather than raises: this sits under an HTTP handler whose job is
65
93
  to hand the page what the board said, and a refusal IS an answer."""
94
+ status, answer = self._post(body)
95
+ if self.refresh is None or not _ran_out(answer):
96
+ return status, answer
97
+ try:
98
+ self.token = self.refresh()
99
+ except Exception: # noqa: BLE001 — a failed refresh is the board's refusal, unchanged
100
+ return status, answer
101
+ return self._post(body)
102
+
103
+ def _post(self, body: bytes) -> tuple[int, bytes]:
66
104
  headers = {"Content-Type": "application/json"}
67
105
  if self.token:
68
106
  # No token is the VIEWER's window onto a public board: sending an
@@ -94,14 +132,28 @@ class Upstream:
94
132
  return seq_of(answer) if status == 200 else 0
95
133
 
96
134
 
135
+ def _ran_out(answer: bytes) -> bool:
136
+ """Is this the board saying the SESSION expired — not any other refusal?
137
+
138
+ `store/creds.py::EXPIRED` is the named sentence `board.py` matches on for
139
+ the same reason: the words after it are a human's instruction and drift."""
140
+ body = as_object(_parsed(answer))
141
+ if body.get("ok"):
142
+ return False
143
+ return EXPIRED in str(as_object(body.get("error")).get("message", ""))
144
+
145
+
146
+ def _parsed(answer: bytes) -> object:
147
+ try:
148
+ return json.loads(answer)
149
+ except ValueError:
150
+ return None
151
+
152
+
97
153
  def seq_of(answer: bytes) -> int:
98
154
  """The `seq` out of an envelope, or 0. Never raises: a body that is not an
99
155
  envelope means the poke is skipped, which costs one tick."""
100
- try:
101
- parsed: object = json.loads(answer)
102
- except ValueError:
103
- return 0
104
- seq: object = as_object(parsed).get("seq", 0)
156
+ seq: object = as_object(_parsed(answer)).get("seq", 0)
105
157
  return seq if isinstance(seq, int) else 0
106
158
 
107
159
 
@@ -29,7 +29,7 @@ SCHEMAS: dict[str, dict[str, Any]] = {
29
29
  **DEV_SCHEMAS,
30
30
  "taskops_board": _object(
31
31
  {
32
- "milestone": _text("ms-… to focus one chapter; default: the open one"),
32
+ "milestone": _text("ms-… one chapter, * the whole board; default: the open one"),
33
33
  "window": _text(
34
34
  'hours over "7d" (the last N calendar days), "month" (this month so '
35
35
  'far), "2026-07" (that calendar month) or "total" (the whole log)'