From 57cbf678e3dde1f7fbcf5c5c3873cc18408e48e6 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 11 Sep 2026 09:47:13 +0200 Subject: [PATCH] fix(memory): show a welcome page instead of 404 on a new wiki Opening the family wiki right after install returned 404. The install output tells you to go there, so the first thing a new famstack did was hand you a broken link, and it stayed broken until the next night. The seeded home page was being deleted on every start. Cleanup treated any page containing the generated splice marker as a leftover projection artifact, but that marker only brackets a region, and the welcome page deliberately reserves an empty one for the wiki to fill in. Cleanup now keys off `generated: true` frontmatter, which is what marks a whole page as generated and what the generator writes on every page it publishes. The reserved region also explained how to fill it in inside an HTML comment, where nobody could read it. It now says so on the page, with the command to run if you would rather not wait for the nightly refresh. --- stacklets/memory/lib.py | 31 ++++++++++++++++---- stacklets/memory/seeds/index.md | 11 ++++++- tests/stacklets/test_memory_install_seeds.py | 30 +++++++++++++++++-- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/stacklets/memory/lib.py b/stacklets/memory/lib.py index e5076bf1..a31b3e0a 100644 --- a/stacklets/memory/lib.py +++ b/stacklets/memory/lib.py @@ -112,7 +112,6 @@ ) BRAIN_SEED_COMMIT_MESSAGE = "seed: initial brain projection scaffold" BRAIN_MIGRATION_TOKEN_NAME = "memory-brain-migration" -GENERATED_PAGE_MARKER = "" ONTOLOGY_PATH_IN_REPO = "ontology.toml" INSTALL_COMMIT_MESSAGE = "seed: initial memory from famstack {version}" @@ -1227,6 +1226,25 @@ def seed_brain( return {"created": created, "skipped": skipped} +def _declares_generated(content: str) -> bool: + """True when a page's frontmatter declares the whole file generated. + + The splice marker only brackets a *region*, and a hand-written page may + reserve one for the wiki to fill in later — the seeded root `index.md` + does exactly that. Treating the marker as proof of a generated page + deleted that welcome page on every start, which left a fresh install + serving 404 at the wiki root until the first nightly sweep. + + `generated: true` is the whole-page declaration, and the generator + stamps it on every page it publishes (`_with_generated_marker`), so + every real projection artifact still matches. + """ + value = _parse_frontmatter(content).get("generated") + if value is True: + return True + return isinstance(value, str) and value.strip().lower() == "true" + + def purge_generated_memory_pages( client: ForgejoClient, *, @@ -1236,9 +1254,10 @@ def purge_generated_memory_pages( B1 moves generation to `family/brain`, but upgraded instances can already have generated wiki pages in the source repo from pre-B1 - runs. The migration is marker-based so it does not depend on stale - path conventions: any markdown file with the generated splice marker - is a projection artifact and is removed from memory. + runs. The migration keys off the page's own frontmatter rather than + stale path conventions: any markdown file declaring `generated: true` + is a projection artifact and is removed from memory. See + `_declares_generated` for why the splice marker alone is not enough. """ deleted: list[str] = [] tree = client.list_tree(REPO_OWNER, REPO_NAME) @@ -1252,7 +1271,7 @@ def purge_generated_memory_pages( if not existing: continue content = existing.get("content", "") - if GENERATED_PAGE_MARKER not in content: + if not _declares_generated(content): continue client.delete_file( REPO_OWNER, REPO_NAME, path, @@ -1281,7 +1300,7 @@ def purge_local_generated_memory_pages(vault_path: Path) -> dict: content = md.read_text(encoding="utf-8", errors="ignore") except OSError: continue - if GENERATED_PAGE_MARKER not in content: + if not _declares_generated(content): continue try: rel = str(md.relative_to(vault_path)) diff --git a/stacklets/memory/seeds/index.md b/stacklets/memory/seeds/index.md index 0283bbdd..3ba164ab 100644 --- a/stacklets/memory/seeds/index.md +++ b/stacklets/memory/seeds/index.md @@ -15,6 +15,15 @@ To add or edit a page, open the **family/memory** repository in Forgejo. Every change is saved as a version you can always look back on. - +## Your family overview + +Nothing has been summarised yet. The wiki writes this section for you from +the documents you file and the notes you save, and refreshes it every night. + +To build it now instead of waiting, run: + +```sh +stack memory wiki +``` diff --git a/tests/stacklets/test_memory_install_seeds.py b/tests/stacklets/test_memory_install_seeds.py index f01913da..3841531e 100644 --- a/tests/stacklets/test_memory_install_seeds.py +++ b/tests/stacklets/test_memory_install_seeds.py @@ -382,7 +382,7 @@ def test_deletes_generated_markdown_from_memory(self, httpserver): method="GET", ).respond_with_json(_file_response( "index.md", - "---\ntitle: Home\n---\n\nbody\n\n", + "---\ntitle: Home\ngenerated: true\n---\n\nbody\n\n", )) httpserver.expect_request( f"/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/contents/index.md", @@ -393,7 +393,7 @@ def test_deletes_generated_markdown_from_memory(self, httpserver): method="GET", ).respond_with_json(_file_response( "homer/about.md", - "---\ntitle: Homer\n---\n\nbody\n\n", + "---\ntitle: Homer\ngenerated: true\n---\n\nbody\n\n", )) httpserver.expect_request( f"/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/contents/homer/about.md", @@ -419,7 +419,7 @@ def test_deletes_generated_markdown_but_keeps_readme_and_sources(self, tmp_path) generated = tmp_path / "homer" / "about.md" generated.parent.mkdir(parents=True) generated.write_text( - "---\ntitle: Homer\n---\n\nbody\n", + "---\ntitle: Homer\ngenerated: true\n---\n\nbody\n", encoding="utf-8", ) readme = tmp_path / "family" / "correspondents" / "README.md" @@ -435,3 +435,27 @@ def test_deletes_generated_markdown_but_keeps_readme_and_sources(self, tmp_path) assert not generated.exists() assert readme.exists() assert source.exists() + + def test_keeps_a_seeded_page_that_merely_reserves_a_generated_region( + self, tmp_path, + ): + """The seeded root `index.md` is hand-written: a welcome page that + reserves an empty generated region for the wiki to fill in later. + + It is not a projection artifact, and deleting it is what left a + fresh install serving 404 at the wiki root until the first nightly + sweep. Only frontmatter saying `generated: true` marks a whole page + as generated, and the generator writes that on every page it + publishes. + """ + seeded = tmp_path / "index.md" + seeded.write_text( + "---\ntitle: Family Memory\n---\n\nWelcome to your family's memory.\n" + "\n\n", + encoding="utf-8", + ) + + result = purge_local_generated_memory_pages(tmp_path) + + assert result == {"deleted": []} + assert seeded.exists()