chore: Various improvements and CI setup

- Add Jenkinsfile for CI/CD pipeline
- Fix keyboard shortcut '?' handling for help dialog
- Update note form and connections components
- Add work mode toggle improvements
- Update navigation history and usage tracking
- Improve validators
- Add session summaries
This commit is contained in:
2026-03-23 22:25:36 -03:00
parent e0433f8e57
commit ece8163d15
12 changed files with 976 additions and 36 deletions

View File

@@ -98,6 +98,16 @@ function ConnectionGroup({
)
}
// Deduplicate notes by id, keeping first occurrence
function deduplicateById<T extends { id: string }>(items: T[]): T[] {
const seen = new Set<string>()
return items.filter(item => {
if (seen.has(item.id)) return false
seen.add(item.id)
return true
})
}
export function NoteConnections({
noteId,
backlinks,
@@ -123,6 +133,13 @@ export function NoteConnections({
const hasAnyConnections =
backlinks.length > 0 || outgoingLinks.length > 0 || relatedNotes.length > 0 || coUsedNotes.length > 0
// Deduplicate all lists to prevent React key warnings
const uniqueBacklinks = deduplicateById(backlinks.map((bl) => ({ id: bl.sourceNote.id, title: bl.sourceNote.title, type: bl.sourceNote.type })))
const uniqueOutgoing = deduplicateById(outgoingLinks.map((ol) => ({ id: ol.sourceNote.id, title: ol.sourceNote.title, type: ol.sourceNote.type })))
const uniqueRelated = deduplicateById(relatedNotes.map((rn) => ({ id: rn.id, title: rn.title, type: rn.type })))
const uniqueCoUsed = deduplicateById(coUsedNotes.map((cu) => ({ id: cu.noteId, title: cu.title, type: cu.type })))
const uniqueHistory = deduplicateById(navigationHistory.slice(0, 5).map((entry) => ({ id: entry.noteId, title: entry.title, type: entry.type })))
const toggleCollapsed = (key: string) => {
setCollapsed((prev) => ({ ...prev, [key]: !prev[key] }))
}
@@ -144,11 +161,7 @@ export function NoteConnections({
<ConnectionGroup
title="Enlaces entrantes"
icon={ExternalLink}
notes={backlinks.map((bl) => ({
id: bl.sourceNote.id,
title: bl.sourceNote.title,
type: bl.sourceNote.type,
}))}
notes={uniqueBacklinks}
emptyMessage="Ningún otro documento enlaza a esta nota"
isCollapsed={collapsed['backlinks']}
onToggle={() => toggleCollapsed('backlinks')}
@@ -158,11 +171,7 @@ export function NoteConnections({
<ConnectionGroup
title="Enlaces salientes"
icon={ArrowRight}
notes={outgoingLinks.map((ol) => ({
id: ol.sourceNote.id,
title: ol.sourceNote.title,
type: ol.sourceNote.type,
}))}
notes={uniqueOutgoing}
emptyMessage="Esta nota no enlaza a ningún otro documento"
isCollapsed={collapsed['outgoing']}
onToggle={() => toggleCollapsed('outgoing')}
@@ -172,11 +181,7 @@ export function NoteConnections({
<ConnectionGroup
title="Relacionadas"
icon={RefreshCw}
notes={relatedNotes.map((rn) => ({
id: rn.id,
title: rn.title,
type: rn.type,
}))}
notes={uniqueRelated}
emptyMessage="No hay notas relacionadas"
isCollapsed={collapsed['related']}
onToggle={() => toggleCollapsed('related')}
@@ -186,11 +191,7 @@ export function NoteConnections({
<ConnectionGroup
title="Co-usadas"
icon={Users}
notes={coUsedNotes.map((cu) => ({
id: cu.noteId,
title: cu.title,
type: cu.type,
}))}
notes={uniqueCoUsed}
emptyMessage="No hay notas co-usadas"
isCollapsed={collapsed['coused']}
onToggle={() => toggleCollapsed('coused')}
@@ -214,15 +215,11 @@ export function NoteConnections({
)}
{/* Navigation history */}
{navigationHistory.length > 0 && (
{uniqueHistory.length > 0 && (
<ConnectionGroup
title="Vista recientemente"
icon={Clock}
notes={navigationHistory.slice(0, 5).map((entry) => ({
id: entry.noteId,
title: entry.title,
type: entry.type,
}))}
notes={uniqueHistory}
emptyMessage="No hay historial de navegación"
isCollapsed={collapsed['history']}
onToggle={() => toggleCollapsed('history')}