Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ jobs:
sudo apt-get update
sudo apt-get install -y jq qt6-declarative-dev

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Checkout Omarchy shell (qmllint import path)
uses: actions/checkout@v5
with:
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ All notable changes to **Taskwarrior Time** are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- IPC action `newTask` opens the Tasks view and focuses the composer for configurable Hyprland keybindings

## [1.0.2] - 2026-09-29

### Fixed
Expand Down Expand Up @@ -65,6 +71,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
версии следуют [Semantic Versioning](https://semver.org/lang/ru/).

## [Не выпущено]

### Добавлено

- IPC-действие `newTask` открывает вкладку «Задачи» и фокусирует композер для настройки горячих клавиш Hyprland

## [1.0.2] - 2026-09-29

### Исправлено
Expand Down
12 changes: 12 additions & 0 deletions Panel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Quickshell.Io
import qs.Commons
import qs.Ui
import "Model.js" as Model
import "ShortcutActions.js" as ShortcutActions
import "i18n.js" as I18n

Panel {
Expand Down Expand Up @@ -881,6 +882,16 @@ Panel {
refresh()
}

function focusNewTaskComposer() {
if (addField) addField.forceActiveFocus()
}

function openNewTaskFromHotkey() {
ShortcutActions.openNewTask(root, function(callback) {
Qt.callLater(callback)
})
}

function close() {
confirmDelete.opened = false
confirmClear.opened = false
Expand Down Expand Up @@ -4508,6 +4519,7 @@ Panel {
function open(): void { root.openFromHotkey() }
function close(): void { root.close() }
function toggle(): void { root.toggle() }
function newTask(): void { root.openNewTaskFromHotkey() }
function about(): void {
root.openFromHotkey()
root.viewMode = "about"
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Anything you do here is stored in your normal Taskwarrior / Timewarrior data. Yo
- **Projects** view: browse projects, rename, clear project from tasks
- **Unsaved changes** dialog when closing a dirty editor (Save / Keep editing / Discard)
- Uniform **hotkeys**: `Ctrl+Enter` commit, `Esc` cancel, `Ctrl+Delete` destroy
- **Configurable Hyprland shortcut** to open the new-task composer
- **i18n**: English and Russian UI (system language, or pick one in About)
- **About** tab with version, developer info, What’s new / Marketplace / Report a problem / GitHub, language switch, and debug logging

Expand Down Expand Up @@ -145,6 +146,16 @@ This disables the widget and deletes the git checkout under `~/.config/omarchy/p

![About & debug](docs/screenshots/en/03-about.png)

### Keyboard shortcut (Hyprland)

The plugin exposes a `newTask` action through Quickshell IPC. Omarchy uses Lua for Hyprland bindings; add this to `~/.config/hypr/bindings.lua` (change the key combination if needed):

```lua
o.bind("SUPER + SHIFT + T", "New task", "qs ipc --path /usr/share/omarchy/shell call taskwarrior-time newTask")
```

Replace `/usr/share/omarchy/shell` if your Omarchy shell lives elsewhere. Omarchy normally reloads Hyprland config on save; run `hyprctl reload` if needed. The shortcut opens the Tasks view and focuses the new-task title field; it does not create a task until you save one.

## Debug logging

When something misbehaves, turn on debug logging from the **About** tab.
Expand Down Expand Up @@ -180,11 +191,11 @@ Please do **not** paste secrets, tokens, or private task content.

## Development checks

From a clone of this repository:
From a clone of this repository (Node.js 22+ required for behavior tests):

```bash
./scripts/enable-git-hooks # once per clone — enables pre-push checks
./scripts/check # manifest validate + qmllint
./scripts/check # manifest validate + behavior tests + qmllint
```

`git push` runs the same checks via `.githooks/pre-push` and aborts if they fail. GitHub Actions runs `./scripts/check` on push and pull requests.
Expand Down
15 changes: 13 additions & 2 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- Вкладка **Проекты**: просмотр, переименование, очистка проекта у задач
- Диалог **несохранённых изменений** при закрытии черновика (Сохранить / Продолжить / Отменить)
- Единые **горячие клавиши**: `Ctrl+Enter` — сохранить/добавить, `Esc` — отмена, `Ctrl+Delete` — удалить/сбросить
- **Настраиваемая горячая клавиша Hyprland** для открытия формы новой задачи
- **Локализация**: английский и русский (системный язык или выбор на вкладке «О плагине»)
- Вкладка **О плагине**: версия, разработчик, Что нового / Маркетплейс / Сообщить о проблеме / GitHub, язык интерфейса и отладочный лог

Expand Down Expand Up @@ -145,6 +146,16 @@ omarchy plugin remove taskwarrior-time

![О плагине и отладка](docs/screenshots/ru/03-about.png)

### Горячая клавиша (Hyprland)

Плагин предоставляет действие `newTask` через Quickshell IPC. В Omarchy биндинги Hyprland задаются на Lua; добавьте это в `~/.config/hypr/bindings.lua` (при необходимости замените сочетание):

```lua
o.bind("SUPER + SHIFT + T", "New task", "qs ipc --path /usr/share/omarchy/shell call taskwarrior-time newTask")
```

Если Omarchy shell установлен в другом месте, замените `/usr/share/omarchy/shell`. Конфигурация Hyprland обычно перезагружается при сохранении; если сочетание не появилось, выполните `hyprctl reload`. Оно открывает вкладку «Задачи» и фокусирует поле названия; задача создаётся только после сохранения.

## Отладочный лог

Если что-то ведёт себя странно, включите отладку на вкладке **О плагине**.
Expand Down Expand Up @@ -180,11 +191,11 @@ omarchy plugin remove taskwarrior-time

## Проверки при разработке

В клоне этого репозитория:
В клоне этого репозитория (для поведенческих тестов нужен Node.js 22+):

```bash
./scripts/enable-git-hooks # один раз на клон — включает pre-push
./scripts/check # validate манифеста + qmllint
./scripts/check # validate манифеста + поведенческие тесты + qmllint
```

`git push` прогоняет те же проверки через `.githooks/pre-push` и отменяет push при ошибке. GitHub Actions запускает `./scripts/check` на push и pull request.
Expand Down
7 changes: 7 additions & 0 deletions ShortcutActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function openNewTask(panel, defer) {
panel.viewMode = "tasks"
panel.openFromHotkey()
defer(function() {
panel.focusNewTaskComposer()
})
}
9 changes: 9 additions & 0 deletions scripts/check
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ else
fi
fi

# --- shortcut behavior tests ------------------------------------------------
section "shortcut behavior tests"
if command -v node >/dev/null 2>&1; then
node --test tests/shortcut-actions.test.js || FAILED=1
else
echo "shortcut behavior tests: node is required" >&2
FAILED=1
fi

# --- 2) qmllint -------------------------------------------------------------
section "qmllint"

Expand Down
54 changes: 54 additions & 0 deletions tests/shortcut-actions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const vm = require("node:vm");

const root = path.join(__dirname, "..");
const context = {};
vm.createContext(context);
vm.runInContext(fs.readFileSync(path.join(root, "ShortcutActions.js"), "utf8"), context);

test("newTask IPC handler connects to the deferred focus path", () => {
const panel = fs.readFileSync(path.join(root, "Panel.qml"), "utf8");
assert.match(panel, /function newTask\(\): void \{ root\.openNewTaskFromHotkey\(\) \}/);
assert.match(panel, /ShortcutActions\.openNewTask\(root, function\(callback\) \{\s*Qt\.callLater\(callback\)/);
});

test("composer focus retains the unsaved-editor guard", () => {
const panel = fs.readFileSync(path.join(root, "Panel.qml"), "utf8");
const fieldStart = panel.indexOf("id: addField");
const buttonStart = panel.indexOf("id: addBtnWrap", fieldStart);
assert.notEqual(fieldStart, -1);
assert.notEqual(buttonStart, -1);
const focusHandler = panel.slice(fieldStart, buttonStart);

assert.match(panel, /function focusNewTaskComposer\(\) \{\s*if \(addField\) addField\.forceActiveFocus\(\)\s*\}/);
assert.match(focusHandler, /root\.requestCloseEditor\(\{ type: "dismiss", thenCompose: true \}\)/);
assert.match(focusHandler, /if \(root\.editDirty\) \{\s*[\s\S]*?return\s*\}/);
});

test("new-task action opens Tasks before deferring composer focus", () => {
const events = [];
let focusCallback;
let createdTasks = 0;
const panel = {
viewMode: "about",
openFromHotkey() { events.push("open"); },
focusNewTaskComposer() { events.push("focus"); },
addTask() { createdTasks++; },
};

context.openNewTask(panel, (callback) => {
events.push("defer");
focusCallback = callback;
});

assert.equal(panel.viewMode, "tasks");
assert.deepEqual(events, ["open", "defer"]);
assert.equal(createdTasks, 0);

focusCallback();
assert.deepEqual(events, ["open", "defer", "focus"]);
assert.equal(createdTasks, 0);
});