From 6968260f540d924f966cb93adaa5c167d1e6f73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 07:13:29 +0100 Subject: [PATCH] a mis-pointed project is a sentence, not a 500 reported as a permission problem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the owner pointed a project at the platform's repo root — an easy mistake, since `todo/` sits inside one — and got "the todo board is owner-only, and this account was refused". three things were wrong and only one of them was the path. A THEME IS NOW A DIRECTORY WITH AN `_index.md`. readTree assumed every subdirectory was one, which was safe when the only possible root was the platform's own todo folder and is not safe now. against a repo root it read `src/_index.md`, threw ENOENT, and the route 500'd. skipping non-themes is also just correct for a real todo folder, which may hold a README beside its themes. THE ERROR MESSAGE WAS A LIE IT INHERITED. "owner-only, and this account was refused" was true of `/todos` in the platform, where the permission was `kind: 'admin'`. here it is grantable and there is no such gate — so the sentence survived the extraction describing a rule that no longer exists, and the first real failure it met was reported as a permission problem. it shows the actual error now. a wrong explanation is worse than none: it sent the owner to check their own account when the answer was in the panel beside them. AND AN EMPTY FOLDER SAYS SO. no themes at all is almost always this same mistake, so it names it — a theme is a directory with an `_index.md`, and if you pointed at a repository the folder you want is usually `todo/` inside it. an empty board otherwise reads as "no work outstanding", which is the opposite of the truth. verified: repo root now 200 with 0 themes and the explanation; the real folder reads 38 themes and 139 items. Co-Authored-By: Claude Opus 5 --- api/tree.ts | 12 +++++++++++- manifest.ts | 2 +- web/BoardPanel.tsx | 45 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/api/tree.ts b/api/tree.ts index 10918d0..948e219 100644 --- a/api/tree.ts +++ b/api/tree.ts @@ -1,4 +1,4 @@ -import { readFileSync, readdirSync, writeFileSync, statSync } from 'node:fs'; +import { existsSync, readFileSync, readdirSync, writeFileSync, statSync } from 'node:fs'; import { join, resolve } from 'node:path'; // The todo tree: `todo//.md`, one item per file, frontmatter + hand-written prose. @@ -88,6 +88,16 @@ export function patchFrontmatter(text: string, updates: Record): export function readTree(root: string): { themes: TodoTheme[] } { const themes = readdirSync(root) .filter((d) => statSync(join(root, d)).isDirectory()) + /* + A THEME is a directory with an `_index.md`. Anything else under the folder is ignored. + + This used to assume every subdirectory was one, which was safe when the only possible root was the + platform's own `todo/`. It is not safe now: point a project at a repository root — an easy mistake, + since `todo/` sits inside one — and this read `src/_index.md`, threw ENOENT, and the board showed a + 500 that the panel reported as a permission problem. Skipping is also just correct for a real todo + folder, which may hold a README or a script beside its themes. + */ + .filter((d) => existsSync(join(root, d, '_index.md'))) .sort() .map((theme) => { const files = readdirSync(join(root, theme)).filter((f) => f.endsWith('.md')); diff --git a/manifest.ts b/manifest.ts index d9efc7d..a98d28a 100644 --- a/manifest.ts +++ b/manifest.ts @@ -33,7 +33,7 @@ import type { PluginManifest } from '@@/plugins/manifest'; */ export const manifest: PluginManifest = { publisher: 'officerdev', - version: '2026.08.29.01', + version: '2026.08.29.02', platform: '>=1.0.0', label: 'Kanban', diff --git a/web/BoardPanel.tsx b/web/BoardPanel.tsx index 0015378..ba16b3e 100644 --- a/web/BoardPanel.tsx +++ b/web/BoardPanel.tsx @@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { AlertTriangle, CalendarClock, X } from 'lucide-react'; import { cn } from '@/lib/utils'; +import { errorText } from 'helpers/error-text'; import type { TodoItem } from './useKanban'; import { useSelectedProject, useTodoItem, useTodoPatch, useTodoTree } from './useKanban'; @@ -54,16 +55,52 @@ export const BoardPanel = () => { return qs ? `?${qs}` : '.'; }; - if (error) { + if (project === null) { return (
- The todo board is owner-only, and this account was refused. Nothing here is broken — there is simply nothing to - show. + Choose a project on the left, or add one. +
+ ); + } + + /* + THE ERROR, not a guess at it. + + This said "the todo board is owner-only, and this account was refused" — true of the version in the + platform, where `/todos` was `kind: 'admin'`, and false here, where the permission is grantable. It + survived the extraction as a sentence describing a rule that no longer exists, and the first real + failure it met was a 500 from a mis-pointed project, which it reported as a permission problem. + + A wrong explanation is worse than none: it sends somebody to check their account when the answer is + on screen in the panel beside them. + */ + if (error) { + return ( +
+ This board could not be read. + {errorText(error, 'unknown error')}
); } if (isLoading || !data) { - return
Reading todo/…
; + return
Reading…
; + } + + /* + No themes at all is almost always the same mistake — the project points at a repository rather than + at the folder inside it that holds the themes. Said here, where somebody is looking, rather than left + as an empty board that reads like "no work outstanding". + */ + if (data.themes.length === 0) { + return ( +
+ No themes in this folder. + + A theme is a directory with an _index.md in it. If you pointed this at a repository, the folder + you want is usually todo/ inside it. + +
+ ); } const themes = data.themes.filter((t) => t.items.length > 0);