diff --git a/assets/images/hzone/2026/05/20260507-133807-4a746efb-e97f-4492-a281-a4dd2d3fa509.jpeg b/assets/images/hzone/2026/05/20260507-133807-4a746efb-e97f-4492-a281-a4dd2d3fa509.jpeg deleted file mode 100755 index 6ad9e04..0000000 Binary files a/assets/images/hzone/2026/05/20260507-133807-4a746efb-e97f-4492-a281-a4dd2d3fa509.jpeg and /dev/null differ diff --git a/assets/images/hzone/2026/05/20260507-134236-example-png-image.png b/assets/images/hzone/2026/05/20260507-134236-example-png-image.png deleted file mode 100755 index d63b4a2..0000000 Binary files a/assets/images/hzone/2026/05/20260507-134236-example-png-image.png and /dev/null differ diff --git a/assets/images/hzone/2026/05/20260507-134526-example-png-image.png b/assets/images/hzone/2026/05/20260507-134526-example-png-image.png deleted file mode 100755 index d63b4a2..0000000 Binary files a/assets/images/hzone/2026/05/20260507-134526-example-png-image.png and /dev/null differ diff --git a/assets/images/hzone/2026/05/20260507-135058-example-png-image.png b/assets/images/hzone/2026/05/20260507-135058-example-png-image.png deleted file mode 100755 index d63b4a2..0000000 Binary files a/assets/images/hzone/2026/05/20260507-135058-example-png-image.png and /dev/null differ diff --git a/assets/images/hzone/2026/05/20260507-140053-example-png-image.png b/assets/images/hzone/2026/05/20260507-140053-example-png-image.png deleted file mode 100755 index d63b4a2..0000000 Binary files a/assets/images/hzone/2026/05/20260507-140053-example-png-image.png and /dev/null differ diff --git a/assets/images/hzone/2026/05/20260507-140101-example-png-image.png b/assets/images/hzone/2026/05/20260507-140101-example-png-image.png deleted file mode 100755 index d63b4a2..0000000 Binary files a/assets/images/hzone/2026/05/20260507-140101-example-png-image.png and /dev/null differ diff --git a/authoring_server.py b/authoring_server.py index 8ce12ed..e3acfa3 100755 --- a/authoring_server.py +++ b/authoring_server.py @@ -24,13 +24,58 @@ from typing import Any from urllib.parse import parse_qs, urlparse -ROOT = Path(__file__).resolve().parent +def looks_like_content_root(path: Path) -> bool: + return (path / "authoring_server.py").exists() and ( + (path / "blogs").exists() + or (path / "posts").exists() + or (path / "lima").exists() + ) + + +def resolve_root() -> Path: + env_root = os.environ.get("AUTHOR_ROOT") + if env_root: + return Path(env_root).expanduser().resolve() + candidates = [ + Path.cwd(), + Path(__file__).resolve().parent, + ] + workspace = os.environ.get("GITHUB_WORKSPACE") + if workspace: + candidates.insert(0, Path(workspace)) + for base in list(candidates): + candidates.extend(base.parents) + seen = set() + for candidate in candidates: + resolved = candidate.expanduser().resolve() + if resolved in seen: + continue + seen.add(resolved) + if looks_like_content_root(resolved): + return resolved + return Path(__file__).resolve().parent + + +ROOT = resolve_root() BLOGS_DIR = ROOT / "blogs" POSTS_DIR = ROOT / "posts" LIMA_DIR = ROOT / "lima" HZONE_ASSETS_DIR = ROOT / "assets" / "images" / "hzone" +EXCLUDED_CONTENT_DIR_NAMES = { + ".agents", + ".codex", + ".git", + ".packages", + ".venv", + "__pycache__", + "assets", + "backups", + "output", + "tags", +} GENERATED_ORG_NAMES = { "blogs-list.org", + "books-list.org", "posts-list.org", "career-list.org", "sitemap.org", @@ -249,12 +294,16 @@ def safe_relative_path(path: str) -> Path: full = (ROOT / rel).resolve() if not full.is_relative_to(ROOT): raise ValueError("Path must stay inside this repository.") - if full.suffix == ".org" and (full.is_relative_to(BLOGS_DIR) or full.is_relative_to(POSTS_DIR)): + if full.name in GENERATED_CONTENT_NAMES or "sync-conflict" in full.name: + raise ValueError("Generated and sync-conflict files are not editable here.") + rel_parts = full.relative_to(ROOT).parts + if any(part in EXCLUDED_CONTENT_DIR_NAMES for part in rel_parts): + raise ValueError("This path is outside the editable content folders.") + if full.suffix == ".org": return full if full.suffix == ".md" and full.is_relative_to(LIMA_DIR): return full - raise ValueError("Only .org files under blogs/posts and .md files under lima can be edited here.") - return full + raise ValueError("Only .org content files and .md files under lima can be edited here.") def safe_target_path(path: str, slug: str, page_type: str) -> Path: @@ -310,7 +359,12 @@ def read_page(path: Path) -> ContentPage: in_header = False body_lines.append(line) rel = path.relative_to(ROOT).as_posix() - page_type = "blog" if path.is_relative_to(BLOGS_DIR) else "post" + if path.is_relative_to(BLOGS_DIR): + page_type = "blog" + elif path.is_relative_to(POSTS_DIR): + page_type = "post" + else: + page_type = "page" slug = meta.get("SLUG") or path.stem tags = normalise_tags(meta.get("FILETAGS", "")) return ContentPage( @@ -346,33 +400,34 @@ def page_to_dict(page: ContentPage) -> dict[str, Any]: def list_pages() -> list[dict[str, Any]]: pages = [] - for base, page_type, extension in ( - (BLOGS_DIR, "blog", "*.org"), - (POSTS_DIR, "post", "*.org"), - (LIMA_DIR, "lima", "*.md"), - ): - if not base.exists(): + org_paths = [] + if ROOT.exists(): + for path in ROOT.rglob("*.org"): + rel_parts = path.relative_to(ROOT).parts + if any(part in EXCLUDED_CONTENT_DIR_NAMES for part in rel_parts): + continue + org_paths.append(path) + md_paths = list(LIMA_DIR.rglob("*.md")) if LIMA_DIR.exists() else [] + for path in sorted(org_paths + md_paths): + if path.name in GENERATED_CONTENT_NAMES or "sync-conflict" in path.name: continue - for path in sorted(base.rglob(extension)): - if path.name in GENERATED_CONTENT_NAMES or "sync-conflict" in path.name: - continue - try: - page = read_page(path) - except UnicodeDecodeError: - continue - parsed = parse_org_datetime(page.date) - pages.append( - { - "path": page.path, - "pageType": page_type, - "title": page.title, - "slug": page.slug, - "tags": page.tags, - "date": page.date, - "format": page.format, - "timestamp": parsed.timestamp() if parsed else path.stat().st_mtime, - } - ) + try: + page = read_page(path) + except UnicodeDecodeError: + continue + parsed = parse_org_datetime(page.date) + pages.append( + { + "path": page.path, + "pageType": page.page_type, + "title": page.title, + "slug": page.slug, + "tags": page.tags, + "date": page.date, + "format": page.format, + "timestamp": parsed.timestamp() if parsed else path.stat().st_mtime, + } + ) return sorted(pages, key=lambda item: item["timestamp"], reverse=True) @@ -396,7 +451,9 @@ def target_path(data: dict[str, Any], existing_path: str | None) -> Path: return folder / f"{slug}.org" if page_type == "lima": return LIMA_DIR / f"{slug}.md" - raise ValueError("pageType must be blog, post, or lima.") + if page_type == "page": + return ROOT / f"{slug}.org" + raise ValueError("pageType must be blog, post, page, or lima.") def render_markdown(data: dict[str, Any]) -> str: @@ -669,9 +726,10 @@ APP_HTML = r"""
@@ -688,6 +746,7 @@ APP_HTML = r""" @@ -819,11 +878,14 @@ APP_HTML = r""" const query = $("#search").value.toLowerCase(); const type = $("#typeFilter").value; pagesBox.innerHTML = ""; - state.pages + const matches = state.pages .filter((page) => (!type || page.pageType === type)) .filter((page) => `${page.title} ${page.path} ${page.tags.join(" ")}`.toLowerCase().includes(query)) - .slice(0, 120) - .forEach((page) => { + .slice(0, 120); + if (!matches.length) { + pagesBox.innerHTML = `
No files matched.
`; + } + matches.forEach((page) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "page-item"; @@ -938,8 +1000,12 @@ APP_HTML = r""" } async function refreshPages() { - state.pages = await api("/api/pages"); - renderPages(); + try { + state.pages = await api("/api/pages"); + renderPages(); + } catch (err) { + pagesBox.innerHTML = `
${html(err.message)}
`; + } } async function refreshBuild() { @@ -1002,6 +1068,10 @@ APP_HTML = r""" editor.targetPath.value = `lima/${slug}.md`; return; } + if (editor.pageType.value === "page") { + editor.targetPath.value = `${slug}.org`; + return; + } if (editor.pageType.value === "post") { const section = slugify(editor.section.value || ""); editor.targetPath.value = section === "untitled" ? `posts/${slug}.org` : `posts/${section}/${slug}.org`; @@ -1299,6 +1369,7 @@ def main() -> None: port = int(os.environ.get("AUTHOR_PORT", "8765")) server = ThreadingHTTPServer(("127.0.0.1", port), Handler) print(f"Authoring UI running at http://127.0.0.1:{port}") + print(f"Content root: {ROOT}") print("Press Ctrl-C to stop.") try: server.serve_forever() diff --git a/blogs/2026/05-may/using-codex-07-05-26.org b/blogs/2026/05-may/using-codex-07-05-26.org new file mode 100644 index 0000000..a9057d2 --- /dev/null +++ b/blogs/2026/05-may/using-codex-07-05-26.org @@ -0,0 +1,10 @@ +#+TITLE: Using Codex +#+OPTIONS: num:nil +#+DATE: <2026-05-07 Thu 16:12> +#+filetags: :life: +#+COMMENTS: t +#+SLUG: using-codex-07-05-26 + +Recently started to play around with codex, installing the CLI, and having it do a few things I've been wanting to work on. So far it's exceeded my expectations. This page [[https://author.zainezq.com][Author]] has been created by Codex using a single python script. + +I also had it create a powershell script to do an analysis of the build pipelines hosted in gitea. diff --git a/blogs/blogs-list.org b/blogs/blogs-list.org index 7dcb3d1..89da91d 100755 --- a/blogs/blogs-list.org +++ b/blogs/blogs-list.org @@ -6,6 +6,7 @@ See the categories: @@html:Categories@@ * 2026 ** May 2026 +- [[file:2026/05-may/using-codex-07-05-26.org][Using Codex]] @@html:07-05-2026 16:12@@ @@html:life@@ - [[file:2026/05-may/03-05-week-review.org][[03-05-2026] - Weekly Review]] @@html:03-05-2026 12:00@@ @@html:review@@ - [[file:2026/05-may/sun-soak-01-05.org][Soaking in the sun]] @@html:01-05-2026 12:27@@ @@html:life@@ diff --git a/home/categories.org b/home/categories.org index d344d61..c24238f 100755 --- a/home/categories.org +++ b/home/categories.org @@ -7,7 +7,7 @@ - [[file:../tags/insights.org][@@html:insights@@]] (4) - [[file:../tags/introduction.org][@@html:introduction@@]] (3) - [[file:../tags/learning.org][@@html:learning@@]] (16) -- [[file:../tags/life.org][@@html:life@@]] (28) +- [[file:../tags/life.org][@@html:life@@]] (29) - [[file:../tags/maths.org][@@html:maths@@]] (1) - [[file:../tags/notes.org][@@html:notes@@]] (17) - [[file:../tags/reading.org][@@html:reading@@]] (1) diff --git a/lima/index.md b/lima/index.md index d8e997e..ad3430f 100755 --- a/lima/index.md +++ b/lima/index.md @@ -8,7 +8,4 @@ I made quite a few changes. It all started when I realised that not everyone kno - One other thing is the [server dashboard](https://zone.zainezq.com/), the purpose of this is so that whenever any changes are made here, you can just click the **Update Website** button which rebuilds the website (cool isn’t it. Had to meddle with makefiles and threads). - I probably didn’t explain it as well, but you can create folders and files under the `lima-website` directory. The files must have a `.md` extension at the end (markdown). -- One other thing, a more personal one: what do you think of all this? am i doing too much? am i doing too little? am i overengineering things? am i forcing you to do something you dont wanna do? these little trinkets work for me, but i’m not sure if it would work for someone else, so at any point if you have reservations, let me know okay? - - -![20260507-140053-example-png-image.png](../assets/images/hzone/2026/05/20260507-140053-example-png-image.png) +- One other thing, a more personal one: what do you think of all this? am i doing too much? am i doing too little? am i overengineering things? am i forcing you to do something you dont wanna do? these little trinkets work for me, but i’m not sure if it would work for someone else, so at any point if you have reservations, let me know okay? diff --git a/posts/posts-list.org b/posts/posts-list.org index 7ca7591..51cf344 100755 --- a/posts/posts-list.org +++ b/posts/posts-list.org @@ -4,7 +4,7 @@ See the categories: @@html:Categories@@ * Posts: -- [[file:career/career-list.org][Career List]] @@html:07-05-2026 15:52@@ +- [[file:career/career-list.org][Career List]] @@html:07-05-2026 16:21@@ - [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:14-04-2026 16:36@@ - [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:learning@@ @@html:notes@@ - [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:learning@@ @@html:notes@@ diff --git a/recently-updated.org b/recently-updated.org index bbead26..90878b0 100755 --- a/recently-updated.org +++ b/recently-updated.org @@ -2,6 +2,7 @@ #+OPTIONS: toc:nil num:nil * Recently Updated (top 26 files) +- [[file:blogs/2026/05-may/using-codex-07-05-26.org][Using Codex]] @@html:2026-05-07 16:12@@ - [[file:blogs/2026/05-may/03-05-week-review.org][[03-05-2026] - Weekly Review]] @@html:2026-05-03 12:00@@ - [[file:blogs/2026/05-may/sun-soak-01-05.org][Soaking in the sun]] @@html:2026-05-01 12:27@@ - [[file:blogs/2026/04-april/wise-words-29-04.org][Wise Words I need to engrain]] @@html:2026-04-29 11:31@@ @@ -15,7 +16,6 @@ - [[file:blogs/2026/04-april/16-04-26.org][Rambles]] @@html:2026-04-16 16:02@@ - [[file:posts/posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:2026-04-14 16:36@@ - [[file:home/categories.sync-conflict-20260417-233429-VT6366A.org][Categories]] @@html:2026-04-14 16:36@@ -- [[file:recently-updated.sync-conflict-20260417-233430-VT6366A.org][Recently Updated]] @@html:2026-04-14 16:36@@ - [[file:blogs/2026/04-april/comparison-14-04.org][Comparison is the thief of joy]] @@html:2026-04-14 09:55@@ - [[file:blogs/2026/04-april/12-04-week-review.org][[12-04-2026] - Weekly Review]] @@html:2026-04-12 12:00@@ - [[file:blogs/2026/04-april/starting-new-rotation-08-04-26.org][Starting new rotation]] @@html:2026-04-08 16:15@@ diff --git a/recently-updated.sync-conflict-20260417-233430-VT6366A.org b/recently-updated.sync-conflict-20260417-233430-VT6366A.org deleted file mode 100755 index 81294f0..0000000 --- a/recently-updated.sync-conflict-20260417-233430-VT6366A.org +++ /dev/null @@ -1,30 +0,0 @@ -#+TITLE: Recently Updated -#+OPTIONS: toc:nil num:nil - -* Recently Updated (top 26 files) -- [[file:blogs/2026/04-april/comparison-14-04.org][Comparison is the thief of joy]] @@html:2026-04-14 09:55@@ -- [[file:blogs/2026/04-april/12-04-week-review.org][[12-04-2026] - Weekly Review]] @@html:2026-04-12 12:00@@ -- [[file:blogs/2026/04-april/starting-new-rotation-08-04-26.org][Starting new rotation]] @@html:2026-04-08 16:15@@ -- [[file:blogs/2026/04-april/action-plan-07-04-26.org][Action plan to change teams]] @@html:2026-04-07 10:04@@ -- [[file:blogs/2026/04-april/weekly-target-06-04-26.org][[06-04-2026] - Weekly target]] @@html:2026-04-06 22:53@@ -- [[file:blogs/2026/04-april/making-notes-through-org-noter-06-05.org][Making Notes through Org Noter]] @@html:2026-04-06 14:30@@ -- [[file:blogs/2026/04-april/05-04-week-review.org][[05-04-2026] - Weekly Review]] @@html:2026-04-05 12:00@@ -- [[file:blogs/2026/04-april/joining-new-team-meeting-02-04-2026.org][New team meeting]] @@html:2026-04-02 16:03@@ -- [[file:home/wird-tracker.org][Wird Tracker]] @@html:2026-04-02 11:17@@ -- [[file:blogs/2026/03-march/intellectually-challenging-myself-30-03-26.org][Intellectually challenging oneself]] @@html:2026-03-31 11:14@@ -- [[file:blogs/2026/03-march/cooking-dinner-29-03-26.org][Cooking Dinner (num)]] @@html:2026-03-30 11:49@@ -- [[file:blogs/2026/03-march/29-03-week-review.org][[29-03-2026] - Weekly Review]] @@html:2026-03-29 12:00@@ -- [[file:blogs/2026/03-march/training-on-friday-afternoon-27-03-26.org][Training time on Friday afternoon]] @@html:2026-03-27 15:09@@ -- [[file:blogs/2026/03-march/more-thoughts-regarding-changes-26-03-26.org][More thoughts on schedule changes]] @@html:2026-03-26 12:33@@ -- [[file:blogs/2026/03-march/thoughts-on-life-25-03-26.org][Thoughts on life...]] @@html:2026-03-25 11:37@@ -- [[file:blogs/2026/03-march/quick-lunch-24-03.org][Making Lunch QUiCk!!]] @@html:2026-03-24 15:30@@ -- [[file:blogs/2026/03-march/installing-emacs-23-03.org][Installing Emacs on Windows]] @@html:2026-03-23 16:47@@ -- [[file:blogs/2026/03-march/22-03-week-review.org][[22-03-2026] - Weekly Review]] @@html:2026-03-22 12:00@@ -- [[file:blogs/2026/03-march/setting-a-wird-tracker-19-03.org][Setting a Wird Tracker]] @@html:2026-03-19 13:15@@ -- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]] @@html:2026-03-19 12:43@@ -- [[file:blogs/2026/03-march/fixing-the-dag-18-03.org][DAG fixes]] @@html:2026-03-18 15:01@@ -- [[file:blogs/2026/03-march/feeling-sleepy.org][Feeling extremely sleepy]] @@html:2026-03-16 16:18@@ -- [[file:blogs/2026/03-march/oversleeping-16-03.org][Oversleeping and missing a meeting...]] @@html:2026-03-16 11:21@@ -- [[file:blogs/2026/03-march/15-03-week-review.org][[15-03-2026] - Weekly Review]] @@html:2026-03-15 12:00@@ -- [[file:posts/career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:2026-03-11 17:18@@ -- [[file:posts/career/javascript.org][Understands the Javascript language]] @@html:2026-03-11 16:52@@ diff --git a/sitemap.org b/sitemap.org index edf4da0..c28154e 100755 --- a/sitemap.org +++ b/sitemap.org @@ -1,7 +1,6 @@ #+TITLE: Sitemap - [[file:index.org][Home]] -- [[file:recently-updated.sync-conflict-20260417-233430-VT6366A.org][Recently Updated]] - [[file:wip.org][Work in progress]] - [[file:recently-updated.org][Recently Updated]] - tags @@ -11,14 +10,14 @@ - [[file:tags/learning.org][Tag: learning]] - [[file:tags/notes.org][Tag: notes]] - [[file:tags/review.org][Tag: review]] - - [[file:tags/life.org][Tag: life]] - [[file:tags/website.org][Tag: website]] + - [[file:tags/life.org][Tag: life]] + - [[file:tags/education.org][Tag: education]] - [[file:tags/update.org][Tag: update]] - [[file:tags/insights.org][Tag: insights]] - [[file:tags/emacs.org][Tag: emacs]] - - [[file:tags/education.org][Tag: education]] - - [[file:tags/reading.org][Tag: reading]] - [[file:tags/maths.org][Tag: maths]] + - [[file:tags/reading.org][Tag: reading]] - home - [[file:home/countdown.org][Countdown]] - [[file:home/contact.org][Contact]] diff --git a/tags/life.org b/tags/life.org index 73994ab..8fd9d01 100755 --- a/tags/life.org +++ b/tags/life.org @@ -2,6 +2,7 @@ #+OPTIONS: toc:nil num:nil title:nil * Posts tagged life +- [[file:../blogs/2026/05-may/using-codex-07-05-26.org][Using Codex]] - [[file:../blogs/2026/05-may/sun-soak-01-05.org][Soaking in the sun]] - [[file:../blogs/2026/04-april/wise-words-29-04.org][Wise Words I need to engrain]] - [[file:../blogs/2026/04-april/april-almost-over-28-04.org][April is almost over...]] diff --git a/tests/test_authoring_server.py b/tests/test_authoring_server.py index 6547f6d..ff8c3f9 100755 --- a/tests/test_authoring_server.py +++ b/tests/test_authoring_server.py @@ -14,10 +14,14 @@ class AuthoringServerTestCase(unittest.TestCase): self.blogs = self.root / "blogs" self.posts = self.root / "posts" self.lima = self.root / "lima" + self.home = self.root / "home" + self.tags = self.root / "tags" self.hzone = self.root / "assets" / "images" / "hzone" self.blogs.mkdir() self.posts.mkdir() self.lima.mkdir() + self.home.mkdir() + self.tags.mkdir() patches = { "ROOT": self.root, @@ -71,9 +75,13 @@ class UtilityTests(AuthoringServerTestCase): server.safe_relative_path("lima/index.md"), self.lima / "index.md", ) + self.assertEqual( + server.safe_relative_path("home/notes.org"), + self.home / "notes.org", + ) def test_safe_relative_path_rejects_escapes_and_wrong_locations(self): - for path in ("../secret.org", "/tmp/secret.org", "sitemap.org", "lima/index.org"): + for path in ("../secret.org", "/tmp/secret.org", "sitemap.org", "assets/style.org", "tags/life.org"): with self.subTest(path=path): with self.assertRaises(ValueError): server.safe_relative_path(path) @@ -156,10 +164,12 @@ class PageRenderingTests(AuthoringServerTestCase): (self.posts / "posts-list.org").write_text("#+TITLE: Generated\n", encoding="utf-8") (self.blogs / "note.sync-conflict-1.org").write_text("#+TITLE: Conflict\n", encoding="utf-8") (self.lima / "index.md").write_text("# Lima Home\n", encoding="utf-8") + (self.home / "notes.org").write_text("#+TITLE: Notes\n", encoding="utf-8") + (self.tags / "life.org").write_text("#+TITLE: Tag\n", encoding="utf-8") paths = [page["path"] for page in server.list_pages()] - self.assertEqual(set(paths), {"blogs/keep.org", "lima/index.md"}) + self.assertEqual(set(paths), {"blogs/keep.org", "home/notes.org", "lima/index.md"}) def test_relative_asset_path_is_calculated_from_lima_page_directory(self): self.assertEqual(