feat(release): v0.2.0

commit 78d6c27c8940da32a6de8e64327c86f74fdaa2eb
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 12:59:22 2026 +0100

    feat: freebsd log rotation config thingie

commit 55af4e6c70122e679272ed247c26e04b1247f694
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 12:58:43 2026 +0100

    feat: embed templates, static resolution

commit 29c917f929a7378ec29c54315ee2e9f420747787
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 10:44:34 2026 +0100

    feat: set log file path

commit 294fd3d1549979eab63587ceec6ff5d0978e9afc
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 10:23:53 2026 +0100

    feat: logging HTTP request

commit c9ae80b240d58e1abed7ae3b7b2c3b283a31f1a1
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 09:54:05 2026 +0100

    feat: freebsd-specific compile target and scripts

commit 86ca154dedd19aa1fe5f571c445dcf17a8396bfa
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 09:25:16 2026 +0100

    feat: mobile friendly CSS

commit 199f4319e0b08a4b6d595d7eb3effb6db6c7beec
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 09:25:03 2026 +0100

    feat: persisting rendered note

commit 865e258237e45d7c542685a4653bcad3c5af259d
Author: adminoo <git@kadath.corp>
Date:   Wed Feb 4 08:06:38 2026 +0100

    fix: grouping notes by folder

commit 242d1d074c92461f38212b033c7a9e383f9dc550
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 16:52:50 2026 +0100

    feat: storage layer logic

    - Prune notes from db not matching current folder structure at start
    - Detect file system deletion on start by comparing in-db notes
    - Prevent updating of in-db notes at start if modification time is not
      newer
    - Delete by path

commit d75d46bc1ab22bd990d0fdc307e571fe52f0dd99
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 15:27:07 2026 +0100

    feat: group notes by root folders

commit e1e25a938e717599332f7b40a449d9bb854b673a
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 14:24:37 2026 +0100

    feat: size in kilobytes

commit 61220272a2df2b66c2b8e356ba359ed01de3bd12
Author: adminoo <git@kadath.corp>
Date:   Tue Feb 3 14:19:40 2026 +0100

    feat: styling inputs
This commit is contained in:
2026-02-04 13:15:55 +01:00
parent 9d1254244f
commit b571588b15
23 changed files with 691 additions and 70 deletions

View File

@ -29,18 +29,21 @@ func newTestEnv(t *testing.T) *testEnv {
ID: "a1",
Title: "Alpha",
Content: "# Alpha\ncontent",
Path: "notes/alpha.md",
UpdatedAt: time.Date(2025, 12, 31, 10, 0, 0, 0, time.UTC),
},
{
ID: "b2",
Title: "Beta",
Content: "# Beta\nRust tips",
Path: "dev/beta.md",
UpdatedAt: time.Date(2026, 1, 1, 10, 0, 0, 0, time.UTC),
},
{
ID: "z9",
Title: "Zulu",
Content: "# Zulu\nRust book",
Path: "notes/zulu.md",
UpdatedAt: time.Date(2026, 1, 2, 10, 0, 0, 0, time.UTC),
},
}
@ -83,6 +86,20 @@ func noteMarker(id string) string {
return `data-hash="` + id + `"`
}
func groupSection(body, name string) string {
header := `<h3 class="note-group">` + name + `</h3>`
start := strings.Index(body, header)
if start == -1 {
return ""
}
rest := body[start+len(header):]
next := strings.Index(rest, `<h3 class="note-group">`)
if next == -1 {
return rest
}
return rest[:next]
}
func TestHandlerRoot_DefaultSortRecent(t *testing.T) {
env := newTestEnv(t)
@ -100,7 +117,24 @@ func TestHandlerRoot_DefaultSortRecent(t *testing.T) {
}
body := rec.Body.String()
assertOrder(t, body, []string{noteMarker("z9"), noteMarker("b2"), noteMarker("a1")})
if !strings.Contains(body, `<h3 class="note-group">dev</h3>`) {
t.Fatalf("expected dev group header")
}
if !strings.Contains(body, `<h3 class="note-group">notes</h3>`) {
t.Fatalf("expected notes group header")
}
devSection := groupSection(body, "dev")
if devSection == "" {
t.Fatalf("expected dev section")
}
assertOrder(t, devSection, []string{noteMarker("b2")})
notesSection := groupSection(body, "notes")
if notesSection == "" {
t.Fatalf("expected notes section")
}
assertOrder(t, notesSection, []string{noteMarker("z9"), noteMarker("a1")})
}
func TestHandlerRoot_SortAlpha(t *testing.T) {
@ -116,7 +150,17 @@ func TestHandlerRoot_SortAlpha(t *testing.T) {
}
body := rec.Body.String()
assertOrder(t, body, []string{noteMarker("a1"), noteMarker("b2"), noteMarker("z9")})
devSection := groupSection(body, "dev")
if devSection == "" {
t.Fatalf("expected dev section")
}
assertOrder(t, devSection, []string{noteMarker("b2")})
notesSection := groupSection(body, "notes")
if notesSection == "" {
t.Fatalf("expected notes section")
}
assertOrder(t, notesSection, []string{noteMarker("a1"), noteMarker("z9")})
}
func TestHandlerRoot_Search(t *testing.T) {
@ -305,3 +349,97 @@ func TestHandlerRoot_TagFilter(t *testing.T) {
t.Fatalf("expected matching note to be included")
}
}
func TestHandlerRoot_RendersActiveNote(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/?search=rust&active=b2", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, "<h1>Beta</h1>") {
t.Fatalf("expected rendered active note content")
}
}
func TestHandlerRoot_RendersRootGroupHeader(t *testing.T) {
env := newTestEnv(t)
rootNote := &note.Note{
ID: "r1",
Title: "Root Note",
Content: "# Root\ncontent",
Path: "root.md",
UpdatedAt: time.Date(2026, 1, 3, 10, 0, 0, 0, time.UTC),
}
if err := env.storage.Create(rootNote); err != nil {
t.Fatalf("create note: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, `<h3 class="note-group">root</h3>`) {
t.Fatalf("expected root group header")
}
if !strings.Contains(body, noteMarker("r1")) {
t.Fatalf("expected root note to be rendered")
}
}
func TestRootFolderName(t *testing.T) {
cases := []struct {
name string
path string
want string
}{
{name: "empty", path: "", want: "root"},
{name: "dot", path: ".", want: "root"},
{name: "file at root", path: "bookmarks.md", want: "root"},
{name: "single", path: "notes/file.md", want: "notes"},
{name: "nested", path: "dev/ideas/note.md", want: "dev"},
{name: "absolute", path: "/writings/notes/a.md", want: "writings"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := rootFolderName(tc.path); got != tc.want {
t.Fatalf("expected %q, got %q", tc.want, got)
}
})
}
}
func TestGroupNotesByFolder(t *testing.T) {
notes := []*note.Note{
{ID: "a1", Title: "Alpha", Path: "notes/a.md"},
{ID: "b2", Title: "Beta", Path: "dev/b.md"},
{ID: "c3", Title: "Gamma", Path: "notes/c.md"},
}
groups := groupNotesByFolder(notes)
if len(groups) != 2 {
t.Fatalf("expected 2 groups, got %d", len(groups))
}
if groups[0].Name != "dev" || len(groups[0].Notes) != 1 {
t.Fatalf("unexpected dev group: %+v", groups[0])
}
if groups[1].Name != "notes" || len(groups[1].Notes) != 2 {
t.Fatalf("unexpected notes group: %+v", groups[1])
}
}