diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5e54af..a5c601d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,28 @@ not from the output of `docker push`.
---
+## 2026-08-12 — A chapter you can see, in the colour you chose
+
+**Images** — web `sha256:…`, server unchanged at `sha256:ed0b9970…`
+
+**Fixed**
+- **A chapter did not show until the notebook had a note in it.** "Nothing here"
+ was asked of the notes alone, so a notebook whose only contents were chapters
+ showed the empty-list panel with the chapters hidden behind it — making a
+ chapter in a fresh notebook appeared to do nothing at all. It is now asked of
+ every row, and a notebook with chapters but no loose notes says so in one line
+ rather than looking like a list that failed to load.
+
+**Changed**
+- **A chapter's colour is chosen while it is being made**, in the same dialog as
+ its name. It is what tells one chapter from the next, and having to right-click
+ a fresh one to say which it is meant two visits for one decision. Renaming and
+ recolouring later are one entry in its menu, and one write.
+- **The word *here* is gone.** The line along the top of the pane says which of
+ the two places you are in; saying it twice was noise.
+
+---
+
## 2026-08-12 — A filed note stays filed
**Images** — web `sha256:2041f103…`, server `sha256:ed0b9970…`
diff --git a/e2e/chapters.mjs b/e2e/chapters.mjs
index 473806c..ecac39f 100644
--- a/e2e/chapters.mjs
+++ b/e2e/chapters.mjs
@@ -24,10 +24,14 @@ try {
const titles = () =>
p.locator("[data-note-row] [data-note-title]").evaluateAll((els) => els.map((e) => e.textContent.trim()));
- const addChapter = async (name) => {
+ /* The colour is chosen while the chapter is being made. It is what tells one
+ chapter from the next, and having to right-click a fresh one to say which
+ it is meant two visits for a single decision. */
+ const addChapter = async (name, colour = null) => {
await p.locator("[data-add-chapter]").click();
await p.waitForTimeout(600);
await p.getByLabel("Chapter name").fill(name);
+ if (colour) await p.locator(`[data-colour-option="${colour}"]`).click();
await p.getByRole("button", { name: "Create", exact: true }).click();
await p.waitForTimeout(2000);
};
@@ -66,6 +70,31 @@ try {
const server = (await api(p, "GET", "/chapters")).body;
check(server.length === 1 && server[0].name === "Rechnungen", "the server has it", JSON.stringify(server));
+ /* ── in an empty notebook too ──
+ * "Nothing here" was asked of the notes alone, so a notebook whose only
+ * contents were chapters showed the empty-list panel with the chapters hidden
+ * behind it: making a chapter in a fresh notebook appeared to do nothing. */
+ await createNotebook(p, "Leeres Buch");
+ await p.locator("button").filter({ hasText: "Leeres Buch" }).first().click();
+ await p.waitForTimeout(1000);
+ await addChapter("Nur ein Kapitel", "#a371f7");
+ check(
+ (await p.locator("[data-chapter-row]").count()) === 1,
+ "a chapter shows in a notebook that has no notes at all",
+ String(await p.locator("[data-chapter-row]").count())
+ );
+ const madeWithColour = (await api(p, "GET", "/chapters")).body.find((c) => c.name === "Nur ein Kapitel");
+ check(
+ madeWithColour?.color === "#a371f7",
+ "and the colour picked while making it is stored with it",
+ String(madeWithColour?.color)
+ );
+ check(
+ (await p.locator("[data-no-loose-notes]").count()) === 1,
+ "and the list says why it is otherwise empty rather than looking broken"
+ );
+ await openNotebook();
+
/* ── filing a note ── */
check(
(await p.locator("[data-move-note]").count()) === 0,
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 7a0ea72..c7fe846 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -640,9 +640,17 @@ export default function App() {
setInChapter(!!next);
};
+ /* Named and coloured in one go. The colour is what tells one chapter from the
+ next, so asking for it while the chapter is being made is one decision
+ rather than a second visit through the right-click menu. */
const addChapter = () => {
if (!activeNotebook) return;
- setNamingChapter({ title: "New chapter", value: "", onSave: (name) => data.createChapter(activeNotebook.id, name) });
+ setNamingChapter({
+ title: "New chapter",
+ value: "",
+ color: null,
+ onSave: (name, color) => data.createChapter(activeNotebook.id, name, color),
+ });
};
const chapterContextMenu = (e, chapter) => {
@@ -653,24 +661,25 @@ export default function App() {
y: e.clientY,
items: [
{
- label: "Rename",
+ label: "Rename or recolour",
icon: I.edit,
action: () =>
setNamingChapter({
- title: "Rename chapter",
+ title: "Chapter",
value: chapter.name,
- onSave: (name) => data.renameChapter(chapter.id, name),
+ color: chapter.color ?? null,
+ onSave: (name, color) => data.editChapter(chapter.id, { name, color }),
}),
},
{
label: "Colour",
icon: I.edit,
submenu: [
- { label: "None", colorDot: T.faint, action: () => data.recolourChapter(chapter.id, null) },
+ { label: "None", colorDot: T.faint, action: () => data.editChapter(chapter.id, { color: null }) },
...CHAPTER_COLORS.map((c) => ({
label: c.label,
colorDot: c.value,
- action: () => data.recolourChapter(chapter.id, c.value),
+ action: () => data.editChapter(chapter.id, { color: c.value }),
})),
],
},
@@ -769,11 +778,6 @@ export default function App() {
paddingLeft: 8,
}}
>
- {inChapter && (
-