taskunity 2026.3__tar.gz → 2026.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.
Files changed (36) hide show
  1. {taskunity-2026.3/src/taskunity.egg-info → taskunity-2026.5}/PKG-INFO +1 -1
  2. {taskunity-2026.3 → taskunity-2026.5}/pyproject.toml +1 -1
  3. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/app.py +6 -0
  4. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/static/app.css +154 -32
  5. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/base.html +51 -0
  6. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/main.html +12 -34
  7. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/milestone_banner.html +1 -1
  8. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/milestone_panel.html +3 -1
  9. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/milestones.html +27 -20
  10. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/task_panel.html +2 -1
  11. {taskunity-2026.3 → taskunity-2026.5/src/taskunity.egg-info}/PKG-INFO +1 -1
  12. {taskunity-2026.3 → taskunity-2026.5}/MANIFEST.in +0 -0
  13. {taskunity-2026.3 → taskunity-2026.5}/README.md +0 -0
  14. {taskunity-2026.3 → taskunity-2026.5}/setup.cfg +0 -0
  15. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/__init__.py +0 -0
  16. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/cli.py +0 -0
  17. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/models.py +0 -0
  18. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/render.py +0 -0
  19. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/static/chart.umd.min.js +0 -0
  20. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/static/chartjs-adapter-date-fns.bundle.min.js +0 -0
  21. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/static/htmx.min.js +0 -0
  22. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/task_store.py +0 -0
  23. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/index.html +0 -0
  24. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/board.html +0 -0
  25. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/calendar.html +0 -0
  26. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/projects.html +0 -0
  27. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/task_list.html +0 -0
  28. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity/templates/partials/timeline.html +0 -0
  29. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity.egg-info/SOURCES.txt +0 -0
  30. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity.egg-info/dependency_links.txt +0 -0
  31. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity.egg-info/entry_points.txt +0 -0
  32. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity.egg-info/requires.txt +0 -0
  33. {taskunity-2026.3 → taskunity-2026.5}/src/taskunity.egg-info/top_level.txt +0 -0
  34. {taskunity-2026.3 → taskunity-2026.5}/tests/test_git_workspace_scope.py +0 -0
  35. {taskunity-2026.3 → taskunity-2026.5}/tests/test_render_jsonantt.py +0 -0
  36. {taskunity-2026.3 → taskunity-2026.5}/tests/test_workspace_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskunity
3
- Version: 2026.3
3
+ Version: 2026.5
4
4
  Summary: A local, file-backed productivity app for program/task tracking.
5
5
  Author: Taskunity Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "taskunity"
7
- version = "2026.3"
7
+ version = "2026.5"
8
8
  description = "A local, file-backed productivity app for program/task tracking."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -320,6 +320,11 @@ def create_app(workspace: str | Path = ".") -> FastAPI:
320
320
  all_tasks = load_all_tasks(workspace)
321
321
  milestones = load_all_milestones(workspace)
322
322
  tasks_by_id = {t.id: t for t in all_tasks}
323
+ panel_task_id = (selected_task.id if selected_task else (request.query_params.get("panel_task") or "").strip())
324
+ if selected_task is None and panel_task_id:
325
+ selected_task = tasks_by_id.get(panel_task_id)
326
+ if selected_task is None:
327
+ panel_task_id = ""
323
328
  milestone_rollups = {m.id: milestone_rollup(m, tasks_by_id) for m in milestones}
324
329
 
325
330
  selected_milestone = None
@@ -443,6 +448,7 @@ def create_app(workspace: str | Path = ".") -> FastAPI:
443
448
  "calendar_next_query": build_query(projects, date_from, date_to, q, "calendar", sort, milestone, show_closed, stale_days, next_month, next_year),
444
449
  "calendar_year_prev_query": build_query(projects, date_from, date_to, q, "calendar", sort, milestone, show_closed, stale_days, year_prev_month, year_prev_year),
445
450
  "calendar_year_next_query": build_query(projects, date_from, date_to, q, "calendar", sort, milestone, show_closed, stale_days, year_next_month, year_next_year),
451
+ "panel_task": panel_task_id,
446
452
  "show_closed": show_closed,
447
453
  "hidden_closed_count": hidden_closed_count,
