Refine scheduled task dashboard UX (#135)

* add gated codex goals support

* sync README SDK versions

* bump claude agent sdk

* add codex goals settings toggle

* reuse status dashboard message and simplify settings actions

* refine settings page UX

* fix settings nav hash routing

* add dashboard UX verification

* refine dashboard settings and inbox UX

* remove inbox top-level navigation

* remove dashboard health top-level navigation

* fix: allow runtime image attachment paths

* feat: configure attachment allowlist dirs

* fix: clean duplicate dashboard status messages

* fix: poll dashboard duplicate cleanup between status updates

* fix: delete dashboard duplicates on create

* Refine settings IA with tabbed sections

* Add read-only runtime inventory settings

* Fix runtime inventory MCP JSON parsing

* Add room skill settings inventory

* Add room skill setting mutations

* Apply room skill overrides to runner spawn

* Refine scheduled task dashboard UX
This commit is contained in:
Eyejoker
2026-05-04 16:38:21 +09:00
committed by GitHub
parent 0a8decde8c
commit 8c24082b19
6 changed files with 1000 additions and 547 deletions

View File

@@ -162,6 +162,8 @@ async function main() {
},
);
await runScheduledBoardScenario(browser, baseUrl);
await runScenario(
'health route redirects to rooms and degraded state is conditional',
browser,
@@ -196,6 +198,29 @@ async function main() {
}
}
async function runScheduledBoardScenario(browser: Browser, baseUrl: string) {
await runScenario(
'scheduled board surfaces next task without empty lanes',
browser,
baseUrl,
async (page) => {
await page.goto(new URL('/#/scheduled', baseUrl).toString(), {
waitUntil: 'networkidle',
});
await assertVisible(page.locator('#scheduled .task-command-center'));
await assertVisible(page.locator('#scheduled .task-create-form'));
await assertVisible(page.getByText('Nightly cleanup').first());
await assertVisible(page.getByText('*/15 * * * *').first());
assert.equal(await page.locator('#scheduled .task-card').count(), 3);
assert.equal(
await page.locator('#scheduled .task-group-empty').count(),
0,
);
},
);
}
async function startDashboardServer(): Promise<ViteDevServer> {
const port = Number(process.env.DASHBOARD_UX_PORT ?? 5175);
const server = await createServer({
@@ -306,7 +331,7 @@ async function handleMockApi(route: Route, state: MockApiState) {
}
if (method === 'GET' && url.pathname === '/api/tasks') {
await fulfillJson(route, []);
await fulfillJson(route, mockTasks());
return;
}
@@ -532,6 +557,52 @@ function mockStatusSnapshot() {
};
}
function mockTask(overrides: Record<string, unknown>) {
return {
agentType: 'codex',
chatJid: 'room@example',
ciMetadata: null,
ciProvider: null,
contextMode: 'group',
createdAt: MOCK_TIME,
groupFolder: 'ejclaw',
id: 'task-fixture',
isWatcher: false,
lastResult: 'ok',
lastRun: MOCK_TIME,
nextRun: new Date(Date.now() + 30 * 60_000).toISOString(),
promptLength: 28,
promptPreview: 'Nightly cleanup',
scheduleType: 'cron',
scheduleValue: '*/15 * * * *',
status: 'active',
suspendedUntil: null,
...overrides,
};
}
function mockTasks() {
return [
mockTask({ id: 'task-nightly-cleanup' }),
mockTask({
ciProvider: 'github',
id: 'task-ci-watch',
isWatcher: true,
promptPreview: 'Watch PR #133',
scheduleType: 'interval',
scheduleValue: '5m',
}),
mockTask({
id: 'task-paused-report',
nextRun: null,
promptPreview: 'Weekly report',
scheduleValue: '0 9 * * 1',
status: 'paused',
suspendedUntil: new Date(Date.now() + 2 * 3_600_000).toISOString(),
}),
];
}
function runtimeSkill(name: string, description: string, skillPath: string) {
return { name, description, path: skillPath };
}