Improve Activity panel + task tracking rules

- Activity loads from tasks.json dynamically
- Added task tracking workflow in AGENTS.md
- Notes UI improvements
- Renamed recipe with date prefix
This commit is contained in:
Echo
2026-01-30 20:58:30 +00:00
parent fdabea7271
commit ea4101710f
8 changed files with 317 additions and 58 deletions

View File

@@ -292,6 +292,64 @@
margin-bottom: var(--space-5);
}
.tag-filter-header {
display: flex;
align-items: center;
gap: var(--space-2);
cursor: pointer;
user-select: none;
margin-bottom: var(--space-2);
}
.tag-filter-header svg {
width: 16px;
height: 16px;
color: var(--text-muted);
transition: transform var(--transition-fast);
}
.tag-filter.collapsed .tag-filter-header svg {
transform: rotate(-90deg);
}
.tag-pills-more {
display: none;
margin-top: var(--space-2);
}
.tag-pills-more.expanded {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
/* More tags toggle button - same style as pills */
.tag-pill.more-toggle {
background: rgba(100, 116, 139, 0.2);
border-color: rgba(100, 116, 139, 0.4);
color: var(--text-muted);
}
.tag-pill.more-toggle:hover {
background: rgba(100, 116, 139, 0.3);
}
.tag-pill.more-toggle.expanded {
background: rgba(100, 116, 139, 0.4);
}
/* Dimmed pills - tags not in visible notes */
.tag-pill.dimmed {
opacity: 0.35;
}
.tag-pill.dimmed:hover {
opacity: 0.6;
}
.filter-count {
font-size: var(--text-xs);
color: var(--text-muted);
margin-left: var(--space-1);
}
.tag-filter-label {
font-size: var(--text-xs);
color: var(--text-muted);
@@ -335,6 +393,44 @@
color: white;
}
/* Category pills (📁) - teal */
.tag-pill.category {
background: rgba(20, 184, 166, 0.2);
border-color: rgba(20, 184, 166, 0.5);
color: #14b8a6;
}
.tag-pill.category:hover {
background: rgba(20, 184, 166, 0.3);
}
.tag-pill.category.active {
background: #14b8a6;
border-color: #14b8a6;
color: white;
}
/* Domain pills (@) - purple */
.tag-pill.domain {
background: rgba(139, 92, 246, 0.2);
border-color: rgba(139, 92, 246, 0.5);
color: #8b5cf6;
}
.tag-pill.domain:hover {
background: rgba(139, 92, 246, 0.3);
}
.tag-pill.domain.active {
background: #8b5cf6;
border-color: #8b5cf6;
color: white;
}
/* Light mode adjustments */
[data-theme="light"] .tag-pill.category {
color: #0d9488;
}
[data-theme="light"] .tag-pill.domain {
color: #7c3aed;
}
.tag-pill-count {
font-size: var(--text-xs);
opacity: 0.7;
@@ -426,8 +522,8 @@
</div>
<div class="tag-filter">
<span class="tag-filter-label">Filtrează după tags:</span>
<div class="tag-pills" id="tagPills"></div>
<div class="tag-pills" id="mainPills"></div>
<div class="tag-pills-more" id="tagPills"></div>
</div>
<div id="notesContainer">
@@ -482,6 +578,7 @@
const notesCache = {};
let notesIndex = [];
let lastFilteredNotes = null;
const notesBasePath = "notes-data/";
const indexPath = notesBasePath + "index.json";
@@ -499,37 +596,107 @@
}
let selectedTags = new Set();
// Extract all tags with counts
// Extract all tags with counts (including domains and categories)
function getAllTags() {
const tagCounts = {};
notesIndex.forEach(note => {
// Category tags (📁youtube, 📁retete)
if (note.category) {
const catTag = '📁' + note.category;
tagCounts[catTag] = (tagCounts[catTag] || 0) + 1;
}
// Domain tags (@work, @health, etc.)
if (note.domains) {
note.domains.forEach(domain => {
const domainTag = '@' + domain;
tagCounts[domainTag] = (tagCounts[domainTag] || 0) + 1;
});
}
// Regular tags
note.tags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
});
// Sort by count descending
// Sort: categories first (📁), then domains (@), then by count
return Object.entries(tagCounts)
.sort((a, b) => b[1] - a[1])
.sort((a, b) => {
const aIsCat = a[0].startsWith('📁');
const bIsCat = b[0].startsWith('📁');
const aIsDomain = a[0].startsWith('@');
const bIsDomain = b[0].startsWith('@');
if (aIsCat && !bIsCat) return -1;
if (!aIsCat && bIsCat) return 1;
if (aIsDomain && !bIsDomain) return -1;
if (!aIsDomain && bIsDomain) return 1;
return b[1] - a[1];
})
.map(([tag, count]) => ({ tag, count }));
}
// Render tag pills
function renderTagPills() {
const container = document.getElementById('tagPills');
function renderTagPills(visibleNotes = null) {
const mainContainer = document.getElementById('mainPills');
const moreContainer = document.getElementById('tagPills');
const tags = getAllTags();
let html = tags.map(({ tag, count }) => `
<span class="tag-pill ${selectedTags.has(tag) ? 'active' : ''}"
onclick="toggleTag('${tag}')">
${tag} <span class="tag-pill-count">(${count})</span>
</span>
`).join('');
// Calculate which tags appear in visible notes
const visibleTags = new Set();
if (visibleNotes && visibleNotes.length > 0) {
visibleNotes.forEach(note => {
note.tags.forEach(t => visibleTags.add(t));
(note.domains || []).forEach(d => visibleTags.add('@' + d));
if (note.category) visibleTags.add('📁' + note.category);
});
}
const hasFilter = visibleNotes !== null;
if (selectedTags.size > 0) {
html += `<button class="clear-filters" onclick="clearTagFilters()">✕ Clear</button>`;
// Separate: categories + domains vs regular tags
const mainTags = tags.filter(({tag}) => tag.startsWith('📁') || tag.startsWith('@'));
const moreTags = tags.filter(({tag}) => !tag.startsWith('📁') && !tag.startsWith('@'));
// Check if more section is expanded
const isExpanded = moreContainer.classList.contains('expanded');
const activeMoreCount = [...selectedTags].filter(t => !t.startsWith('📁') && !t.startsWith('@')).length;
// Render main pills (categories + domains)
let mainHtml = mainTags.map(({ tag, count }) => {
let pillClass = 'tag-pill';
if (tag.startsWith('📁')) pillClass += ' category';
else if (tag.startsWith('@')) pillClass += ' domain';
if (selectedTags.has(tag)) pillClass += ' active';
// Dim tags not in visible notes (unless it's already selected)
if (hasFilter && !visibleTags.has(tag) && !selectedTags.has(tag)) pillClass += ' dimmed';
return `<span class="${pillClass}" onclick="toggleTag('${tag}')">
${tag} <span class="tag-pill-count">(${count})</span>
</span>`;
}).join('');
// Add "more tags" toggle button inline
if (moreTags.length > 0) {
const visibleMoreCount = moreTags.filter(({tag}) => visibleTags.has(tag)).length;
const moreLabel = activeMoreCount > 0
? `+${moreTags.length} tags (${activeMoreCount} active)`
: (hasFilter && visibleMoreCount > 0 ? `+${visibleMoreCount}/${moreTags.length} tags` : `+${moreTags.length} tags`);
mainHtml += `<span class="tag-pill more-toggle ${isExpanded ? 'expanded' : ''}" onclick="toggleMoreTags()">
${isExpanded ? '' : '+'} ${moreLabel}
</span>`;
}
container.innerHTML = html;
if (selectedTags.size > 0) {
mainHtml += `<button class="clear-filters" onclick="clearTagFilters()">✕ Clear</button>`;
}
mainContainer.innerHTML = mainHtml;
// Render more pills (regular tags)
const moreHtml = moreTags.map(({ tag, count }) => {
let pillClass = 'tag-pill';
if (selectedTags.has(tag)) pillClass += ' active';
if (hasFilter && !visibleTags.has(tag) && !selectedTags.has(tag)) pillClass += ' dimmed';
return `<span class="${pillClass}" onclick="toggleTag('${tag}')">
${tag} <span class="tag-pill-count">(${count})</span>
</span>`;
}).join('');
moreContainer.innerHTML = moreHtml;
}
// Toggle tag selection
@@ -550,17 +717,29 @@
filterNotes();
}
// Toggle more tags section
function toggleMoreTags() {
const moreContainer = document.getElementById('tagPills');
moreContainer.classList.toggle('expanded');
renderTagPills(lastFilteredNotes);
}
// Filter notes by search and tags
function filterNotes() {
const query = document.getElementById('searchInput').value.toLowerCase().trim();
let filtered = notesIndex;
// Filter by selected tags (AND logic)
// Filter by selected tags (AND logic) - includes categories and domains
if (selectedTags.size > 0) {
filtered = filtered.filter(note =>
[...selectedTags].every(tag => note.tags.includes(tag))
);
filtered = filtered.filter(note => {
const allNoteTags = [
...note.tags,
...(note.domains || []).map(d => '@' + d),
note.category ? '📁' + note.category : null
].filter(Boolean);
return [...selectedTags].every(tag => allNoteTags.includes(tag));
});
}
// Filter by search query
@@ -574,6 +753,12 @@
}
renderNotesAccordion(filtered);
// Save filtered notes for tag pills
lastFilteredNotes = filtered;
// Update tag pills to show which tags are in visible notes
renderTagPills(filtered);
}
// Group notes by date category