From 9ec454fa56574c507ac94472f3c6abcca3a7c340 Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 13 Oct 2026 17:56:14 +0200 Subject: [PATCH 1/2] feat(wiki): add the family's lists to the menu The lists had a page each in the wiki but no way to reach it except a link from a topic or person page. The menu now has a Lists entry after Recent, with every family list and the number of its open items. A list page's breadcrumb leads back to it. --- stacklets/memory/quartz/components/familyModel.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/stacklets/memory/quartz/components/familyModel.ts b/stacklets/memory/quartz/components/familyModel.ts index f3ad2906..fbbf3620 100644 --- a/stacklets/memory/quartz/components/familyModel.ts +++ b/stacklets/memory/quartz/components/familyModel.ts @@ -31,6 +31,7 @@ const LABELS = { menu: "Menu", start: "Start", recent: "Recent", + lists: "Lists", diary: "Diary", people: "People", topics: "Topics", @@ -75,6 +76,7 @@ const LABELS = { menu: "Menü", start: "Start", recent: "Aktuell", + lists: "Listen", diary: "Tagebuch", people: "Personen", topics: "Themen", @@ -392,6 +394,7 @@ function build(ctx: BuildCtx, allFiles: QuartzPluginData[]): Model { const records: Rec[] = [] const recTopics = new Map() const people: NavNode[] = [] + const familyLists: NavNode[] = [] const diaryYears = new Map() let diaryRoot: string | undefined @@ -419,6 +422,16 @@ function build(ctx: BuildCtx, allFiles: QuartzPluginData[]): Model { continue } + // family lists: /lists/, counted by their open items; + // the archive of ticked items below it is not a list of its own + if (segs[0] === bucket && segs[1] === "lists") { + if (segs.length === 3 && meta.type === "list") { + const open = openTasks(readText(ctx, f)).length + familyLists.push({ label: String(meta.title ?? segs[2]), slug, count: open || undefined }) + } + continue + } + // topic folders: about page and task list if (segs[0] === bucket && segs.length === 3 && !KIND_FOLDERS.has(segs[1])) { const t = folderTopic(segs[1]) @@ -632,6 +645,8 @@ function build(ctx: BuildCtx, allFiles: QuartzPluginData[]): Model { // ── assemble, in the agreed order ── const nav: NavNode[] = [{ key: "start", label: L.start, slug: "index" }] if (recent.length) nav.push({ key: "recent", label: L.recent, open: true, children: recent.map(topicNode) }) + if (familyLists.length) + nav.push({ key: "lists", label: L.lists, children: familyLists.sort((a, b) => a.label.localeCompare(b.label, lang)) }) if (diaryRoot || diaryYears.size) { const yearsDesc = [...diaryYears.values()].sort((a, b) => Number(b.label) - Number(a.label)) nav.push({ From f7ebd6851cd818a3e4fecb90496b7afcea05b6ba Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 13 Oct 2026 17:56:14 +0200 Subject: [PATCH 2/2] feat(wiki): add "List" to the title of a list page A list is often named for what it is about, so a page titled "Camping" read as the topic. A list page's title now ends in "List" ("Liste" in German), unless the family's title already says so, as "Einkaufsliste" does. The heading, the browser tab, search and the menu use the same title; the file in the family memory keeps its own. `./stack restart memory` rebuilds the wiki with it. --- stacklets/memory/Dockerfile | 1 + .../memory/quartz/components/familyModel.ts | 7 ++++++ stacklets/memory/quartz/plugins/listTitles.ts | 22 +++++++++++++++++++ stacklets/memory/quartz/quartz.config.ts | 3 +++ 4 files changed, 33 insertions(+) create mode 100644 stacklets/memory/quartz/plugins/listTitles.ts diff --git a/stacklets/memory/Dockerfile b/stacklets/memory/Dockerfile index 70b67c9c..06a58f14 100644 --- a/stacklets/memory/Dockerfile +++ b/stacklets/memory/Dockerfile @@ -88,6 +88,7 @@ COPY quartz/components/FamilyCrumbs.tsx ./quartz/components/FamilyCrumbs.tsx COPY quartz/components/scripts/familyNav.inline.ts ./quartz/components/scripts/familyNav.inline.ts COPY quartz/plugins/familyLists.tsx ./quartz/plugins/emitters/familyLists.tsx COPY quartz/plugins/vaultLinks.ts ./quartz/plugins/transformers/vaultLinks.ts +COPY quartz/plugins/listTitles.ts ./quartz/plugins/transformers/listTitles.ts # The shipped ontology, for the topic areas a vault installed earlier # does not name yet (see readOntology in familyModel.ts). COPY seeds/ontology.toml ./ontology.seed.toml diff --git a/stacklets/memory/quartz/components/familyModel.ts b/stacklets/memory/quartz/components/familyModel.ts index fbbf3620..ec18ded3 100644 --- a/stacklets/memory/quartz/components/familyModel.ts +++ b/stacklets/memory/quartz/components/familyModel.ts @@ -32,6 +32,7 @@ const LABELS = { start: "Start", recent: "Recent", lists: "Lists", + list: "List", diary: "Diary", people: "People", topics: "Topics", @@ -77,6 +78,7 @@ const LABELS = { start: "Start", recent: "Aktuell", lists: "Listen", + list: "Liste", diary: "Tagebuch", people: "Personen", topics: "Themen", @@ -120,6 +122,11 @@ const LABELS = { export const L = LABELS[lang] +// A list is often named for what it is about ("Camping"), which reads as +// the topic. Its title says it is a list, unless the family's name for it +// already does ("Einkaufsliste", "Packing list"). +export const listTitle = (title: string) => (/list/i.test(title) ? title : `${title} ${L.list}`) + export const locale = lang === "de" ? "de-DE" : "en-US" const dateFormat = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", year: "numeric" }) export const fmtDate = (d: Date) => dateFormat.format(d) diff --git a/stacklets/memory/quartz/plugins/listTitles.ts b/stacklets/memory/quartz/plugins/listTitles.ts new file mode 100644 index 00000000..2890d57e --- /dev/null +++ b/stacklets/memory/quartz/plugins/listTitles.ts @@ -0,0 +1,22 @@ +import { QuartzTransformerPlugin } from "../types" +import { listTitle } from "../../components/familyModel" + +// NEW TRANSFORMER (not an upstream override). +// +// A family list is titled the way the family named it, often for what it +// is about: "Camping". On its own page that reads as the topic, so a list +// page says it is a list ("Camping Liste"). The title is set here, once, +// so the page heading, the browser tab, search and the menu all agree. +// The file in the vault keeps the family's own title. + +export const ListTitles: QuartzTransformerPlugin = () => ({ + name: "ListTitles", + markdownPlugins() { + return [ + () => (_tree, file) => { + const fm = file.data.frontmatter + if (fm?.type === "list" && typeof fm.title === "string") fm.title = listTitle(fm.title) + }, + ] + }, +}) diff --git a/stacklets/memory/quartz/quartz.config.ts b/stacklets/memory/quartz/quartz.config.ts index 9e3f6b2a..170b605e 100644 --- a/stacklets/memory/quartz/quartz.config.ts +++ b/stacklets/memory/quartz/quartz.config.ts @@ -15,6 +15,7 @@ import { QuartzConfig } from "./quartz/cfg" import * as Plugin from "./quartz/plugins" import { FamilyLists } from "./quartz/plugins/emitters/familyLists" import { VaultLinks } from "./quartz/plugins/transformers/vaultLinks" +import { ListTitles } from "./quartz/plugins/transformers/listTitles" // Runtime env. `WIKI_HOST` is "memory." in domain mode and // the empty-domain rendering "memory." in port mode — we treat the @@ -123,6 +124,8 @@ const config: QuartzConfig = { plugins: { transformers: [ Plugin.FrontMatter(), + // A list page's title says it is a list; needs the frontmatter read. + ListTitles(), // The vault is a git repo — let git timestamps win over // filesystem mtime so a fresh clone shows real commit dates. Plugin.CreatedModifiedDate({