ui.window¶
WindowAPI and WindowChild for pywebview-based floating UI.
This document covers the legacy pywebview shell. The repo also has
an optional Electron shell with the same multi-window popup behavior;
see electron_shell.md and
src/taskclf/ui/ELECTRON_MIGRATION.md.
Overview¶
Creates frameless, always-on-top, draggable windows backed by the
platform webview (WebKit on macOS, Edge WebView2 on Windows). Exposes
a WindowAPI to the SolidJS frontend via window.pywebview.api.
Three windows are managed:
| Window | Size (w x h) | Purpose |
|---|---|---|
| Compact pill | 150 x 30 | Persistent header badge showing current label/app |
| Label grid | 280 x 330 | Quick-label popup (hidden by default) |
| State panel | 280 x 520 | System/history debug panel (hidden by default) |
The compact pill is positioned at the top-right of the primary screen. Child windows (label grid, panel) are anchored below the pill on initial show. Once visible, children can be freely dragged to any monitor; they will not snap back until hidden and re-shown.
Electron uses the same three-window arrangement. Its main process
creates separate popup BrowserWindow instances for the label grid
(?view=label) and state panel (?view=panel), with an equivalent
child-window state machine for hover, pin, delayed hide, and drag
detection. Those popups are created on first use (not at process
startup) so the pill dashboard can load with lower overhead; routes and
markup match the pywebview shell once opened.
The compact route in the Solid app uses the same layout and host
commands as pywebview and Electron. The child routes ?view=label and
?view=panel use the same markup as native popups (full viewport, top
drag strip, hover handlers that call Host.invoke) — not a separate
“browser preview” layout.
A plain browser tab (for example Vite alone, with no
window.pywebview or window.electronHost) still loads that UI; the
compact route uses a light solid page background plus right-aligned
in-page label/panel popups with the same 300 ms delayed-hide feel as the
native shell because Host.invoke window calls are no-ops there. The
separate ?view=label and ?view=panel routes match native popup
markup for focused testing; use pywebview or Electron for real
multi-window behavior.
See host_window_drag_strip.md for the
shared grab-bar component used on child routes.
WindowChild¶
Encapsulates the visibility / pin / timer state machine shared by the
label-grid and state-panel child windows. Each WindowChild instance
holds its own window reference, visibility and pin flags, a
delayed-hide timer, and an expected-position tuple for drag detection.
Positioning logic is injected via a callback (position_fn) so each
child can use different layout math while sharing the same state
machine.
WindowChild and WindowAPI are implemented as slotted dataclasses;
constructor arguments remain unchanged.
Methods¶
| Method | Description |
|---|---|
visibility_on(main) |
Show on hover (non-pinned); cancels any pending hide timer |
visibility_off_deferred() |
Schedule a delayed hide (300 ms) unless pinned |
visibility_off() |
Immediate hide — clears visible, pinned, and expected position |
pin_toggle(main) |
Toggle pinned state (click to pin/unpin) |
timer_cancel() |
Cancel any pending hide timer |
position_sync() |
Reposition via the injected layout callback |
drag_detected() |
True if the user has dragged the window away from expected position |
WindowAPI¶
Python API exposed to JavaScript as window.pywebview.api.<method>().
The Host adapter in the frontend's host.ts calls these methods;
components never reference window.pywebview directly.
Internally, WindowAPI delegates to two WindowChild instances
(_label and _panel) for the label grid and state panel.
Public methods¶
| Method | Description |
|---|---|
bind(window) |
Bind the main compact window and subscribe to its moved event |
bind_label(label) |
Bind the label grid window |
bind_panel(panel) |
Bind the state panel window |
window_toggle() |
Toggle the compact pill's visibility |
label_grid_show() |
Show the label grid below the pill (right-aligned) |
label_grid_hide() |
Schedule a delayed hide of the label grid (300 ms) |
show_transition_notification(prompt) |
Show a native desktop notification for a prompt_label event using privacy-safe copy plus the exact local time range |
state_panel_toggle() |
Toggle the panel's visibility; positions below the label grid when both are visible |
frontend_debug_log(message) |
Accept frontend debug lines from the webview and forward them to the Python logger at DEBUG level |
frontend_error_log(message) |
Accept frontend error lines from the webview and forward them to the Python logger at ERROR level |
visible |
Property returning whether the compact pill is currently visible |
Window positioning¶
- The label grid is placed at
(pill.x + pill.width - grid.width, pill.y + pill.height + 4), right-aligned with the pill. - The panel is placed below the pill (or below the label grid if it is visible), also right-aligned.
- When the pill is dragged, the label grid follows only if the user has not independently dragged it. Once the user drags a child window away (beyond a 10 px tolerance), that child stops following and stays where the user placed it.
- Hiding a child window resets its expected position, so the next show re-anchors it to the pill.
- Child windows hide with a 300 ms delay to avoid flicker on rapid toggle.
Dragging¶
All three windows use CSS pywebview-drag-region elements for drag
handles. The compact pill uses left and right flex spacers (empty
regions that grow with flex: 1) as drag targets; the label badge and
status dot sit in a fixed center column outside those regions so they
still receive hover/click events. Each child window keeps a small grab
bar at the top. The main pill sets easy_drag=False to avoid conflicts
between the native easy-drag handler and the CSS drag region, which
previously caused glitches on multi-monitor setups.
window_run (ui.window_run)¶
window_run(
port: int = 8741,
on_ready: Callable[..., Any] | None = None,
window_api: WindowAPI | None = None,
) -> None
Creates all three pywebview windows and starts the GUI loop. Blocks on the main thread until the user closes the window.
Defined in taskclf.ui.window_run.
| Parameter | Default | Description |
|---|---|---|
port |
8741 |
Port of the FastAPI server to load in the webview |
on_ready |
None |
Callback invoked after the GUI loop starts (receives the window object) |
window_api |
None |
Shared WindowAPI instance; a new one is created when None |
The main window loads http://127.0.0.1:{port}, the label grid loads
?view=label, and the panel loads ?view=panel. All windows are
created with frameless=True, on_top=True, transparent=True.
On macOS, stderr is temporarily redirected to /dev/null during
pywebview startup to suppress noisy WebKit warnings, then restored
in the startup callback.
Integration¶
- Used by the
uiCLI command andui.trayto launch the native window. - The
WindowAPIinstance is shared with the FastAPI app so that REST endpoints (e.g.POST /api/window/show-label-grid) can control window visibility. - Events flow from
ActivityMonitorinui.runtimethroughEventBusto the WebSocket layer inside the webview.
taskclf.ui.window
¶
WindowAPI and WindowChild for pywebview-based floating UI.
WindowAPI is exposed to the SolidJS frontend via
window.pywebview.api. WindowChild encapsulates the
visibility / pin / timer state machine shared by the label-grid
and state-panel child windows.
WindowChild
dataclass
¶
Visibility, pinning, and delayed-hide for an anchored child window.
Source code in src/taskclf/ui/window.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | |
visibility_on(main)
¶
Show on hover (non-pinned).
Source code in src/taskclf/ui/window.py
visibility_off_deferred()
¶
Schedule hide unless pinned.
Source code in src/taskclf/ui/window.py
visibility_off()
¶
Immediate hide — clears visible, pinned, and expected_pos.
Source code in src/taskclf/ui/window.py
pin_toggle(main)
¶
Toggle pinned state.
Source code in src/taskclf/ui/window.py
timer_cancel()
¶
position_sync()
¶
drag_detected()
¶
True if the user has dragged this window away from expected position.
Source code in src/taskclf/ui/window.py
WindowAPI
dataclass
¶
Python methods exposed to JS as window.pywebview.api.<method>().
Each method returns a value that pywebview serializes as a JSON
Promise to the frontend. The Host adapter in host.ts
calls these; components never reference window.pywebview
directly.
Source code in src/taskclf/ui/window.py
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | |
dashboard_toggle()
¶
Toggle all windows. Re-show positions the pill at its default location.
Source code in src/taskclf/ui/window.py
label_grid_show()
¶
label_grid_hide()
¶
label_grid_cancel_hide()
¶
label_grid_toggle()
¶
show_transition_notification(prompt)
¶
Show a native desktop notification for a transition prompt.
Source code in src/taskclf/ui/window.py
state_panel_show()
¶
state_panel_hide()
¶
state_panel_cancel_hide()
¶
state_panel_toggle()
¶
frontend_debug_log(message)
¶
Accept debug log lines from the frontend webview.
taskclf.ui.window_run
¶
Webview lifecycle bootstrap for the taskclf floating window.
Creates all three pywebview windows (compact pill, label grid, state panel) and starts the GUI event loop. Blocks on the main thread.
window_run(port=8741, on_ready=None, window_api=None)
¶
Create and start the pywebview floating window (blocks on main thread).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
port
|
int
|
Port of the FastAPI server to load in the webview. |
8741
|
on_ready
|
Callable[..., Any] | None
|
Optional callback invoked after the GUI loop starts. Receives the window object as its first argument. |
None
|
window_api
|
WindowAPI | None
|
Shared |
None
|
Source code in src/taskclf/ui/window_run.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |