feat(ui): Settings view + per-space project cards (status/research/edit/delete) + theming pass
- Settings (#/settings): API tokens (mint/list/revoke), Agents list, Orthos Mode placeholder - Per-space Projects: Void-1-style expandable cards — inline status, ↻ Research (Eithan stub), Edit/New modal, Delete-with-confirm; migration 019 adds research_status/notes/timestamps; POST /api/projects/:id/research stub; GET /api/agent-tokens list - Global +1 font bump; themed scrollbars; larger/bolder themed topbar Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,9 @@ tokensRouter.use(requireOwner);
|
||||
|
||||
router.get('/', asyncWrap(async (_req, res) => { res.json(await repo.list()); }));
|
||||
|
||||
// Token metadata across all agents (label/usage/revocation; never the hash) for Settings.
|
||||
tokensRouter.get('/', asyncWrap(async (_req, res) => { res.json(await repo.listTokenMeta()); }));
|
||||
|
||||
router.post('/',
|
||||
validate({ body: createSchema }),
|
||||
asyncWrap(async (req, res) => {
|
||||
|
||||
@@ -80,6 +80,17 @@ router.patch('/:id',
|
||||
})
|
||||
);
|
||||
|
||||
// Research stub — owner asks Eithan (later) to research this project.
|
||||
router.post('/:id/research',
|
||||
validate({ params: idParams }),
|
||||
asyncWrap(async (req, res) => {
|
||||
if (req.actor?.kind !== 'user') return res.status(403).json({ error: { code: 'owner_only' } });
|
||||
const row = await repo.requestResearch(req.params.id, req.actor);
|
||||
if (!row) throw new NotFoundError('project not found');
|
||||
res.json(row);
|
||||
})
|
||||
);
|
||||
|
||||
router.delete('/:id',
|
||||
requireWrite('project'),
|
||||
validate({ params: idParams }),
|
||||
|
||||
8
lib/db/migrations/019_project_research.sql
Normal file
8
lib/db/migrations/019_project_research.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- 019_project_research.sql — fields for the per-space project research feature.
|
||||
-- The Research button (stub) sets research_status='requested'; the future Eithan
|
||||
-- agent picks those up, sets 'researching' → 'done', and fills research_notes.
|
||||
ALTER TABLE projects ADD COLUMN research_status text NOT NULL DEFAULT 'none'
|
||||
CHECK (research_status IN ('none', 'requested', 'researching', 'done', 'error'));
|
||||
ALTER TABLE projects ADD COLUMN research_notes text;
|
||||
ALTER TABLE projects ADD COLUMN research_requested_at timestamptz;
|
||||
ALTER TABLE projects ADD COLUMN last_researched_at timestamptz;
|
||||
@@ -43,6 +43,18 @@ export async function update(id, patch, actor) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Stub for the future Eithan research agent: flag the project as research-requested.
|
||||
// Eithan will later transition requested → researching → done and fill research_notes.
|
||||
export async function requestResearch(id, actor) {
|
||||
const before = await getById(id);
|
||||
if (!before) return null;
|
||||
const { rows: [r] } = await pool.query(
|
||||
`UPDATE projects SET research_status='requested', research_requested_at=now(), updated_at=now()
|
||||
WHERE id=$1 RETURNING *`, [id]);
|
||||
await recordAudit(actor, 'update', 'project', id, before, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
export async function del(id, actor) {
|
||||
const before = await getById(id);
|
||||
await pool.query(`DELETE FROM projects WHERE id=$1`, [id]);
|
||||
|
||||
Reference in New Issue
Block a user