read the task category order from the items store
CATEGORY_ORDER hardcoded Video/Audio/Cleanup in the frontend, so adding a category meant a code change. The order now lives in categories.yaml at the root of the items store and reaches the client via GET /tasks/categories — the platform no longer knows any category by name. The endpoint is declared before /:name, which would otherwise match "categories". Categories used by a task but absent from the file still work: they sort alphabetically after the listed ones, and Other stays last. Menus consume grouped tasks rather than grouping them per row. Groups are built from the tasks and the file only ranks them, so a category listed with no matching tasks cannot produce an empty submenu — locked in by tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6a77ec22df
commit
5f7d574dec
@@ -1,6 +1,6 @@
|
||||
import { readdir, mkdir, rm } from 'node:fs/promises';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { itemsDir } from '../../data-path';
|
||||
import { itemsDir, OFFICER_ITEMS_DIR } from '../../data-path';
|
||||
|
||||
// File-backed task store. Every task is a directory under $OFFICER_ITEMS_DIR/tasks/<dirName>/ with a
|
||||
// TASK.md (metadata + prose body) and, for script-mode tasks, a sibling implementation file. There are
|
||||
@@ -149,6 +149,25 @@ async function readImplementation(dirName: string, language: string | null): Pro
|
||||
return (await file.exists()) ? file.text() : null;
|
||||
}
|
||||
|
||||
// categories.yaml at the root of the items store lists the task categories in the order they should
|
||||
// appear in the file browser's Run Task submenu. It is optional and purely presentational: the
|
||||
// platform never decides a task's category, only how known ones sort. A missing, empty or malformed
|
||||
// file leaves everything to alphabetical ordering.
|
||||
export async function readCategoryOrder(): Promise<string[]> {
|
||||
const file = Bun.file(join(OFFICER_ITEMS_DIR, 'categories.yaml'));
|
||||
if (!(await file.exists())) return [];
|
||||
try {
|
||||
const parsed = YAML.parse(await file.text());
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed
|
||||
.filter((entry): entry is string => typeof entry === 'string')
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function listTasks(): Promise<TaskSummary[]> {
|
||||
let entries;
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createRouter } from '../../create-router';
|
||||
import { listTasks, getTaskByDirName, createTask, deleteTask } from './task-files';
|
||||
import { listTasks, getTaskByDirName, createTask, deleteTask, readCategoryOrder } from './task-files';
|
||||
|
||||
type TriggerConfig = { type: 'file'; extensions: string[] } | { type: 'directory' };
|
||||
|
||||
@@ -20,6 +20,11 @@ tasksRouter.get('/', async (ctx) => {
|
||||
return ctx.json(tasks);
|
||||
});
|
||||
|
||||
// Must be declared before '/:name', which would otherwise match "categories".
|
||||
tasksRouter.get('/categories', async (ctx) => {
|
||||
return ctx.json(await readCategoryOrder());
|
||||
});
|
||||
|
||||
tasksRouter.get('/:name', async (ctx) => {
|
||||
const name = ctx.req.param('name');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user