feat: kanban status buttons, task back-nav, RSS UI, weather search, briefing fixes

Project view:
- Add inline status advance buttons on kanban task cards (todo→in_progress,
  in_progress→done); buttons reveal on hover, stop link navigation

Task viewer:
- Back button navigates to task's project instead of /tasks when project_id set
- Esc key navigates to project (or /tasks); blurs focused element first

Quick capture:
- Use user's configured model instead of hardcoded Config.OLLAMA_MODEL
- Remove create_project from classifier prompt (tool not offered, caused
  task-shaped inputs to silently fall through to note fallback)

Briefing scheduler:
- Fix get_event_loop() → get_running_loop() so background thread uses the
  correct hypercorn event loop (jobs were scheduling but never executing)
- Suppress bare greeting when both LLM synthesis lanes return empty

RSS feed UI (SettingsView):
- Show last-fetched age, category badge, and feed URL per row
- Category input field when adding a feed
- Refresh all button: fetches latest items, reloads list, toasts with count
- Enter key submits add-feed form; better empty-state hint with example feeds

Weather tool:
- Accept any city/region name in addition to 'home'/'work'/'all'
- Geocodes via Nominatim + fetches live from Open-Meteo for arbitrary queries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 00:42:01 -04:00
parent a9414cf949
commit a2ba90160c
9 changed files with 238 additions and 34 deletions
+21 -3
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, computed, ref, watch } from "vue";
import { onMounted, onUnmounted, computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useTasksStore } from "@/stores/tasks";
import { useNotesStore } from "@/stores/notes";
@@ -94,7 +94,22 @@ async function loadTask(id: number) {
if (bl.status === "fulfilled") backlinks.value = bl.value;
}
onMounted(() => loadTask(taskId.value));
function handleKeydown(e: KeyboardEvent) {
if (e.key !== "Escape") return;
const active = document.activeElement as HTMLElement | null;
if (active && active !== document.body) { active.blur(); return; }
if (store.currentTask?.project_id) {
router.push(`/projects/${store.currentTask.project_id}`);
} else {
router.push("/tasks");
}
}
onMounted(() => {
loadTask(taskId.value);
window.addEventListener("keydown", handleKeydown);
});
onUnmounted(() => window.removeEventListener("keydown", handleKeydown));
watch(() => route.params.id, (newId) => {
if (newId) loadTask(Number(newId));
@@ -221,7 +236,10 @@ const subTaskProgress = computed(() => {
</div>
<template v-else-if="store.currentTask">
<div class="toolbar">
<router-link to="/tasks" class="btn-back"> Tasks</router-link>
<router-link
:to="store.currentTask.project_id ? `/projects/${store.currentTask.project_id}` : '/tasks'"
class="btn-back"
>{{ store.currentTask.project_id ? "← Project" : "← Tasks" }}</router-link>
<router-link
:to="`/tasks/${store.currentTask.id}/edit`"
class="btn-edit"