448
454
  "toggle_closed_query": build_query(projects, date_from, date_to, q, view, sort, milestone, not show_closed, stale_days, focus_month, focus_year),
@@ -1,5 +1,7 @@
1
1
  :root {
2
2
  color-scheme: light;
3
+ --topbar-height: 4.4rem;
4
+ --controls-height: 5.7rem;
3
5
  --bg: #eef1f7;
4
6
  --bg-soft: #f7f9fc;
5
7
  --card: #ffffff;
@@ -78,6 +80,7 @@ body {
78
80
  color: var(--text);
79
81
  -webkit-font-smoothing: antialiased;
80
82
  text-rendering: optimizeLegibility;
83
+ padding-top: var(--topbar-height);
81
84
  }
82
85
  html[data-theme="dark"] body {
83
86
  background: var(--bg);
@@ -96,8 +99,10 @@ html[data-theme="dark"] body {
96
99
  background: rgba(255, 255, 255, .82);
97
100
  backdrop-filter: saturate(160%) blur(10px);
98
101
  border-bottom: 1px solid var(--line);
99
- position: sticky;
102
+ position: fixed;
100
103
  top: 0;
104
+ left: 0;
105
+ right: 0;
101
106
  z-index: 10;
102
107
  }
103
108
  html[data-theme="dark"] .topbar {
@@ -188,6 +193,7 @@ h1, h2, h3, p { margin-top: 0; }
188
193
  .theme-option.active { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
189
194
  .settings-anchor { position: relative; }
190
195
  .new-task-form { display: flex; gap: .5rem; }
196
+ .new-task-form .new-task-btn { white-space: nowrap; }
191
197
  input, select, textarea, button {
192
198
  font: inherit;
193
199
  border: 1px solid var(--line);
@@ -220,7 +226,17 @@ button:focus-visible { outline: none; box-shadow: var(--ring); }
220
226
  padding: 1.1rem 1.4rem;
221
227
  align-items: start;
222
228
  }
223
- .content { min-width: 0; }
229
+ .layout.panel-collapsed {
230
+ grid-template-columns: minmax(0, 1fr);
231
+ }
232
+ .layout.panel-collapsed .pane-resizer,
233
+ .layout.panel-collapsed .side-panel {
234
+ display: none;
235
+ }
236
+ .content {
237
+ min-width: 0;
238
+ padding-top: calc(var(--controls-height) + .35rem);
239
+ }
224
240
  .pane-resizer {
225
241
  align-self: stretch;
226
242
  width: 10px;
@@ -370,6 +386,49 @@ button:focus-visible { outline: none; box-shadow: var(--ring); }
370
386
  margin-top: .55rem;
371
387
  }
372
388
  .mini-progress div { height: 100%; background: var(--working); }
389
+ .content-toolbar {
390
+ position: fixed !important;
391
+ top: var(--topbar-height);
392
+ left: 1.4rem;
393
+ right: calc(var(--side-pane-width, 430px) + 10px + .55rem + 1.4rem);
394
+ z-index: 8;
395
+ background: var(--bg);
396
+ border-bottom: 1px solid var(--line-soft);
397
+ padding: 0 0 .28rem;
398
+ min-height: var(--controls-height);
399
+ }
400
+ .layout.panel-collapsed .content-toolbar {
401
+ right: 1.4rem;
402
+ }
403
+ .content-toolbar::before {
404
+ content: "";
405
+ position: absolute;
406
+ left: 0;
407
+ right: 0;
408
+ top: -8px;
409
+ height: 8px;
410
+ background: var(--bg);
411
+ pointer-events: none;
412
+ }
413
+ .content-toolbar .filter-bar,
414
+ .content-toolbar .view-bar {
415
+ flex-wrap: nowrap;
416
+ overflow-x: auto;
417
+ overflow-y: hidden;
418
+ scrollbar-width: thin;
419
+ -webkit-overflow-scrolling: touch;
420
+ }
421
+ .content-toolbar .filter-bar { margin-bottom: .55rem; }
422
+ .content-toolbar .view-bar { margin-bottom: .25rem; }
423
+ .content-toolbar .pills,
424
+ .content-toolbar .view-switch,
425
+ .content-toolbar .new-task-form,
426
+ .content-toolbar .git-chip,
427
+ .content-toolbar .btn-link,
428
+ .content-toolbar .filter-menu {
429
+ flex: 0 0 auto;
430
+ }
431
+ .content-toolbar .filter-bar .spacer { flex: 1 0 1.2rem; }
373
432
  .filter-bar {
374
433
  display: flex;
375
434
  flex-wrap: wrap;
@@ -895,13 +954,21 @@ button:focus-visible { outline: none; box-shadow: var(--ring); }
895
954
  .dep-id { margin-left: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .68rem; color: var(--muted); }
896
955
  .dep-empty { padding: .5rem .55rem; font-size: .82rem; }
897
956
  .side-panel {
898
- position: sticky;
957
+ position: fixed;
899
958
  top: 5rem;
900
- max-height: calc(100vh - 6rem);
959
+ right: 1.4rem;
960
+ height: calc(100vh - 6rem);
961
+ width: var(--side-pane-width, 430px);
901
962
  overflow: hidden;
902
963
  box-shadow: var(--shadow-md);
903
964
  }
904
- .panel-scroll { overflow-y: auto; max-height: calc(100vh - 6rem); padding: 0 1rem 1rem; font-size: .93rem; }
965
+ .panel-scroll {
966
+ overflow-y: auto;
967
+ height: 100%;
968
+ overscroll-behavior: contain;
969
+ padding: 0 1rem 1rem;
970
+ font-size: .93rem;
971
+ }
905
972
  .empty-panel { padding: 1.25rem 1rem; color: var(--muted); }
906
973
  .empty-panel h2 { margin: 0 0 .35rem; font-size: 1.05rem; font-weight: 650; color: var(--text); }
907
974
  .empty-panel p { margin: 0; font-size: .88rem; }
@@ -920,6 +987,14 @@ button:focus-visible { outline: none; box-shadow: var(--ring); }
920
987
  align-items: center;
921
988
  gap: .45rem;
922
989
  }
990
+ .task-collapse-btn {
991
+ width: 1.85rem;
992
+ height: 1.85rem;
993
+ padding: 0;
994
+ text-align: center;
995
+ line-height: 1;
996
+ font-size: 1rem;
997
+ }
923
998
  .task-back-btn {
924
999
  padding: .2rem .45rem;
925
1000
  border: 1px solid var(--line);
@@ -1236,19 +1311,21 @@ input[type="file"]::file-selector-button:hover {
1236
1311
  }
1237
1312
  .burndown-preview-link:hover { text-decoration: underline; }
1238
1313
  .burndown-preview-thumb {
1239
- display: inline-flex;
1314
+ display: block;
1240
1315
  border: 1px solid var(--line);
1241
1316
  border-radius: var(--radius-sm);
1242
1317
  overflow: hidden;
1243
- max-width: min(430px, calc(100vw - 44px));
1244
- max-height: 240px;
1318
+ width: 100%;
1319
+ max-width: 100%;
1320
+ max-height: 180px;
1245
1321
  background: var(--card);
1246
1322
  }
1247
1323
  .burndown-preview-thumb img {
1248
1324
  display: block;
1249
1325
  width: 100%;
1326
+ max-width: 100%;
1250
1327
  height: auto;
1251
- object-fit: cover;
1328
+ object-fit: contain;
1252
1329
  }
1253
1330
  .burndown-preview-text {
1254
1331
  margin: 0;
@@ -1279,13 +1356,34 @@ input[type="file"]::file-selector-button:hover {
1279
1356
  .pane-resizer { display: none; }
1280
1357
  .side-panel { position: static; max-height: none; }
1281
1358
  .panel-scroll { max-height: none; }
1359
+ .content { padding-top: calc(var(--controls-height) + .25rem); }
1360
+ .content-toolbar {
1361
+ top: var(--topbar-height);
1362
+ left: 1.4rem;
1363
+ right: 1.4rem;
1364
+ }
1282
1365
  .board, .summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1283
1366
  }
1284
1367
  @media (max-width: 700px) {
1285
- .topbar, .new-task-form { flex-direction: column; align-items: stretch; }
1368
+ .topbar { flex-direction: column; align-items: stretch; }
1369
+ .new-task-form { width: 100%; }
1370
+ .new-task-form .new-task-btn { width: 100%; }
1286
1371
  .board, .summary-grid { grid-template-columns: 1fr; }
1287
1372
  .timeline-row { grid-template-columns: 1fr; }
1288
1373
  .layout { padding: .5rem; }
1374
+ .content { padding-top: calc(var(--controls-height) + .6rem); }
1375
+ .content-toolbar {
1376
+ left: .5rem;
1377
+ right: .5rem;
1378
+ }
1379
+ }
1380
+
1381
+ @media (max-width: 900px) {
1382
+ .mb-actions {
1383
+ width: 100%;
1384
+ justify-content: flex-start;
1385
+ }
1386
+ .mc-stats.compact { width: 100%; }
1289
1387
  }
1290
1388
 
1291
1389
  /* --- Milestones --- */
@@ -1305,32 +1403,55 @@ input[type="file"]::file-selector-button:hover {
1305
1403
  .ms-status.done { background: color-mix(in srgb, var(--done) 14%, white); color: var(--done); border-color: color-mix(in srgb, var(--done) 35%, var(--line)); }
1306
1404
  .ms-status.planned { background: var(--bg-soft); color: var(--muted); }
1307
1405
 
1308
- .milestone-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: .8rem; }
1309
- .milestone-card {
1310
- display: flex;
1311
- flex-direction: column;
1406
+ .milestone-list {
1407
+ display: grid;
1408
+ gap: .7rem;
1409
+ }
1410
+ .milestone-list-item {
1411
+ display: grid;
1412
+ gap: .42rem;
1312
1413
  background: var(--card);
1414
+ color: var(--text);
1313
1415
  border: 1px solid var(--line);
1314
1416
  border-left: 4px solid var(--ms-color, var(--accent));
1315
- border-radius: var(--radius);
1316
- overflow: hidden;
1417
+ border-radius: var(--radius-sm);
1418
+ padding: .68rem .8rem;
1317
1419
  box-shadow: var(--shadow-sm);
1318
1420
  }
1319
- .milestone-open {
1320
- flex: 1;
1321
- display: grid;
1322
- gap: .5rem;
1421
+ .milestone-open-row {
1422
+ width: 100%;
1323
1423
  text-align: left;
1324
- background: transparent;
1325
- color: var(--text);
1326
- border: none;
1327
- border-radius: 0;
1328
- padding: .9rem 1rem;
1329
- box-shadow: none;
1330
1424
  }
1331
- .milestone-open:hover { filter: none; background: var(--bg-soft); }
1332
- .mc-head { display: flex; align-items: center; gap: .45rem; }
1333
- .mc-head strong { flex: 1; font-size: 1rem; }
1425
+ .milestone-open-row:hover {
1426
+ filter: none;
1427
+ border-color: color-mix(in srgb, var(--line) 60%, var(--accent));
1428
+ box-shadow: var(--shadow-md);
1429
+ }
1430
+ .milestone-list-head {
1431
+ display: flex;
1432
+ align-items: center;
1433
+ justify-content: space-between;
1434
+ gap: .5rem;
1435
+ flex-wrap: wrap;
1436
+ }
1437
+ .milestone-list-title {
1438
+ display: flex;
1439
+ align-items: center;
1440
+ gap: .45rem;
1441
+ min-width: 0;
1442
+ }
1443
+ .milestone-list-title strong {
1444
+ font-size: .93rem;
1445
+ min-width: 0;
1446
+ }
1447
+ .mc-stats.compact { gap: .5rem; font-size: .74rem; }
1448
+ .mc-projects.compact { gap: .25rem; }
1449
+ .mc-projects.compact .project { font-size: .72rem; padding: .1rem .42rem; }
1450
+ .clamp-1 {
1451
+ overflow: hidden;
1452
+ text-overflow: ellipsis;
1453
+ white-space: nowrap;
1454
+ }
1334
1455
  .mc-flag { color: var(--ms-color, var(--accent)); font-size: .8rem; }
1335
1456
  .mc-summary { margin: 0; color: var(--muted); font-size: .85rem; }
1336
1457
  .mc-projects { display: flex; flex-wrap: wrap; gap: .3rem; }
@@ -1343,7 +1464,6 @@ input[type="file"]::file-selector-button:hover {
1343
1464
  .mc-progress > div, .mb-progress > div { height: 100%; background: var(--accent); border-radius: 999px; }
1344
1465
  .mc-stats { display: flex; flex-wrap: wrap; gap: .6rem; font-size: .76rem; color: var(--muted); }
1345
1466
  .mc-target { margin-left: auto; }
1346
- .mc-foot { border-top: 1px solid var(--line); padding: .4rem 1rem; display: flex; justify-content: flex-end; }
1347
1467
  .new-milestone-form { margin-top: 1.1rem; border-top: 1px solid var(--line); padding-top: 1rem; display: grid; gap: .6rem; }
1348
1468
  .new-milestone-form h3 { margin: 0; font-size: .95rem; }
1349
1469
 
@@ -1360,11 +1480,13 @@ input[type="file"]::file-selector-button:hover {
1360
1480
  box-shadow: var(--shadow-sm);
1361
1481
  }
1362
1482
  .mb-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
1363
- .mb-title { display: flex; align-items: flex-start; gap: .55rem; }
1483
+ .mb-title { display: flex; align-items: flex-start; gap: .55rem; min-width: 0; flex: 1 1 18rem; }
1364
1484
  .mb-flag { color: var(--ms-color, var(--accent)); font-size: 1rem; line-height: 1.4; }
1365
1485
  .mb-title h2 { margin: 0; font-size: 1.1rem; }
1366
1486
  .mb-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-top: .25rem; font-size: .78rem; color: var(--muted); }
1367
- .mb-actions { display: flex; align-items: center; gap: .5rem; }
1487
+ .mb-actions { display: flex; align-items: center; justify-content: flex-end; gap: .5rem; flex: 1 1 16rem; flex-wrap: wrap; }
1488
+ .mb-actions .btn-link,
1489
+ .mb-actions .pill { white-space: nowrap; }
1368
1490
  .mb-rollup { display: grid; gap: .45rem; }
1369
1491
  .mb-stats { display: flex; flex-wrap: wrap; gap: .9rem; font-size: .8rem; color: var(--muted); }
1370
1492
  .mb-stats .done strong { color: var(--done); }
@@ -326,6 +326,57 @@
326
326
  });
327
327
  });
328
328
 
329
+ function getMainLayout() {
330
+ return document.getElementById('app-main');
331
+ }
332
+ function collapseTaskPanel() {
333
+ var main = getMainLayout();
334
+ if (!main) return;
335
+ main.classList.add('panel-collapsed');
336
+ }
337
+ function expandTaskPanel() {
338
+ var main = getMainLayout();
339
+ if (!main) return;
340
+ main.classList.remove('panel-collapsed');
341
+ }
342
+ document.addEventListener('click', function (e) {
343
+ var btn = e.target.closest('[data-panel-collapse]');
344
+ if (!btn) return;
345
+ collapseTaskPanel();
346
+ });
347
+ document.addEventListener('click', function (e) {
348
+ var panelTrigger = e.target.closest('[hx-target="#task-panel"]');
349
+ if (!panelTrigger) return;
350
+ expandTaskPanel();
351
+ });
352
+ document.addEventListener('pointerdown', function (e) {
353
+ var panelTrigger = e.target.closest('[hx-target="#task-panel"]');
354
+ if (!panelTrigger) return;
355
+ expandTaskPanel();
356
+ }, true);
357
+ document.body.addEventListener('htmx:beforeRequest', function (e) {
358
+ var trigger = e && e.detail ? e.detail.elt : null;
359
+ if (!trigger) return;
360
+ var panelTarget = trigger.getAttribute && trigger.getAttribute('hx-target');
361
+ if (panelTarget === '#task-panel' || trigger.closest('[hx-target="#task-panel"]')) {
362
+ expandTaskPanel();
363
+ }
364
+ });
365
+ document.body.addEventListener('htmx:beforeSwap', function (e) {
366
+ var target = e && e.detail ? e.detail.target : null;
367
+ if (!target) return;
368
+ var panel = target.id === 'task-panel' ? target : target.closest ? target.closest('#task-panel') : null;
369
+ if (panel) expandTaskPanel();
370
+ });
371
+ document.body.addEventListener('htmx:afterSwap', function (e) {
372
+ var target = e && e.detail ? e.detail.target : null;
373
+ if (!target) return;
374
+ var panel = target.id === 'task-panel' ? target : target.closest ? target.closest('#task-panel') : null;
375
+ if (!panel) return;
376
+ var hasContent = !!panel.children.length && (panel.textContent || '').trim().length > 0;
377
+ if (hasContent) expandTaskPanel();
378
+ });
379
+
329
380
  function paneBounds() {
330
381
  var max = Math.min(Math.floor(window.innerWidth * 0.62), 820);
331
382
  var min = 300;
@@ -1,24 +1,6 @@
1
- <main id="app-main" class="layout" data-resizable-layout>
1
+ <main id="app-main" class="layout{% if not selected_task and not selected_milestone %} panel-collapsed{% endif %}" data-resizable-layout>
2
2
  <section class="content">
3
- <section class="summary-grid">
4
- <div class="summary-card">
5
- <span class="label">Total tasks</span>
6
- <strong>{{ model.total }}</strong>
7
- </div>
8
- <div class="summary-card">
9
- <span class="label">Done</span>
10
- <strong>{{ model.counts.get('done', 0) }}</strong>
11
- </div>
12
- <div class="summary-card">
13
- <span class="label">Working</span>
14
- <strong>{{ model.counts.get('working', 0) }}</strong>
15
- </div>
16
- <div class="summary-card">
17
- <span class="label">Blocked</span>
18
- <strong>{{ model.counts.get('blocked', 0) }}</strong>
19
- </div>
20
- </section>
21
-
3
+ <div class="content-toolbar">
22
4
  <div class="filter-bar">
23
5
  <details class="filter-menu">
24
6
  <summary>
@@ -187,12 +169,12 @@
187
169
 
188
170
  <div class="view-bar">
189
171
  <div class="view-switch">
190
- <button class="{% if filters.view == 'list' %}active{% endif %}" hx-get="/partials/main?view=list{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Task List</button>
191
- <button class="{% if filters.view == 'board' %}active{% endif %}" hx-get="/partials/main?view=board{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Task Board</button>
192
- <button class="{% if filters.view == 'gantt' %}active{% endif %}" hx-get="/partials/main?view=gantt{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Gantt</button>
193
- <button class="{% if filters.view == 'calendar' %}active{% endif %}" hx-get="/partials/main?view=calendar{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Calendar</button>
194
- <button class="{% if filters.view == 'milestones' %}active{% endif %}" hx-get="/partials/main?view=milestones{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Milestones</button>
195
- <button class="{% if filters.view == 'projects' %}active{% endif %}" hx-get="/partials/main?view=projects{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Projects</button>
172
+ <button class="{% if filters.view == 'list' %}active{% endif %}" hx-get="/partials/main?view=list{% if filters.query %}&{{ filters.query }}{% endif %}{% if filters.panel_task %}&panel_task={{ filters.panel_task }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Task List</button>
173
+ <button class="{% if filters.view == 'board' %}active{% endif %}" hx-get="/partials/main?view=board{% if filters.query %}&{{ filters.query }}{% endif %}{% if filters.panel_task %}&panel_task={{ filters.panel_task }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Task Board</button>
174
+ <button class="{% if filters.view == 'gantt' %}active{% endif %}" hx-get="/partials/main?view=gantt{% if filters.query %}&{{ filters.query }}{% endif %}{% if filters.panel_task %}&panel_task={{ filters.panel_task }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Gantt</button>
175
+ <button class="{% if filters.view == 'calendar' %}active{% endif %}" hx-get="/partials/main?view=calendar{% if filters.query %}&{{ filters.query }}{% endif %}{% if filters.panel_task %}&panel_task={{ filters.panel_task }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Calendar</button>
176
+ <button class="{% if filters.view == 'milestones' %}active{% endif %}" hx-get="/partials/main?view=milestones{% if filters.query %}&{{ filters.query }}{% endif %}{% if filters.panel_task %}&panel_task={{ filters.panel_task }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Milestones</button>
177
+ <button class="{% if filters.view == 'projects' %}active{% endif %}" hx-get="/partials/main?view=projects{% if filters.query %}&{{ filters.query }}{% endif %}{% if filters.panel_task %}&panel_task={{ filters.panel_task }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Projects</button>
196
178
  </div>
197
179
  {% if filters.view == 'calendar' %}
198
180
  <div class="calendar-nav">
@@ -226,7 +208,7 @@
226
208
  </div>
227
209
  {% endif %}
228
210
  <form class="new-task-form" hx-post="/tasks/create" hx-target="#app-main" hx-swap="outerHTML">
229
- <input name="title" placeholder="New task title">
211
+ <input type="hidden" name="title" value="New task">
230
212
  {% for p in filters.projects %}<input type="hidden" name="f_project" value="{{ p }}">{% endfor %}
231
213
  <input type="hidden" name="f_from" value="{{ filters.date_from }}">
232
214
  <input type="hidden" name="f_to" value="{{ filters.date_to }}">
@@ -237,9 +219,10 @@
237
219
  <input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
238
220
  <input type="hidden" name="f_calendar_month" value="{{ filters.calendar_month }}">
239
221
  <input type="hidden" name="f_calendar_year" value="{{ filters.calendar_year }}">
240
- <button type="submit">Create Task</button>
222
+ <button type="submit" class="new-task-btn">New task</button>
241
223
  </form>
242
224
  </div>
225
+ </div>
243
226
 
244
227
  {% if selected_milestone and filters.view != 'milestones' %}
245
228
  {% include "partials/milestone_banner.html" %}
@@ -268,16 +251,11 @@
268
251
  title="Drag to resize details pane. Double-click to reset."
269
252
  aria-orientation="vertical"
270
253
  ></button>
271
- <aside id="task-panel" class="side-panel">
254
+ <aside id="task-panel" class="side-panel{% if not selected_task and not selected_milestone %} side-panel-empty{% endif %}">
272
255
  {% if selected_task %}
273
256
  {% include "partials/task_panel.html" %}
274
257
  {% elif selected_milestone %}
275
258
  {% include "partials/milestone_panel.html" %}
276
- {% else %}
277
- <div class="empty-panel">
278
- <h2>Select a task</h2>
279
- <p>Details will appear when a task is selected.</p>
280
- </div>
281
259
  {% endif %}
282
260
  </aside>
283
261
  </main>
@@ -109,7 +109,7 @@
109
109
  return;
110
110
  }
111
111
  if (ext === 'pdf') {
112
- hoverCard.innerHTML += '<iframe class="burndown-preview-frame" src="' + url + '#toolbar=0" title="Attachment preview"></iframe>';
112
+ hoverCard.innerHTML += '<div class="burndown-preview-body muted">PDF preview is not shown inline here. Use “Open in new tab” to view it.</div>';
113
113
  return;
114
114
  }
115
115
  if (isTextExt(ext)) {
@@ -4,7 +4,9 @@
4
4
  <span class="ms-status {{ selected_milestone.status }}">{{ selected_milestone.status }}</span>
5
5
  <h2>{{ selected_milestone.title }}</h2>
6
6
  </div>
7
- <button class="btn-link" hx-get="/partials/main?milestone={{ selected_milestone.id }}&view={{ filters.view if filters.view != 'milestones' else 'board' }}{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML" title="Filter the board to this milestone">Filter tasks</button>
7
+ <div class="task-panel-title-row">
8
+ <button type="button" class="btn-link task-collapse-btn" data-panel-collapse title="Collapse details" aria-label="Collapse details">×</button>
9
+ </div>
8
10
  </div>
9
11
 
10
12
  <form hx-post="/milestones/{{ selected_milestone.id }}/save" hx-target="#app-main" hx-swap="outerHTML" class="task-form">
@@ -5,38 +5,45 @@
5
5
  </div>
6
6
 
7
7
  {% if milestones %}
8
- <div class="milestone-cards">
8
+ <div class="milestone-list">
9
9
  {% for m in milestones %}
10
10
  {% set r = milestone_rollups.get(m.id) %}
11
- <div class="milestone-card {{ m.status }}" style="--ms-color: {{ m.color or '#3567e0' }}">
12
- <button class="milestone-open" hx-get="/partials/main?milestone={{ m.id }}&view=board{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML" title="Filter tasks to this milestone">
13
- <div class="mc-head">
11
+ <button
12
+ class="milestone-list-item milestone-open-row {{ m.status }}"
13
+ style="--ms-color: {{ m.color or '#3567e0' }}"
14
+ hx-get="/partials/main?milestone={{ m.id }}&view=board{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}"
15
+ hx-target="#app-main"
16
+ hx-swap="outerHTML"
17
+ title="Open milestone view"
18
+ >
19
+ <div class="milestone-list-head">
20
+ <div class="milestone-list-title">
14
21
  <span class="mc-flag" aria-hidden="true">◆</span>
15
22
  <strong>{{ m.title }}</strong>
16
23
  <span class="ms-status {{ m.status }}">{{ m.status }}</span>
17
24
  </div>
18
- {% if m.summary %}<p class="mc-summary">{{ m.summary }}</p>{% endif %}
19
- <div class="mc-projects">
20
- {% for proj in m.projects %}
21
- <span class="project" style="background: {{ project_colors.get(proj, '#2e6fd8') }}22; color: {{ project_colors.get(proj, '#2e6fd8') }}">{{ proj }}</span>
22
- {% else %}
23
- <span class="muted">No projects assigned</span>
24
- {% endfor %}
25
- </div>
26
- <div class="mc-progress" title="{{ r.progress }}% complete">
27
- <div style="width: {{ r.progress }}%; background: {{ m.color or '#3567e0' }}"></div>
28
- </div>
29
- <div class="mc-stats">
25
+ <div class="mc-stats compact">
30
26
  <span>{{ r.total }} task{{ '' if r.total == 1 else 's' }}</span>
31
27
  <span>{{ r.done }} done</span>
32
28
  <span>{{ r.progress }}%</span>
33
29
  {% if m.target_date %}<span class="mc-target">🎯 {{ m.target_date }}</span>{% endif %}
34
30
  </div>
35
- </button>
36
- <div class="mc-foot">
37
- <button class="btn-link" hx-get="/milestones/{{ m.id }}/panel?view=milestones{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#task-panel" hx-swap="innerHTML">Details</button>
38
31
  </div>
39
- </div>
32
+ {% if m.summary %}<p class="mc-summary clamp-1">{{ m.summary }}</p>{% endif %}
33
+ <div class="mc-projects compact">
34
+ {% for proj in m.projects[:3] %}
35
+ <span class="project" style="background: {{ project_colors.get(proj, '#2e6fd8') }}22; color: {{ project_colors.get(proj, '#2e6fd8') }}">{{ proj }}</span>
36
+ {% endfor %}
37
+ {% if m.projects|length > 3 %}
38
+ <span class="muted">+{{ m.projects|length - 3 }} more</span>
39
+ {% elif m.projects|length == 0 %}
40
+ <span class="muted">No projects assigned</span>
41
+ {% endif %}
42
+ </div>
43
+ <div class="mc-progress" title="{{ r.progress }}% complete">
44
+ <div style="width: {{ r.progress }}%; background: {{ m.color or '#3567e0' }}"></div>
45
+ </div>
46
+ </button>
40
47
  {% endfor %}
41
48
  </div>
42
49
  {% else %}
@@ -1,6 +1,7 @@
1
1
  <div class="panel-scroll">
2
2
  <div class="panel-heading sticky">
3
3
  <div class="task-panel-title-row">
4
+ <button type="button" class="btn-link task-collapse-btn" data-panel-collapse title="Collapse details" aria-label="Collapse details">×</button>
4
5
  {% if filters.milestone %}
5
6
  <button
6
7
  type="button"
@@ -313,7 +314,7 @@
313
314
  return;
314
315
  }
315
316
  if (ext === 'pdf') {
316
- hoverCard.innerHTML += '<iframe class="burndown-preview-frame" src="' + url + '#toolbar=0" title="Attachment preview"></iframe>';
317
+ hoverCard.innerHTML += '<div class="burndown-preview-body muted">PDF preview is not shown inline here. Use “Open in new tab” to view it.</div>';
317
318
  return;
318
319
  }
319
320
  if (isTextExt(ext)) {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskunity
3
- Version: 2026.3
3
+ Version: 2026.5
4
4
  Summary: A local, file-backed productivity app for program/task tracking.
5
5
  Author: Taskunity Contributors
6
6
  License: MIT
File without changes
File without changes
File without changes