feat: Auto-save components, soft delete, tags, compact WCO header

- Auto-save rendered Vue components to DB on render_vue_component
- Soft delete (archive) instead of hard delete for components
- Tags support for component categorization
- Gallery limited to 10 most recent items per section
- Upsert with ON CONFLICT for component saves
- PUT endpoint for partial component updates
- Collapsible toolbar with animated toggle button
- Window Controls Overlay support for PWA titlebar
- Compact header mode (32px) with hidden dot toggle
- Dynamic theme-color meta sync for Windows titlebar
This commit is contained in:
2026-02-15 02:54:27 -06:00
parent 8154bac63f
commit 9f9f335439
10 changed files with 401 additions and 84 deletions

View File

@@ -38,13 +38,15 @@ const editIcon = ref('')
const editOrder = ref(99)
const filteredCanvases = computed(() => {
const list = showArchived.value ? store.canvases : store.activeCanvasesList
if (!searchQuery.value) return list
const q = searchQuery.value.toLowerCase()
return list.filter(c =>
c.name.toLowerCase().includes(q) ||
(c.description && c.description.toLowerCase().includes(q))
)
let list = showArchived.value ? store.canvases : store.activeCanvasesList
if (searchQuery.value) {
const q = searchQuery.value.toLowerCase()
list = list.filter(c =>
c.name.toLowerCase().includes(q) ||
(c.description && c.description.toLowerCase().includes(q))
)
}
return list.slice(0, 10)
})
const filteredSnapshots = computed(() => {
@@ -168,7 +170,7 @@ async function loadComponent(comp: VueComponentDefinition) {
async function fetchComponents() {
try {
savedComponents.value = await componentsApi.getAll()
savedComponents.value = await componentsApi.getAll({ limit: 10 })
} catch {
savedComponents.value = []
}
@@ -432,10 +434,14 @@ onMounted(() => {
<div class="card-content">
<div class="card-name">{{ comp.name }}</div>
<div class="card-desc card-id">{{ comp.id }}</div>
<div v-if="comp.tags?.length" class="card-tags">
<span v-for="tag in comp.tags" :key="tag" class="tag-pill">{{ tag }}</span>
</div>
</div>
<div class="card-meta">
<span class="card-badge component">componente</span>
<span v-if="comp.status === 'archived'" class="card-badge archived-badge">Archivado</span>
</div>
<!-- Loading overlay -->
@@ -955,4 +961,20 @@ onMounted(() => {
.new-btn.cancel:hover {
background: var(--border-color);
}
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-top: 0.375rem;
}
.tag-pill {
padding: 0.0625rem 0.375rem;
background: rgba(99, 102, 241, 0.1);
color: #818cf8;
border-radius: 999px;
font-size: 0.625rem;
font-weight: 500;
}
</style>

View File

@@ -4,6 +4,10 @@ import { RouterLink, useRoute } from 'vue-router'
import { useCanvasStore } from '../stores/canvas'
import { useProjectCanvasStore } from '../stores/projectCanvas'
defineProps<{
collapsed?: boolean
}>()
const route = useRoute()
const canvasStore = useCanvasStore()
const projectCanvasStore = useProjectCanvasStore()
@@ -26,7 +30,7 @@ onMounted(() => {
</script>
<template>
<aside class="toolbar">
<aside class="toolbar" :class="{ collapsed }">
<!-- Navegacion principal -->
<div class="toolbar-section nav-section">
<RouterLink to="/" class="toolbar-btn" :class="{ active: route.path === '/' }" title="Home">
@@ -121,6 +125,12 @@ onMounted(() => {
<path d="M18 9a9 9 0 0 1-9 9"/>
</svg>
</RouterLink>
<RouterLink to="/agents" class="toolbar-btn" :class="{ active: route.path === '/agents' }" title="Agents">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L9.5 7.5 4 8.5l4 4-1 5.5L12 15l5 3-1-5.5 4-4-5.5-1z"/>
</svg>
</RouterLink>
</div>
<div class="toolbar-divider"></div>
@@ -154,6 +164,19 @@ onMounted(() => {
display: flex;
flex-direction: column;
gap: 0.5rem;
overflow: hidden;
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.2s ease;
flex-shrink: 0;
}
.toolbar.collapsed {
width: 0;
padding: 0;
border-right-color: transparent;
opacity: 0;
pointer-events: none;
}
.toolbar-section {