Add version footer, task history UI, and note version retention policy

- App.vue: quiet version footer (fetches /api/version, 0.45 opacity)
- TaskEditorView: History button + HistoryPanel integration (mirrors NoteEditorView)
- note_versions.py: raise MAX_VERSIONS 20→50; add 5-min minimum interval gate
  to prevent autosave exhausting history slots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 16:23:56 -04:00
parent 9383f10dab
commit 2cb4e6d3b2
3 changed files with 76 additions and 3 deletions
+22 -1
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, onUnmounted, watch } from "vue";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import AppHeader from "@/components/AppHeader.vue";
import ToastNotification from "@/components/ToastNotification.vue";
@@ -8,10 +8,12 @@ import { useShortcuts } from "@/composables/useShortcuts";
import { useAuthStore } from "@/stores/auth";
import { useChatStore } from "@/stores/chat";
import { useSettingsStore } from "@/stores/settings";
import { apiGet } from "@/api/client";
useTheme();
const router = useRouter();
const appVersion = ref("dev");
const authStore = useAuthStore();
const chatStore = useChatStore();
const settingsStore = useSettingsStore();
@@ -123,6 +125,12 @@ onMounted(async () => {
if (authStore.isAuthenticated) {
startAppServices();
}
try {
const data = await apiGet<{ version: string }>("/api/version");
appVersion.value = data.version;
} catch {
// silent — version display is non-critical
}
});
watch(
@@ -150,6 +158,7 @@ onUnmounted(() => {
<div id="main-content" class="app-content">
<router-view />
</div>
<footer class="app-footer">v{{ appVersion }}</footer>
</div>
<!-- Keyboard shortcuts overlay -->
@@ -299,6 +308,18 @@ onUnmounted(() => {
overflow: hidden;
}
/* Version footer */
.app-footer {
flex-shrink: 0;
text-align: center;
padding: 0.2rem 0;
font-size: 0.68rem;
color: var(--color-text-muted);
opacity: 0.45;
user-select: none;
letter-spacing: 0.03em;
}
/* Shortcuts overlay */
.shortcuts-overlay {
position: fixed;
+28
View File
@@ -22,6 +22,7 @@ import MilestoneSelector from "@/components/MilestoneSelector.vue";
import TaskLogSection from "@/components/TaskLogSection.vue";
import InlineAssistPanel from "@/components/InlineAssistPanel.vue";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import HistoryPanel from "@/components/HistoryPanel.vue";
const route = useRoute();
const router = useRouter();
@@ -321,6 +322,7 @@ async function save() {
}
const showDeleteConfirm = ref(false);
const showHistory = ref(false);
function remove() {
if (taskId.value) {
@@ -386,6 +388,7 @@ useEditorGuards(dirty, save);
<button class="btn-save" @click="save" :disabled="saving">
{{ saving ? "Saving..." : "Save" }}
</button>
<button v-if="isEditing" class="btn-history" @click="showHistory = true">History</button>
<button v-if="isEditing" class="btn-delete" @click="remove">
Delete
</button>
@@ -654,6 +657,15 @@ useEditorGuards(dirty, save);
@confirm="confirmDelete"
@cancel="showDeleteConfirm = false"
/>
<!-- History panel -->
<HistoryPanel
v-if="showHistory && taskId"
:note-id="taskId"
:current-body="body"
@restore="(b: string, t: string[]) => { body = b; tags = t; markDirty(); }"
@close="showHistory = false"
/>
</main>
</template>
@@ -843,6 +855,22 @@ useEditorGuards(dirty, save);
font-family: inherit;
}
/* History button */
.btn-history {
padding: 0.45rem 1rem;
background: none;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.875rem;
color: var(--color-text-secondary);
font-family: inherit;
}
.btn-history:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
/* Tag suggest row inside sidebar */
.tag-suggest-row {
display: flex;