td-task 0.2.4__tar.gz → 0.2.5__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.
@@ -0,0 +1,50 @@
1
+ name: Publish homepage
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [main]
7
+ paths:
8
+ - index.html
9
+ - website/**
10
+ - assets/demo.gif
11
+ - .github/workflows/pages.yml
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: github-pages
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ deploy:
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ pages: write
25
+ id-token: write
26
+ environment:
27
+ name: github-pages
28
+ url: ${{ steps.deployment.outputs.page_url }}
29
+ steps:
30
+ - name: Checkout
31
+ uses: actions/checkout@v6
32
+
33
+ - name: Configure Pages
34
+ uses: actions/configure-pages@v5
35
+
36
+ - name: Stage public files
37
+ run: |
38
+ mkdir -p _site/assets
39
+ cp index.html _site/
40
+ cp -R website _site/website
41
+ cp assets/demo.gif _site/assets/
42
+
43
+ - name: Upload site
44
+ uses: actions/upload-pages-artifact@v4
45
+ with:
46
+ path: _site
47
+
48
+ - name: Deploy
49
+ id: deployment
50
+ uses: actions/deploy-pages@v4
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: td-task
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Fast TUI and CLI manager for tasks and Markdown notes
5
5
  Requires-Python: >=3.14
6
6
  Requires-Dist: cryptography>=42.0.0
@@ -110,6 +110,9 @@ td notes delete 12 --json
110
110
 
111
111
  ## TUI Keybindings
112
112
 
113
+ The TUI redraws automatically when you resize the terminal, keeping the current
114
+ selection and unsaved edits. Help supports arrow-key and PageUp/PageDown scrolling.
115
+
113
116
  ### Tasks
114
117
 
115
118
  | Key | Action |
@@ -121,6 +124,7 @@ td notes delete 12 --json
121
124
  | `d` | Delete selected task |
122
125
  | `Space` | Toggle done / active |
123
126
  | `s` | Star task (pin to top, bold yellow) |
127
+ | `S` | Cycle task sort: A-Z / Z-A / original order |
124
128
  | `c` | Archive all completed tasks |
125
129
  | `y` | Copy task list to clipboard |
126
130
  | `f` | Filter notes by title in current list; submit empty query to clear |
@@ -99,6 +99,9 @@ td notes delete 12 --json
99
99
 
100
100
  ## TUI Keybindings
101
101
 
102
+ The TUI redraws automatically when you resize the terminal, keeping the current
103
+ selection and unsaved edits. Help supports arrow-key and PageUp/PageDown scrolling.
104
+
102
105
  ### Tasks
103
106
 
104
107
  | Key | Action |
@@ -110,6 +113,7 @@ td notes delete 12 --json
110
113
  | `d` | Delete selected task |
111
114
  | `Space` | Toggle done / active |
112
115
  | `s` | Star task (pin to top, bold yellow) |
116
+ | `S` | Cycle task sort: A-Z / Z-A / original order |
113
117
  | `c` | Archive all completed tasks |
114
118
  | `y` | Copy task list to clipboard |
115
119
  | `f` | Filter notes by title in current list; submit empty query to clear |
@@ -0,0 +1,78 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="theme-color" content="#141414">
7
+ <meta name="description" content="td: tasks and Markdown notes in your terminal.">
8
+ <meta property="og:type" content="website">
9
+ <meta property="og:title" content="td">
10
+ <meta property="og:description" content="Tasks and Markdown notes in your terminal.">
11
+ <title>td — terminal tasks and notes</title>
12
+ <link rel="icon" href="website/favicon.svg" type="image/svg+xml">
13
+ <link rel="stylesheet" href="website/style.css">
14
+ <script src="website/main.js" defer></script>
15
+ </head>
16
+ <body>
17
+ <a class="skip-link" href="#main">Skip to content</a>
18
+ <div class="page">
19
+ <header>
20
+ <p>td — tasks and Markdown notes in your terminal.</p>
21
+ </header>
22
+
23
+ <main id="main">
24
+ <ul class="features" aria-label="Features">
25
+ <li>Multiple lists and keyboard navigation.</li>
26
+ <li>Markdown notes with a built-in editor.</li>
27
+ <li>Optional database and list encryption.</li>
28
+ <li>JSON export and a scriptable CLI.</li>
29
+ </ul>
30
+ <figure>
31
+ <a href="assets/demo.gif" aria-label="Open the demo GIF at full size">
32
+ <img src="assets/demo.gif" width="680" height="360" alt="Demo of td: adding tasks, starring priorities, and switching lists with the keyboard." fetchpriority="high">
33
+ </a>
34
+ </figure>
35
+
36
+ <section id="install" aria-label="Installation and usage">
37
+ <dl class="commands">
38
+ <div>
39
+ <dt>install (uv)</dt>
40
+ <dd>
41
+ <code>uv tool install td-task</code>
42
+ <button type="button" data-copy="uv tool install td-task" aria-label="Copy uv install command" hidden>[copy]</button>
43
+ </dd>
44
+ </div>
45
+ <div>
46
+ <dt>or pip</dt>
47
+ <dd>
48
+ <code>pip install td-task</code>
49
+ <button type="button" data-copy="pip install td-task" aria-label="Copy pip install command" hidden>[copy]</button>
50
+ </dd>
51
+ </div>
52
+ <div>
53
+ <dt>run</dt>
54
+ <dd>
55
+ <code>td</code>
56
+ <button type="button" data-copy="td" aria-label="Copy td launch command" hidden>[copy]</button>
57
+ </dd>
58
+ </div>
59
+ <div>
60
+ <dt>or uvx</dt>
61
+ <dd>
62
+ <code>uvx td-task</code>
63
+ <button type="button" data-copy="uvx td-task" aria-label="Copy run without installing command" hidden>[copy]</button>
64
+ </dd>
65
+ </div>
66
+ </dl>
67
+ <p class="requirement">Python 3.14+. <a href="https://docs.astral.sh/uv/getting-started/installation/">uv docs</a>.</p>
68
+ <p class="copy-status" role="status" aria-live="polite"></p>
69
+ </section>
70
+ </main>
71
+
72
+ <footer>
73
+ <a href="https://github.com/lucaspon/td">github.com/lucaspon/td</a>
74
+ <a href="https://twitter.com/lucasponn">@lucasponn</a>
75
+ </footer>
76
+ </div>
77
+ </body>
78
+ </html>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "td-task"
3
- version = "0.2.4"
3
+ version = "0.2.5"
4
4
  description = "Fast TUI and CLI manager for tasks and Markdown notes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.5] - 2026-11-04
4
+ - Redraw automatically when terminal dimensions change, without waiting for a keypress.
5
+ - Preserve selection, unsaved edits, note cursor, and confirmations while resizing.
6
+ - Fit frames to smaller windows and reflow note content when the terminal grows.
7
+ - Scroll help with arrow keys and PageUp/PageDown.
8
+
3
9
  ## [0.2.4] - 2026-08-05
4
10
  - Add live, case-insensitive note-title filtering with `f` in the current list.
5
11
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import sys
5
+ import time
5
6
 
6
7
  IS_WINDOWS = sys.platform == "win32"
7
8
 
@@ -38,6 +39,30 @@ KEY_CTRL_BACKSPACE = "\x17"
38
39
  KEY_CMD_BACKSPACE = "\x15"
39
40
  KEY_CTRL_P = "\x10"
40
41
  KEY_CTRL_S = "\x13"
42
+ KEY_RESIZE = "<resize>"
43
+
44
+ _last_size: os.terminal_size | None = None
45
+ _RESIZE_POLL_INTERVAL = 0.05
46
+
47
+
48
+ def get_terminal_size() -> os.terminal_size:
49
+ """Read live terminal dimensions, without stale COLUMNS/LINES overrides."""
50
+ for stream in (sys.stdout, sys.stdin):
51
+ try:
52
+ size = os.get_terminal_size(stream.fileno())
53
+ except (AttributeError, OSError, ValueError):
54
+ continue
55
+ if size.columns > 0 and size.lines > 0:
56
+ return size
57
+ return os.terminal_size((80, 24))
58
+
59
+
60
+ def _terminal_resized() -> bool:
61
+ global _last_size
62
+ size = get_terminal_size()
63
+ changed = _last_size is not None and size != _last_size
64
+ _last_size = size
65
+ return changed
41
66
 
42
67
 
43
68
  if IS_WINDOWS:
@@ -86,6 +111,8 @@ if IS_WINDOWS:
86
111
  class _RawContext:
87
112
  """Context manager for raw terminal mode on Windows."""
88
113
  def __enter__(self):
114
+ global _last_size
115
+ _last_size = get_terminal_size()
89
116
  return self
90
117
 
91
118
  def __exit__(self, *args):
@@ -101,6 +128,12 @@ if IS_WINDOWS:
101
128
  we translate to ANSI sequences. A literal Esc (or a VT-input terminal
102
129
  sending real escape sequences) is handled as a fallback.
103
130
  """
131
+ while True:
132
+ if _terminal_resized():
133
+ return KEY_RESIZE
134
+ if msvcrt.kbhit():
135
+ break
136
+ time.sleep(_RESIZE_POLL_INTERVAL)
104
137
  ch = msvcrt.getwch()
105
138
 
106
139
  # Native special-key protocol.
@@ -131,9 +164,15 @@ if IS_WINDOWS:
131
164
  return ch
132
165
 
133
166
  else:
167
+ import codecs
168
+ import select
134
169
  import tty
135
170
  import termios
136
171
 
172
+ _input_decoder = codecs.getincrementaldecoder(
173
+ sys.stdin.encoding or "utf-8"
174
+ )(errors="replace")
175
+
137
176
  class _RawContext:
138
177
  """Context manager for raw terminal mode on Unix."""
139
178
  def __init__(self):
@@ -141,7 +180,12 @@ else:
141
180
  self.old_settings = termios.tcgetattr(self.fd)
142
181
 
143
182
  def __enter__(self):
183
+ global _last_size, _input_decoder
144
184
  tty.setcbreak(self.fd)
185
+ _last_size = get_terminal_size()
186
+ _input_decoder = codecs.getincrementaldecoder(
187
+ sys.stdin.encoding or "utf-8"
188
+ )(errors="replace")
145
189
  return self
146
190
 
147
191
  def __exit__(self, *args):
@@ -152,30 +196,38 @@ else:
152
196
 
153
197
  ESC = "\x1b"
154
198
 
199
+ def _read_char(fd: int, timeout: float | None = None) -> str:
200
+ """Wait for input or a resize; keep pasted UTF-8 input out of TextIO buffers."""
201
+ deadline = time.monotonic() + timeout if timeout is not None else None
202
+ while True:
203
+ if deadline is None and _terminal_resized():
204
+ return KEY_RESIZE
205
+ wait = _RESIZE_POLL_INTERVAL
206
+ if deadline is not None:
207
+ wait = min(wait, max(0, deadline - time.monotonic()))
208
+ if select.select([fd], [], [], wait)[0]:
209
+ data = os.read(fd, 1)
210
+ if not data:
211
+ return ""
212
+ char = _input_decoder.decode(data)
213
+ if char:
214
+ return char
215
+ if deadline is not None and time.monotonic() >= deadline:
216
+ return ""
217
+
155
218
  def read_key() -> str:
156
- """Read a single keypress on Unix, handling escape sequences."""
219
+ """Read a key or resize event without waiting for the next keypress."""
157
220
  fd = sys.stdin.fileno()
158
- ch = sys.stdin.read(1)
221
+ ch = _read_char(fd)
159
222
  if ch == ESC:
160
223
  seq = ch
161
- try:
162
- old = termios.tcgetattr(fd)
163
- new = termios.tcgetattr(fd)
164
- new[3] = new[3] & ~(termios.ICANON | termios.ECHO)
165
- new[6][termios.VMIN] = 0
166
- new[6][termios.VTIME] = 1
167
- termios.tcsetattr(fd, termios.TCSANOW, new)
168
- while True:
169
- c = sys.stdin.read(1)
170
- if c:
171
- seq += c
172
- if c.isalpha() or c == "~":
173
- break
174
- else:
175
- break
176
- termios.tcsetattr(fd, termios.TCSANOW, old)
177
- except Exception:
178
- pass
224
+ while True:
225
+ char = _read_char(fd, timeout=0.1)
226
+ if not char:
227
+ break
228
+ seq += char
229
+ if char.isalpha() or char == "~":
230
+ break
179
231
  return seq
180
232
  return ch
181
233