taskops-cli 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl

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.
taskops/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  __title__ = "taskops"
4
- __version__ = "0.5.4" # single source of truth; pyproject reads it dynamically
4
+ __version__ = "0.5.6" # single source of truth; pyproject reads it dynamically
taskops/http/handler.py CHANGED
@@ -75,8 +75,16 @@ class Handler(BaseHTTPRequestHandler):
75
75
  elif tail.startswith("ui"):
76
76
  page.answer(self, board, tail[2:]) # 0.5.0's address, kept — links were pasted
77
77
  elif self.mounts.ui is None and NAME.match(board) and (not tail or static.asset(tail)):
78
- # the page at the board's OWN address; assets are a CLOSED set
79
- page.answer(self, board, tail)
78
+ if not tail and not self.path.partition("?")[0].endswith("/"):
79
+ # The page's assets are RELATIVE (./app.js — one bundle, three mounts),
80
+ # so the slashless /<board> resolves them against the root and renders
81
+ # blank: exactly the address people paste. One hop repairs them all —
82
+ # keeping the query, because ?token= is how a window authenticates.
83
+ _, sep, query = self.path.partition("?")
84
+ self._redirect(f"/{board}/{sep}{query}")
85
+ else:
86
+ # the page at the board's OWN address; assets are a CLOSED set
87
+ page.answer(self, board, tail)
80
88
  elif (found := static.at_root(self.mounts.ui, self.path)) is not None:
81
89
  self._send(200, *found) # a WINDOW serves its page at the ROOT
82
90
  else:
@@ -170,6 +178,13 @@ class Handler(BaseHTTPRequestHandler):
170
178
  def _json(self, status: int, body: dict[str, Any]) -> None:
171
179
  self._send(status, json.dumps(body).encode(), "application/json")
172
180
 
181
+ def _redirect(self, location: str) -> None:
182
+ """One permanent hop, no body — the only writer of a Location header."""
183
+ self.send_response(301)
184
+ self.send_header("Location", location)
185
+ self.send_header("Content-Length", "0")
186
+ self.end_headers()
187
+
173
188
  def _send(self, status: int, data: bytes, kind: str) -> None:
174
189
  """Content-Length on EVERY answer: this is HTTP/1.1 with keep-alive, and a body
175
190
  without one leaves the next request on the socket unparseable."""