Initial commit: note-taking/task-tracking app with LLM integration scaffold

Vue 3 + TypeScript frontend with Pinia stores, markdown rendering (marked + DOMPurify),
wikilink/tag linkification, and autocomplete. Quart async backend with SQLAlchemy 2.0,
PostgreSQL ARRAY columns, task-note companion linking, backlinks, and note-to-task
conversion. Docker Compose setup with PostgreSQL 16 and Ollama.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 23:35:44 -05:00
commit 22a3a3c1d1
71 changed files with 7173 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
<script setup lang="ts">
import type { Task, TaskStatus } from "@/types/task";
import StatusBadge from "@/components/StatusBadge.vue";
import PriorityBadge from "@/components/PriorityBadge.vue";
import TagPill from "@/components/TagPill.vue";
import { relativeTime } from "@/composables/useRelativeTime";
import { renderPreview } from "@/utils/markdown";
const props = defineProps<{ task: Task }>();
const emit = defineEmits<{
"tag-click": [tag: string];
"status-toggle": [id: number, status: TaskStatus];
}>();
const statusCycle: Record<TaskStatus, TaskStatus> = {
todo: "in_progress",
in_progress: "done",
done: "todo",
};
function cycleStatus() {
emit("status-toggle", props.task.id, statusCycle[props.task.status]);
}
function isOverdue(): boolean {
if (!props.task.due_date || props.task.status === "done") return false;
return new Date(props.task.due_date) < new Date(new Date().toDateString());
}
</script>
<template>
<router-link :to="`/tasks/${task.id}`" class="task-card">
<div class="task-top">
<StatusBadge
:status="task.status"
clickable
@click.prevent.stop="cycleStatus"
/>
<PriorityBadge :priority="task.priority" />
<h3 class="task-title">{{ task.title || "Untitled" }}</h3>
</div>
<div v-if="task.description" class="task-preview prose" v-html="renderPreview(task.description)"></div>
<div class="task-meta">
<span v-if="task.due_date" :class="['due-date', { overdue: isOverdue() }]">
Due: {{ task.due_date }}
</span>
<TagPill
v-for="tag in task.tags"
:key="tag"
:tag="tag"
@click.stop="emit('tag-click', tag)"
/>
<span class="timestamp">{{ relativeTime(task.updated_at) }}</span>
</div>
</router-link>
</template>
<style scoped>
.task-card {
display: block;
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 6px;
text-decoration: none;
color: inherit;
background: var(--color-bg-card);
transition: box-shadow 0.15s;
}
.task-card:hover {
box-shadow: 0 2px 8px var(--color-shadow);
}
.task-top {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.task-title {
margin: 0;
font-size: 1.1rem;
}
.task-preview {
margin: 0 0 0.5rem;
color: var(--color-text-secondary);
font-size: 0.9rem;
max-height: 7.5em;
overflow: hidden;
}
.task-meta {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.due-date {
font-size: 0.8rem;
color: var(--color-text-secondary);
}
.due-date.overdue {
color: var(--color-overdue);
font-weight: 600;
}
.timestamp {
margin-left: auto;
font-size: 0.75rem;
color: var(--color-text-muted);
}
</style>