claude-home 1.5.36 → 1.5.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/package.json +1 -1
- package/public/index.html +272 -32
- package/server.js +4 -3
package/README.md
CHANGED
|
@@ -29,6 +29,14 @@ Claude can save notes directly from any session — just say "save this as a not
|
|
|
29
29
|
|
|
30
30
|
Direct links (`#/note/filename`) let you open a specific note instantly.
|
|
31
31
|
|
|
32
|
+
**Note linking** — write `#slug` inside a note to link it to another, creating a network of connected notes. Backlinks appear automatically at the bottom of each note. An autocomplete dropdown appears as you type `#` in the editor.
|
|
33
|
+
|
|
34
|
+
**Related notes** — notes with similar vocabulary are suggested below backlinks, using cosine similarity on tokenized content.
|
|
35
|
+
|
|
36
|
+
**Scratch pad** — a session-only notepad (⚡) pinned at the top of the sidebar. Nothing is persisted until you hit *Save as note…*
|
|
37
|
+
|
|
38
|
+
**Clipboard capture** — opening *New note* automatically pre-fills the content with whatever is in your clipboard.
|
|
39
|
+
|
|
32
40
|
### Projects
|
|
33
41
|
Visual overview of all your Claude projects with session count, token usage, cost, and memory files. Drill into any project to browse its sessions, memory entries, and `CLAUDE.md` files.
|
|
34
42
|
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -484,6 +484,76 @@
|
|
|
484
484
|
.md-content th { background: var(--canvas); font-weight: 600; }
|
|
485
485
|
.md-content hr { border: none; border-top: 1px solid var(--rule); margin: 10px 0; }
|
|
486
486
|
|
|
487
|
+
/* Scratch row */
|
|
488
|
+
.scratch-row {
|
|
489
|
+
border-bottom: 1px solid var(--rule);
|
|
490
|
+
background: color-mix(in srgb, var(--canvas) 60%, var(--white));
|
|
491
|
+
}
|
|
492
|
+
.scratch-row.active { background: color-mix(in srgb, var(--blue) 6%, var(--white)); }
|
|
493
|
+
|
|
494
|
+
/* Note-to-note links */
|
|
495
|
+
.note-ref-link {
|
|
496
|
+
color: var(--blue);
|
|
497
|
+
background: color-mix(in srgb, var(--blue) 8%, transparent);
|
|
498
|
+
border: 1px solid color-mix(in srgb, var(--blue) 20%, transparent);
|
|
499
|
+
border-radius: 3px;
|
|
500
|
+
padding: 0 4px;
|
|
501
|
+
font-size: 0.92em;
|
|
502
|
+
text-decoration: none;
|
|
503
|
+
cursor: pointer;
|
|
504
|
+
}
|
|
505
|
+
.note-ref-link:hover { background: color-mix(in srgb, var(--blue) 15%, transparent); }
|
|
506
|
+
|
|
507
|
+
/* Backlinks */
|
|
508
|
+
.note-backlinks {
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: center;
|
|
511
|
+
gap: 6px;
|
|
512
|
+
flex-wrap: wrap;
|
|
513
|
+
padding: 14px 20px;
|
|
514
|
+
border-top: 1px solid var(--rule);
|
|
515
|
+
margin-top: 8px;
|
|
516
|
+
}
|
|
517
|
+
.note-backlinks-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3); margin-right: 2px; }
|
|
518
|
+
.note-backlink-chip {
|
|
519
|
+
font-size: 11px;
|
|
520
|
+
background: var(--canvas);
|
|
521
|
+
border: 1px solid var(--rule-2);
|
|
522
|
+
border-radius: 20px;
|
|
523
|
+
padding: 2px 9px;
|
|
524
|
+
cursor: pointer;
|
|
525
|
+
color: var(--ink-2);
|
|
526
|
+
}
|
|
527
|
+
.note-backlink-chip:hover { border-color: var(--blue); color: var(--blue); }
|
|
528
|
+
.note-related-chip { color: var(--ink-3); border-style: dashed; }
|
|
529
|
+
.note-related-chip:hover { border-color: var(--ink-2); color: var(--ink); border-style: solid; }
|
|
530
|
+
|
|
531
|
+
/* Note editor autocomplete */
|
|
532
|
+
.note-autocomplete {
|
|
533
|
+
position: absolute;
|
|
534
|
+
bottom: calc(100% + 4px);
|
|
535
|
+
left: 0;
|
|
536
|
+
background: var(--white);
|
|
537
|
+
border: 1px solid var(--rule-2);
|
|
538
|
+
border-radius: 6px;
|
|
539
|
+
box-shadow: 0 4px 12px rgba(0,0,0,.12);
|
|
540
|
+
z-index: 200;
|
|
541
|
+
min-width: 220px;
|
|
542
|
+
overflow: hidden;
|
|
543
|
+
}
|
|
544
|
+
.note-autocomplete-item {
|
|
545
|
+
display: flex;
|
|
546
|
+
align-items: center;
|
|
547
|
+
gap: 4px;
|
|
548
|
+
padding: 6px 10px;
|
|
549
|
+
font-size: 12px;
|
|
550
|
+
cursor: pointer;
|
|
551
|
+
color: var(--ink);
|
|
552
|
+
}
|
|
553
|
+
.note-autocomplete-item.active, .note-autocomplete-item:hover { background: var(--canvas); }
|
|
554
|
+
.note-ac-hash { color: var(--blue); font-weight: 600; }
|
|
555
|
+
.note-ac-title { color: var(--ink-3); font-size: 11px; margin-left: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
556
|
+
|
|
487
557
|
/* Collapsibles */
|
|
488
558
|
.collapsible {
|
|
489
559
|
border: 1px solid var(--rule);
|
|
@@ -2708,17 +2778,22 @@
|
|
|
2708
2778
|
<button class="btn btn-sm" style="background:var(--red-dim,#3a1a1a);color:var(--red);border:1px solid var(--red)" @click="deletePersonalNote()">Delete</button>
|
|
2709
2779
|
</div>
|
|
2710
2780
|
</template>
|
|
2711
|
-
<button class="btn btn-primary btn-sm" @click="
|
|
2781
|
+
<button class="btn btn-primary btn-sm" @click="openNewNote()">+ New note</button>
|
|
2712
2782
|
</div>
|
|
2713
2783
|
</div>
|
|
2714
2784
|
|
|
2715
2785
|
<!-- New note form -->
|
|
2716
2786
|
<template x-if="noteCreating">
|
|
2717
2787
|
<div style="padding:16px 20px;border-bottom:1px solid var(--rule);background:var(--canvas-2)">
|
|
2718
|
-
<input class="settings-input" type="text" placeholder="Note title…" x-model="noteNewTitle" style="width:100%;margin-bottom:8px;font-size:13px" @keydown.enter="createPersonalNote()" />
|
|
2719
|
-
<
|
|
2788
|
+
<input class="settings-input" type="text" placeholder="Note title…" x-model="noteNewTitle" style="width:100%;margin-bottom:8px;font-size:13px" @keydown.enter="createPersonalNote()" x-init="$nextTick(()=>$el.focus())" />
|
|
2789
|
+
<div style="position:relative">
|
|
2790
|
+
<textarea class="settings-input" placeholder="Content (optional)…" x-model="noteNewBody" rows="3" style="width:100%;resize:vertical;font-size:12px;font-family:inherit"></textarea>
|
|
2791
|
+
<template x-if="noteFromClipboard">
|
|
2792
|
+
<span style="position:absolute;top:6px;right:8px;font-size:10px;background:var(--canvas);border:1px solid var(--rule-2);border-radius:3px;padding:1px 5px;color:var(--ink-3);pointer-events:none">from clipboard</span>
|
|
2793
|
+
</template>
|
|
2794
|
+
</div>
|
|
2720
2795
|
<div style="display:flex;gap:8px;margin-top:8px;justify-content:flex-end">
|
|
2721
|
-
<button class="btn btn-sm btn-outline" @click="noteCreating=false">Cancel</button>
|
|
2796
|
+
<button class="btn btn-sm btn-outline" @click="noteCreating=false;noteFromClipboard=false">Cancel</button>
|
|
2722
2797
|
<button class="btn btn-primary btn-sm" @click="createPersonalNote()" :disabled="noteSaving||!noteNewTitle.trim()">Create</button>
|
|
2723
2798
|
</div>
|
|
2724
2799
|
</div>
|
|
@@ -2738,31 +2813,23 @@
|
|
|
2738
2813
|
</div>
|
|
2739
2814
|
</div>
|
|
2740
2815
|
</template>
|
|
2741
|
-
<template x-if="!personalNotesLoading
|
|
2742
|
-
<div style="max-width:480px;margin:40px auto;padding:0 24px;text-align:center">
|
|
2743
|
-
<div style="font-size:28px;margin-bottom:12px">✎</div>
|
|
2744
|
-
<div style="font-size:15px;font-weight:600;margin-bottom:8px">Your personal notepad</div>
|
|
2745
|
-
<div style="font-size:13px;color:var(--ink-3);margin-bottom:24px;line-height:1.6">Notes are <strong>for you</strong>, not for Claude. Claude won't read them as context or memory — they're a place to capture what matters to you across your sessions.</div>
|
|
2746
|
-
<div style="background:var(--canvas-2);border:1px solid var(--rule);border-radius:8px;padding:16px;text-align:left;margin-bottom:20px">
|
|
2747
|
-
<div style="font-size:11px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Try saying to Claude</div>
|
|
2748
|
-
<div style="display:flex;flex-direction:column;gap:7px">
|
|
2749
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Save this as a note"</span></div>
|
|
2750
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Save this last output as a note"</span></div>
|
|
2751
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Add this decision to my notes"</span></div>
|
|
2752
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Save this as a TIL"</span></div>
|
|
2753
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Show me my last note"</span></div>
|
|
2754
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"What notes do I have?"</span></div>
|
|
2755
|
-
<div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Show me my notes"</span> <span style="font-size:11px;color:var(--ink-3)">→ http://localhost:3141</span></div>
|
|
2756
|
-
</div>
|
|
2757
|
-
</div>
|
|
2758
|
-
<div style="font-size:11px;color:var(--ink-3)">Or create a note manually with the <strong>+ New note</strong> button above.</div>
|
|
2759
|
-
</div>
|
|
2760
|
-
</template>
|
|
2761
|
-
<template x-if="!personalNotesLoading && personalNotes.length > 0">
|
|
2816
|
+
<template x-if="!personalNotesLoading">
|
|
2762
2817
|
<div class="memory-layout">
|
|
2763
2818
|
<div class="memory-sidebar" :style="'width:'+sidebarW+'px'">
|
|
2819
|
+
<!-- Scratch — always pinned at top -->
|
|
2820
|
+
<div class="plan-row scratch-row" :class="{active: scratchActive}" @click="scratchActive=true;selectedNote=null;noteEditing=false;window.location.hash=''">
|
|
2821
|
+
<div style="display:flex;align-items:center;gap:5px">
|
|
2822
|
+
<span style="font-size:11px;opacity:.7">⚡</span>
|
|
2823
|
+
<span style="font-size:12.5px;font-weight:500">Scratch</span>
|
|
2824
|
+
<span style="font-size:10px;color:var(--ink-3);margin-left:2px">session only</span>
|
|
2825
|
+
</div>
|
|
2826
|
+
<div x-show="scratchContent.length > 0" style="font-size:10px;color:var(--ink-3)" x-text="scratchContent.slice(0,40) + (scratchContent.length>40?'…':'')"></div>
|
|
2827
|
+
</div>
|
|
2828
|
+
<template x-if="personalNotes.length > 0">
|
|
2829
|
+
<div style="border-top:1px solid var(--rule);margin:4px 0"></div>
|
|
2830
|
+
</template>
|
|
2764
2831
|
<template x-for="n in personalNotes.filter(n => !noteSearch || n.title.toLowerCase().includes(noteSearch.toLowerCase()) || n.content.toLowerCase().includes(noteSearch.toLowerCase()))" :key="n.filename">
|
|
2765
|
-
<div class="plan-row" :class="{active: selectedNote?.filename === n.filename}" @click="selectedNote=n;noteEditing=false;window.location.hash='#/note/'+n.filename">
|
|
2832
|
+
<div class="plan-row" :class="{active: selectedNote?.filename === n.filename}" @click="selectedNote=n;scratchActive=false;noteEditing=false;window.location.hash='#/note/'+n.filename">
|
|
2766
2833
|
<div class="plan-row-title" x-text="n.title"></div>
|
|
2767
2834
|
<div class="plan-row-meta">
|
|
2768
2835
|
<span x-text="formatDate(n.modified)"></span>
|
|
@@ -2773,7 +2840,20 @@
|
|
|
2773
2840
|
</div>
|
|
2774
2841
|
<div class="resize-handle" @mousedown.prevent="startResize($event)"></div>
|
|
2775
2842
|
<div class="memory-detail">
|
|
2776
|
-
|
|
2843
|
+
<!-- Scratch detail -->
|
|
2844
|
+
<template x-if="scratchActive">
|
|
2845
|
+
<div style="display:flex;flex-direction:column;height:100%;gap:10px">
|
|
2846
|
+
<div style="display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
|
2847
|
+
<span style="font-size:11px;color:var(--ink-3)">⚡ Not saved · cleared on page reload</span>
|
|
2848
|
+
<div style="display:flex;gap:8px">
|
|
2849
|
+
<button class="btn btn-sm btn-outline" @click="scratchContent='';sessionStorage.removeItem('cs:scratch')" x-show="scratchContent.length>0">Clear</button>
|
|
2850
|
+
<button class="btn btn-primary btn-sm" @click="promoteScratch()" :disabled="!scratchContent.trim()">Save as note…</button>
|
|
2851
|
+
</div>
|
|
2852
|
+
</div>
|
|
2853
|
+
<textarea class="settings-input" x-model="scratchContent" @input="sessionStorage.setItem('cs:scratch',scratchContent)" style="flex:1;resize:none;font-family:monospace;font-size:12px;min-height:0;width:100%" placeholder="Scratch pad — nothing here is persisted. Use ↑ Save as note to keep it."></textarea>
|
|
2854
|
+
</div>
|
|
2855
|
+
</template>
|
|
2856
|
+
<template x-if="!scratchActive && !selectedNote">
|
|
2777
2857
|
<div style="padding:24px;max-width:380px">
|
|
2778
2858
|
<div style="font-size:13px;font-weight:600;margin-bottom:6px;color:var(--ink)">Your personal notepad</div>
|
|
2779
2859
|
<div style="font-size:12px;color:var(--ink-3);margin-bottom:16px;line-height:1.6">Notes are <strong>for you</strong>, not for Claude. He won't read them as context — they're yours to capture, review and revisit.</div>
|
|
@@ -2793,17 +2873,45 @@
|
|
|
2793
2873
|
<div class="memory-detail-title" x-text="selectedNote.title"></div>
|
|
2794
2874
|
<div class="memory-detail-meta">
|
|
2795
2875
|
<span style="font-size:11px;color:var(--ink-3)" x-text="formatDate(selectedNote.modified)"></span>
|
|
2796
|
-
<template x-if="selectedNote.session">
|
|
2876
|
+
<template x-if="selectedNote.session && selectedNote.session.length >= 32">
|
|
2797
2877
|
<span style="font-size:11px;color:var(--blue);cursor:pointer" @click="openSessionById(selectedNote.session)" x-text="'→ Session ' + selectedNote.session.slice(0,8) + '…'"></span>
|
|
2798
2878
|
</template>
|
|
2799
2879
|
</div>
|
|
2800
|
-
<div class="memory-content md-content" x-html="selectedNote.content ?
|
|
2880
|
+
<div class="memory-content md-content" @click="handleNoteLinkClick($event)" x-html="selectedNote.content ? renderNotesMd(selectedNote.content) : '<span style=\'color:var(--ink-3)\'>Empty note. Click Edit to add content.</span>'"></div>
|
|
2881
|
+
<template x-if="noteBacklinks(selectedNote).length > 0">
|
|
2882
|
+
<div class="note-backlinks">
|
|
2883
|
+
<span class="note-backlinks-label">Referenced by</span>
|
|
2884
|
+
<template x-for="bl in noteBacklinks(selectedNote)" :key="bl.filename">
|
|
2885
|
+
<span class="note-backlink-chip" @click="selectedNote=bl;noteEditing=false;window.location.hash='#/note/'+bl.filename" x-text="bl.title"></span>
|
|
2886
|
+
</template>
|
|
2887
|
+
</div>
|
|
2888
|
+
</template>
|
|
2889
|
+
<template x-if="noteRelated(selectedNote).length > 0">
|
|
2890
|
+
<div class="note-backlinks">
|
|
2891
|
+
<span class="note-backlinks-label">Related</span>
|
|
2892
|
+
<template x-for="rn in noteRelated(selectedNote)" :key="rn.filename">
|
|
2893
|
+
<span class="note-backlink-chip note-related-chip" @click="selectedNote=rn;noteEditing=false;window.location.hash='#/note/'+rn.filename" x-text="rn.title"></span>
|
|
2894
|
+
</template>
|
|
2895
|
+
</div>
|
|
2896
|
+
</template>
|
|
2801
2897
|
</div>
|
|
2802
2898
|
</template>
|
|
2803
2899
|
<template x-if="selectedNote && noteEditing">
|
|
2804
|
-
<div style="display:flex;flex-direction:column;height:100%;gap:10px">
|
|
2900
|
+
<div style="display:flex;flex-direction:column;height:100%;gap:10px;position:relative">
|
|
2805
2901
|
<input class="settings-input" type="text" x-model="noteTitleDraft" style="font-size:14px;font-weight:600;width:100%" />
|
|
2806
|
-
<
|
|
2902
|
+
<div style="position:relative;flex:1;display:flex;flex-direction:column">
|
|
2903
|
+
<textarea class="settings-input note-body-ta" x-model="noteBodyDraft" @input="noteEditorInput($event)" @keydown="noteEditorKeydown($event)" @blur="setTimeout(()=>noteAutocomplete.visible=false,150)" style="flex:1;resize:none;font-family:monospace;font-size:12px;min-height:300px;width:100%"></textarea>
|
|
2904
|
+
<template x-if="noteAutocomplete.visible">
|
|
2905
|
+
<div class="note-autocomplete">
|
|
2906
|
+
<template x-for="(n, i) in noteAutocomplete.results" :key="n.filename">
|
|
2907
|
+
<div class="note-autocomplete-item" :class="{active: i===noteAutocomplete.pos}" @mousedown.prevent="noteAutocompleteSelect(n)">
|
|
2908
|
+
<span class="note-ac-hash">#</span><span x-text="n.filename.replace(/\.md$/,'').replace(/^\d{4}-\d{2}-\d{2}-/,'')"></span>
|
|
2909
|
+
<span class="note-ac-title" x-text="n.title"></span>
|
|
2910
|
+
</div>
|
|
2911
|
+
</template>
|
|
2912
|
+
</div>
|
|
2913
|
+
</template>
|
|
2914
|
+
</div>
|
|
2807
2915
|
</div>
|
|
2808
2916
|
</template>
|
|
2809
2917
|
</div>
|
|
@@ -4478,6 +4586,10 @@
|
|
|
4478
4586
|
noteNewBody: '',
|
|
4479
4587
|
noteClaudeInstalled: null,
|
|
4480
4588
|
noteSetupMsg: '',
|
|
4589
|
+
noteAutocomplete: { visible: false, query: '', results: [], pos: 0 },
|
|
4590
|
+
noteFromClipboard: false,
|
|
4591
|
+
scratchContent: sessionStorage.getItem('cs:scratch') || '',
|
|
4592
|
+
scratchActive: false,
|
|
4481
4593
|
sidebarW: parseInt(localStorage.getItem('cm:sidebarW') || '260'),
|
|
4482
4594
|
historyEntries: [],
|
|
4483
4595
|
historyLoading: false,
|
|
@@ -4626,7 +4738,11 @@
|
|
|
4626
4738
|
if (savedFrom) this.filterFrom = savedFrom;
|
|
4627
4739
|
if (savedTo) this.filterTo = savedTo;
|
|
4628
4740
|
|
|
4629
|
-
this.$watch('view',
|
|
4741
|
+
this.$watch('view', v => {
|
|
4742
|
+
localStorage.setItem('cs:view', v);
|
|
4743
|
+
if (v !== 'notes' && window.location.hash.startsWith('#/note/')) window.location.hash = '';
|
|
4744
|
+
if (v !== 'sessions' && window.location.hash.startsWith('#/session/')) window.location.hash = '';
|
|
4745
|
+
});
|
|
4630
4746
|
this.$watch('filterProject', v => localStorage.setItem('cs:project', v));
|
|
4631
4747
|
this.$watch('filterBranch', v => localStorage.setItem('cs:branch', v));
|
|
4632
4748
|
this.$watch('filterText', v => localStorage.setItem('cs:filterText', v));
|
|
@@ -5043,6 +5159,108 @@
|
|
|
5043
5159
|
|
|
5044
5160
|
renderMd(text) { try { return marked.parse(text); } catch { return text; } },
|
|
5045
5161
|
|
|
5162
|
+
renderNotesMd(text) {
|
|
5163
|
+
const processed = text.replace(/(^|[^&\w])#([a-zA-Z][\w-]+)/g, (match, pre, slug) => {
|
|
5164
|
+
const note = this.personalNotes.find(n => {
|
|
5165
|
+
const base = n.filename.replace(/\.md$/, '');
|
|
5166
|
+
const slugPart = base.replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
5167
|
+
return slugPart === slug || base === slug;
|
|
5168
|
+
});
|
|
5169
|
+
if (!note) return match;
|
|
5170
|
+
return `${pre}[#${slug}](#/note/${note.filename})`;
|
|
5171
|
+
});
|
|
5172
|
+
let html;
|
|
5173
|
+
try { html = marked.parse(processed); } catch { html = processed; }
|
|
5174
|
+
// Mark note-ref links so we can style/intercept them
|
|
5175
|
+
html = html.replace(/href="#\/note\//g, 'class="note-ref-link" href="#/note/');
|
|
5176
|
+
return html;
|
|
5177
|
+
},
|
|
5178
|
+
|
|
5179
|
+
handleNoteLinkClick(e) {
|
|
5180
|
+
const a = e.target.closest('a.note-ref-link');
|
|
5181
|
+
if (!a) return;
|
|
5182
|
+
e.preventDefault();
|
|
5183
|
+
const filename = a.getAttribute('href').replace('#/note/', '');
|
|
5184
|
+
const note = this.personalNotes.find(n => n.filename === filename);
|
|
5185
|
+
if (note) { this.selectedNote = note; this.noteEditing = false; window.location.hash = '#/note/' + filename; }
|
|
5186
|
+
},
|
|
5187
|
+
|
|
5188
|
+
noteTokens(note) {
|
|
5189
|
+
const STOP = new Set(['the','a','an','and','or','but','in','on','at','to','for','of','with','by','from','is','are','was','were','be','been','have','has','had','do','does','did','will','would','could','should','may','might','that','this','it','its','as','if','not','no','so','up','out','de','la','el','en','que','se','los','las','un','una','con','por','para','como','más','una','sus','del','al','lo','le','les','nos','pero','fue','si','ya','también','este','esta','estos','estas','hay','una']);
|
|
5190
|
+
const text = (note.title + ' ' + (note.content || '')).toLowerCase();
|
|
5191
|
+
return new Set(text.match(/[a-záéíóúüñ]{3,}/g)?.filter(w => !STOP.has(w)) || []);
|
|
5192
|
+
},
|
|
5193
|
+
|
|
5194
|
+
noteRelated(note, limit = 4) {
|
|
5195
|
+
if (!note) return [];
|
|
5196
|
+
const ta = this.noteTokens(note);
|
|
5197
|
+
if (ta.size === 0) return [];
|
|
5198
|
+
const backlinked = new Set(this.noteBacklinks(note).map(n => n.filename));
|
|
5199
|
+
return this.personalNotes
|
|
5200
|
+
.filter(n => n.filename !== note.filename && !backlinked.has(n.filename))
|
|
5201
|
+
.map(n => {
|
|
5202
|
+
const tb = this.noteTokens(n);
|
|
5203
|
+
const shared = [...ta].filter(w => tb.has(w)).length;
|
|
5204
|
+
const score = shared / Math.sqrt(ta.size * tb.size);
|
|
5205
|
+
return { note: n, score };
|
|
5206
|
+
})
|
|
5207
|
+
.filter(x => x.score > 0.08)
|
|
5208
|
+
.sort((a, b) => b.score - a.score)
|
|
5209
|
+
.slice(0, limit)
|
|
5210
|
+
.map(x => x.note);
|
|
5211
|
+
},
|
|
5212
|
+
|
|
5213
|
+
noteBacklinks(note) {
|
|
5214
|
+
if (!note) return [];
|
|
5215
|
+
const slug = note.filename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
5216
|
+
const fullBase = note.filename.replace(/\.md$/, '');
|
|
5217
|
+
return this.personalNotes.filter(n => {
|
|
5218
|
+
if (n.filename === note.filename) return false;
|
|
5219
|
+
return n.content && (n.content.includes('#' + slug) || n.content.includes('#' + fullBase));
|
|
5220
|
+
});
|
|
5221
|
+
},
|
|
5222
|
+
|
|
5223
|
+
noteEditorInput(e) {
|
|
5224
|
+
const ta = e.target;
|
|
5225
|
+
const val = ta.value;
|
|
5226
|
+
const pos = ta.selectionStart;
|
|
5227
|
+
// Find # trigger: look back from cursor for #word (no spaces)
|
|
5228
|
+
const before = val.slice(0, pos);
|
|
5229
|
+
const m = before.match(/#([a-zA-Z][\w-]*)$/);
|
|
5230
|
+
if (m) {
|
|
5231
|
+
const query = m[1].toLowerCase();
|
|
5232
|
+
const results = this.personalNotes.filter(n => {
|
|
5233
|
+
const base = n.filename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
5234
|
+
return base.includes(query) || n.title.toLowerCase().includes(query);
|
|
5235
|
+
}).slice(0, 6);
|
|
5236
|
+
this.noteAutocomplete = { visible: results.length > 0, query: m[1], results, pos: 0 };
|
|
5237
|
+
} else {
|
|
5238
|
+
this.noteAutocomplete.visible = false;
|
|
5239
|
+
}
|
|
5240
|
+
},
|
|
5241
|
+
|
|
5242
|
+
noteEditorKeydown(e) {
|
|
5243
|
+
const ac = this.noteAutocomplete;
|
|
5244
|
+
if (!ac.visible) return;
|
|
5245
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); ac.pos = Math.min(ac.pos + 1, ac.results.length - 1); }
|
|
5246
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); ac.pos = Math.max(ac.pos - 1, 0); }
|
|
5247
|
+
else if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); this.noteAutocompleteSelect(ac.results[ac.pos]); }
|
|
5248
|
+
else if (e.key === 'Escape') { ac.visible = false; }
|
|
5249
|
+
},
|
|
5250
|
+
|
|
5251
|
+
noteAutocompleteSelect(note) {
|
|
5252
|
+
const slug = note.filename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
5253
|
+
const ta = document.querySelector('textarea.note-body-ta');
|
|
5254
|
+
if (!ta) return;
|
|
5255
|
+
const pos = ta.selectionStart;
|
|
5256
|
+
const before = ta.value.slice(0, pos);
|
|
5257
|
+
const after = ta.value.slice(pos);
|
|
5258
|
+
const replaced = before.replace(/#([a-zA-Z][\w-]*)$/, '#' + slug);
|
|
5259
|
+
this.noteBodyDraft = replaced + after;
|
|
5260
|
+
this.noteAutocomplete.visible = false;
|
|
5261
|
+
this.$nextTick(() => { ta.focus(); ta.selectionStart = ta.selectionEnd = replaced.length; });
|
|
5262
|
+
},
|
|
5263
|
+
|
|
5046
5264
|
renderInlineCode(text) {
|
|
5047
5265
|
const esc = text
|
|
5048
5266
|
.replace(/&/g, '&').replace(/</g, '<')
|
|
@@ -5530,6 +5748,28 @@
|
|
|
5530
5748
|
setTimeout(() => this.planShareMsg = '', 5000);
|
|
5531
5749
|
},
|
|
5532
5750
|
|
|
5751
|
+
async openNewNote() {
|
|
5752
|
+
this.noteNewTitle = '';
|
|
5753
|
+
this.noteNewBody = '';
|
|
5754
|
+
this.noteFromClipboard = false;
|
|
5755
|
+
this.noteCreating = true;
|
|
5756
|
+
try {
|
|
5757
|
+
const text = await navigator.clipboard.readText();
|
|
5758
|
+
if (text && text.trim().length > 0) {
|
|
5759
|
+
this.noteNewBody = text.trim();
|
|
5760
|
+
this.noteFromClipboard = true;
|
|
5761
|
+
}
|
|
5762
|
+
} catch (e) { /* clipboard access denied or empty */ }
|
|
5763
|
+
},
|
|
5764
|
+
|
|
5765
|
+
promoteScratch() {
|
|
5766
|
+
this.noteNewTitle = '';
|
|
5767
|
+
this.noteNewBody = this.scratchContent;
|
|
5768
|
+
this.noteFromClipboard = false;
|
|
5769
|
+
this.scratchActive = false;
|
|
5770
|
+
this.noteCreating = true;
|
|
5771
|
+
},
|
|
5772
|
+
|
|
5533
5773
|
async loadPersonalNotes() {
|
|
5534
5774
|
if (this.personalNotesLoading) return;
|
|
5535
5775
|
this.personalNotesLoading = true;
|
package/server.js
CHANGED
|
@@ -1928,13 +1928,14 @@ When the user asks you to "save a note", "add to notes", "guarda esto como nota"
|
|
|
1928
1928
|
---
|
|
1929
1929
|
title: <descriptive title>
|
|
1930
1930
|
date: <current ISO date>
|
|
1931
|
-
session: <
|
|
1931
|
+
session: <run: ls -t ~/.claude/projects/$(pwd | sed 's|/|-|g' | sed 's|^-||')/*.jsonl 2>/dev/null | head -1 | xargs basename 2>/dev/null | sed 's/\.jsonl//'>
|
|
1932
1932
|
---
|
|
1933
1933
|
|
|
1934
1934
|
<the content the user wants to save>
|
|
1935
1935
|
\`\`\`
|
|
1936
|
-
2.
|
|
1937
|
-
3.
|
|
1936
|
+
2. **Note linking**: If the note references concepts that likely exist in other notes, use \`#slug\` syntax to link them. The slug is the part of the filename after the date (\`2026-03-31-\`**\`my-slug\`**\`.md\` → \`#my-slug\`). To discover existing notes and their slugs, \`Glob ~/.claude/claude-home/notes/*.md\` before writing.
|
|
1937
|
+
3. Use the Write tool to create the file (not Bash).
|
|
1938
|
+
4. Confirm with: "Saved to Notes: http://localhost:3141/#/note/<filename>"
|
|
1938
1939
|
|
|
1939
1940
|
The notes directory may not exist yet — the app creates it automatically on first load.
|
|
1940
1941
|
|