stagent 0.1.6 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +16 -24
- package/package.json +1 -1
- package/src/app/api/documents/route.ts +21 -2
- package/src/app/api/tasks/route.ts +16 -3
- package/src/app/api/uploads/route.ts +17 -3
- package/src/app/globals.css +156 -120
- package/src/app/inbox/page.tsx +1 -1
- package/src/app/layout.tsx +6 -6
- package/src/app/page.tsx +1 -1
- package/src/app/profiles/[id]/edit/page.tsx +1 -1
- package/src/app/profiles/[id]/page.tsx +1 -1
- package/src/app/profiles/new/page.tsx +1 -1
- package/src/app/profiles/page.tsx +1 -1
- package/src/app/projects/page.tsx +1 -1
- package/src/app/settings/loading.tsx +1 -1
- package/src/app/settings/page.tsx +1 -1
- package/src/app/tasks/new/page.tsx +10 -2
- package/src/app/workflows/[id]/edit/page.tsx +1 -1
- package/src/app/workflows/new/page.tsx +1 -1
- package/src/components/costs/cost-dashboard.tsx +1 -1
- package/src/components/shared/theme-toggle.tsx +1 -1
- package/src/components/tasks/__tests__/kanban-board-persistence.test.tsx +124 -0
- package/src/components/tasks/__tests__/task-create-panel.test.tsx +58 -0
- package/src/components/tasks/ai-assist-panel.tsx +50 -12
- package/src/components/tasks/kanban-board.tsx +201 -5
- package/src/components/tasks/kanban-column.tsx +156 -5
- package/src/components/tasks/task-card.tsx +186 -44
- package/src/components/tasks/task-create-panel.tsx +3 -2
- package/src/components/tasks/task-detail-view.tsx +58 -1
- package/src/components/tasks/task-edit-dialog.tsx +277 -0
- package/src/hooks/__tests__/use-persisted-state.test.ts +57 -0
- package/src/hooks/use-persisted-state.ts +40 -0
- package/src/lib/agents/claude-agent.ts +17 -7
- package/src/lib/agents/runtime/claude-sdk.ts +20 -6
- package/src/lib/agents/runtime/claude.ts +23 -5
- package/src/lib/agents/runtime/openai-codex.ts +14 -1
- package/src/lib/db/bootstrap.ts +17 -32
- package/src/lib/documents/cleanup.ts +3 -2
- package/src/lib/notifications/permissions.ts +4 -2
- package/src/lib/workflows/engine.ts +2 -2
package/dist/cli.js
CHANGED
|
@@ -276,33 +276,25 @@ function bootstrapStagentDatabase(sqlite2) {
|
|
|
276
276
|
CREATE INDEX IF NOT EXISTS idx_learned_context_profile_version ON learned_context(profile_id, version);
|
|
277
277
|
CREATE INDEX IF NOT EXISTS idx_learned_context_change_type ON learned_context(change_type);
|
|
278
278
|
`);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
279
|
+
const addColumnIfMissing = (ddl) => {
|
|
280
|
+
try {
|
|
281
|
+
sqlite2.exec(ddl);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
284
|
+
if (!msg.includes("duplicate column")) {
|
|
285
|
+
console.error("[bootstrap] ALTER TABLE failed:", msg);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
addColumnIfMissing(`ALTER TABLE tasks ADD COLUMN agent_profile TEXT;`);
|
|
283
290
|
sqlite2.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_agent_profile ON tasks(agent_profile);`);
|
|
284
|
-
|
|
285
|
-
sqlite2.exec(`ALTER TABLE tasks ADD COLUMN workflow_id TEXT REFERENCES workflows(id);`);
|
|
286
|
-
} catch {
|
|
287
|
-
}
|
|
291
|
+
addColumnIfMissing(`ALTER TABLE tasks ADD COLUMN workflow_id TEXT REFERENCES workflows(id);`);
|
|
288
292
|
sqlite2.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_workflow_id ON tasks(workflow_id);`);
|
|
289
|
-
|
|
290
|
-
sqlite2.exec(`ALTER TABLE tasks ADD COLUMN schedule_id TEXT REFERENCES schedules(id);`);
|
|
291
|
-
} catch {
|
|
292
|
-
}
|
|
293
|
+
addColumnIfMissing(`ALTER TABLE tasks ADD COLUMN schedule_id TEXT REFERENCES schedules(id);`);
|
|
293
294
|
sqlite2.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_schedule_id ON tasks(schedule_id);`);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
sqlite2.exec(`ALTER TABLE schedules ADD COLUMN assigned_agent TEXT;`);
|
|
300
|
-
} catch {
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
sqlite2.exec(`ALTER TABLE documents ADD COLUMN version INTEGER NOT NULL DEFAULT 1;`);
|
|
304
|
-
} catch {
|
|
305
|
-
}
|
|
295
|
+
addColumnIfMissing(`ALTER TABLE projects ADD COLUMN working_directory TEXT;`);
|
|
296
|
+
addColumnIfMissing(`ALTER TABLE schedules ADD COLUMN assigned_agent TEXT;`);
|
|
297
|
+
addColumnIfMissing(`ALTER TABLE documents ADD COLUMN version INTEGER NOT NULL DEFAULT 1;`);
|
|
306
298
|
}
|
|
307
299
|
function hasLegacyStagentTables(sqlite2) {
|
|
308
300
|
const placeholders = STAGENT_TABLES.map(() => "?").join(", ");
|
package/package.json
CHANGED
|
@@ -3,6 +3,11 @@ import { db } from "@/lib/db";
|
|
|
3
3
|
import { documents, tasks, projects } from "@/lib/db/schema";
|
|
4
4
|
import { eq, and, like, or, desc, sql } from "drizzle-orm";
|
|
5
5
|
|
|
6
|
+
const VALID_DOC_STATUSES = ["uploaded", "processing", "ready", "error"] as const;
|
|
7
|
+
const VALID_DOC_DIRECTIONS = ["input", "output"] as const;
|
|
8
|
+
type DocStatus = typeof VALID_DOC_STATUSES[number];
|
|
9
|
+
type DocDirection = typeof VALID_DOC_DIRECTIONS[number];
|
|
10
|
+
|
|
6
11
|
export async function GET(req: NextRequest) {
|
|
7
12
|
const url = new URL(req.url);
|
|
8
13
|
const taskId = url.searchParams.get("taskId");
|
|
@@ -11,12 +16,26 @@ export async function GET(req: NextRequest) {
|
|
|
11
16
|
const direction = url.searchParams.get("direction");
|
|
12
17
|
const search = url.searchParams.get("search");
|
|
13
18
|
|
|
19
|
+
if (status && !VALID_DOC_STATUSES.includes(status as DocStatus)) {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: `Invalid status. Must be one of: ${VALID_DOC_STATUSES.join(", ")}` },
|
|
22
|
+
{ status: 400 }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (direction && !VALID_DOC_DIRECTIONS.includes(direction as DocDirection)) {
|
|
27
|
+
return NextResponse.json(
|
|
28
|
+
{ error: `Invalid direction. Must be one of: ${VALID_DOC_DIRECTIONS.join(", ")}` },
|
|
29
|
+
{ status: 400 }
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
const conditions = [];
|
|
15
34
|
|
|
16
35
|
if (taskId) conditions.push(eq(documents.taskId, taskId));
|
|
17
36
|
if (projectId) conditions.push(eq(documents.projectId, projectId));
|
|
18
|
-
if (status) conditions.push(eq(documents.status, status as
|
|
19
|
-
if (direction) conditions.push(eq(documents.direction, direction as
|
|
37
|
+
if (status) conditions.push(eq(documents.status, status as DocStatus));
|
|
38
|
+
if (direction) conditions.push(eq(documents.direction, direction as DocDirection));
|
|
20
39
|
|
|
21
40
|
if (search) {
|
|
22
41
|
conditions.push(
|
|
@@ -6,14 +6,24 @@ import { createTaskSchema } from "@/lib/validators/task";
|
|
|
6
6
|
import { processDocument } from "@/lib/documents/processor";
|
|
7
7
|
import { validateRuntimeProfileAssignment } from "@/lib/agents/profiles/assignment-validation";
|
|
8
8
|
|
|
9
|
+
const VALID_TASK_STATUSES = ["planned", "queued", "running", "completed", "failed", "cancelled"] as const;
|
|
10
|
+
type TaskStatus = typeof VALID_TASK_STATUSES[number];
|
|
11
|
+
|
|
9
12
|
export async function GET(req: NextRequest) {
|
|
10
13
|
const url = new URL(req.url);
|
|
11
14
|
const projectId = url.searchParams.get("projectId");
|
|
12
15
|
const status = url.searchParams.get("status");
|
|
13
16
|
|
|
17
|
+
if (status && !VALID_TASK_STATUSES.includes(status as TaskStatus)) {
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ error: `Invalid status. Must be one of: ${VALID_TASK_STATUSES.join(", ")}` },
|
|
20
|
+
{ status: 400 }
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
const conditions = [];
|
|
15
25
|
if (projectId) conditions.push(eq(tasks.projectId, projectId));
|
|
16
|
-
if (status) conditions.push(eq(tasks.status, status as
|
|
26
|
+
if (status) conditions.push(eq(tasks.status, status as TaskStatus));
|
|
17
27
|
|
|
18
28
|
const result = await db
|
|
19
29
|
.select()
|
|
@@ -70,10 +80,13 @@ export async function POST(req: NextRequest) {
|
|
|
70
80
|
.where(eq(documents.id, fileId));
|
|
71
81
|
|
|
72
82
|
// Trigger processing if not already done (fire-and-forget)
|
|
73
|
-
processDocument(fileId).catch(() => {
|
|
83
|
+
processDocument(fileId).catch((err) => {
|
|
84
|
+
console.error(`[tasks] processDocument failed for ${fileId}:`, err);
|
|
85
|
+
});
|
|
74
86
|
}
|
|
75
|
-
} catch {
|
|
87
|
+
} catch (err) {
|
|
76
88
|
// File association is best-effort — don't fail task creation
|
|
89
|
+
console.error("[tasks] File association failed:", err);
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
|
|
@@ -3,6 +3,7 @@ import { writeFile, mkdir } from "fs/promises";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { db } from "@/lib/db";
|
|
5
5
|
import { documents } from "@/lib/db/schema";
|
|
6
|
+
import { eq } from "drizzle-orm";
|
|
6
7
|
import { processDocument } from "@/lib/documents/processor";
|
|
7
8
|
import { getStagentUploadsDir } from "@/lib/utils/stagent-paths";
|
|
8
9
|
|
|
@@ -48,9 +49,22 @@ export async function POST(req: NextRequest) {
|
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
// Fire-and-forget: trigger async document processing
|
|
51
|
-
processDocument(id).catch((err) =>
|
|
52
|
-
console.error(`[upload] Processing failed for ${id}:`, err)
|
|
53
|
-
|
|
52
|
+
processDocument(id).catch(async (err) => {
|
|
53
|
+
console.error(`[upload] Processing failed for ${id}:`, err);
|
|
54
|
+
// Ensure document doesn't stay stuck in "processing" state
|
|
55
|
+
try {
|
|
56
|
+
await db
|
|
57
|
+
.update(documents)
|
|
58
|
+
.set({
|
|
59
|
+
status: "error",
|
|
60
|
+
processingError: err instanceof Error ? err.message : String(err),
|
|
61
|
+
updatedAt: new Date(),
|
|
62
|
+
})
|
|
63
|
+
.where(eq(documents.id, id));
|
|
64
|
+
} catch (dbErr) {
|
|
65
|
+
console.error(`[upload] Failed to update error status for ${id}:`, dbErr);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
54
68
|
|
|
55
69
|
return NextResponse.json(
|
|
56
70
|
{
|
package/src/app/globals.css
CHANGED
|
@@ -185,41 +185,41 @@
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
.dark {
|
|
188
|
-
/* Background —
|
|
189
|
-
--background: oklch(0.
|
|
190
|
-
--foreground: oklch(0.93 0.
|
|
191
|
-
|
|
192
|
-
/* Cards —
|
|
193
|
-
--card: oklch(0.16 0.
|
|
194
|
-
--card-foreground: oklch(0.93 0.
|
|
195
|
-
--popover: oklch(0.
|
|
196
|
-
--popover-foreground: oklch(0.93 0.
|
|
197
|
-
|
|
198
|
-
/* Primary — blue
|
|
199
|
-
--primary: oklch(0.65 0.18
|
|
200
|
-
--primary-foreground: oklch(0.
|
|
201
|
-
|
|
202
|
-
/* Secondary —
|
|
203
|
-
--secondary: oklch(0.
|
|
204
|
-
--secondary-foreground: oklch(0.93 0.
|
|
205
|
-
|
|
206
|
-
/* Muted —
|
|
207
|
-
--muted: oklch(0.
|
|
208
|
-
--muted-foreground: oklch(0.6 0.
|
|
209
|
-
--surface-1: oklch(0.16 0.02
|
|
210
|
-
--surface-2: oklch(0.14 0.
|
|
211
|
-
--surface-3: oklch(0.
|
|
212
|
-
--surface-foreground: oklch(0.93 0.
|
|
213
|
-
|
|
214
|
-
/* Accent —
|
|
215
|
-
--accent: oklch(0.
|
|
216
|
-
--accent-foreground: oklch(0.93 0.
|
|
188
|
+
/* Background — unified hue 250 blue-indigo */
|
|
189
|
+
--background: oklch(0.13 0.02 250);
|
|
190
|
+
--foreground: oklch(0.93 0.01 250);
|
|
191
|
+
|
|
192
|
+
/* Cards — hue 250 translucency */
|
|
193
|
+
--card: oklch(0.16 0.02 250 / 0.6);
|
|
194
|
+
--card-foreground: oklch(0.93 0.01 250);
|
|
195
|
+
--popover: oklch(0.16 0.02 250 / 0.5);
|
|
196
|
+
--popover-foreground: oklch(0.93 0.01 250);
|
|
197
|
+
|
|
198
|
+
/* Primary — blue-indigo hue 250 */
|
|
199
|
+
--primary: oklch(0.65 0.18 250);
|
|
200
|
+
--primary-foreground: oklch(0.13 0.02 250);
|
|
201
|
+
|
|
202
|
+
/* Secondary — unified hue 250 */
|
|
203
|
+
--secondary: oklch(0.19 0.02 250);
|
|
204
|
+
--secondary-foreground: oklch(0.93 0.01 250);
|
|
205
|
+
|
|
206
|
+
/* Muted — consistent hue 250 */
|
|
207
|
+
--muted: oklch(0.19 0.02 250);
|
|
208
|
+
--muted-foreground: oklch(0.6 0.02 250);
|
|
209
|
+
--surface-1: oklch(0.16 0.02 250 / 0.96);
|
|
210
|
+
--surface-2: oklch(0.14 0.02 250 / 0.9);
|
|
211
|
+
--surface-3: oklch(0.13 0.02 250 / 0.84);
|
|
212
|
+
--surface-foreground: oklch(0.93 0.01 250);
|
|
213
|
+
|
|
214
|
+
/* Accent — unified hue 250 */
|
|
215
|
+
--accent: oklch(0.19 0.02 250);
|
|
216
|
+
--accent-foreground: oklch(0.93 0.01 250);
|
|
217
217
|
|
|
218
218
|
--destructive: oklch(0.55 0.2 25);
|
|
219
219
|
--destructive-foreground: oklch(0.98 0.005 260);
|
|
220
220
|
--border: var(--glass-border);
|
|
221
|
-
--input: oklch(0.
|
|
222
|
-
--ring: oklch(0.65 0.18
|
|
221
|
+
--input: oklch(0.16 0.02 250 / 0.4);
|
|
222
|
+
--ring: oklch(0.65 0.18 250);
|
|
223
223
|
|
|
224
224
|
--chart-1: oklch(0.65 0.22 260);
|
|
225
225
|
--chart-2: oklch(0.65 0.19 170);
|
|
@@ -227,15 +227,15 @@
|
|
|
227
227
|
--chart-4: oklch(0.65 0.22 300);
|
|
228
228
|
--chart-5: oklch(0.7 0.19 30);
|
|
229
229
|
|
|
230
|
-
/* Sidebar —
|
|
231
|
-
--sidebar: oklch(0.
|
|
232
|
-
--sidebar-foreground: oklch(0.93 0.
|
|
233
|
-
--sidebar-primary: oklch(0.65 0.18
|
|
234
|
-
--sidebar-primary-foreground: oklch(0.
|
|
235
|
-
--sidebar-accent: oklch(0.
|
|
236
|
-
--sidebar-accent-foreground: oklch(0.93 0.
|
|
230
|
+
/* Sidebar — unified hue 250 */
|
|
231
|
+
--sidebar: oklch(0.13 0.02 250 / 0.6);
|
|
232
|
+
--sidebar-foreground: oklch(0.93 0.01 250);
|
|
233
|
+
--sidebar-primary: oklch(0.65 0.18 250);
|
|
234
|
+
--sidebar-primary-foreground: oklch(0.13 0.02 250);
|
|
235
|
+
--sidebar-accent: oklch(0.19 0.02 250);
|
|
236
|
+
--sidebar-accent-foreground: oklch(0.93 0.01 250);
|
|
237
237
|
--sidebar-border: var(--glass-border-subtle);
|
|
238
|
-
--sidebar-ring: oklch(0.65 0.18
|
|
238
|
+
--sidebar-ring: oklch(0.65 0.18 250);
|
|
239
239
|
/* Semantic status tokens */
|
|
240
240
|
--status-running: oklch(0.65 0.18 260);
|
|
241
241
|
--status-completed: oklch(0.65 0.15 170);
|
|
@@ -251,72 +251,42 @@
|
|
|
251
251
|
--complexity-moderate: oklch(0.75 0.15 75);
|
|
252
252
|
--complexity-complex: oklch(0.65 0.2 25);
|
|
253
253
|
|
|
254
|
-
/* Dark mode glass overrides —
|
|
255
|
-
--glass-bg: oklch(0.
|
|
256
|
-
--glass-bg-heavy: oklch(0.
|
|
257
|
-
--glass-bg-light: oklch(0.16 0.02
|
|
258
|
-
--glass-bg-subtle: oklch(0.
|
|
259
|
-
|
|
260
|
-
/* Glass borders —
|
|
261
|
-
--glass-border: oklch(0.
|
|
262
|
-
--glass-border-strong: oklch(0.
|
|
263
|
-
--glass-border-subtle: oklch(0.
|
|
264
|
-
|
|
265
|
-
/* Glass shadows — deeper with
|
|
266
|
-
--glass-shadow: 0 8px 32px oklch(0.05 0.
|
|
267
|
-
--glass-shadow-lg: 0 12px 48px oklch(0.05 0.
|
|
268
|
-
--glass-shadow-sm: 0 4px 16px oklch(0.05 0.
|
|
269
|
-
|
|
270
|
-
/* Inner glow — faint
|
|
271
|
-
--glass-inner-glow: inset 0 1px 0 0 oklch(0.5 0.
|
|
272
|
-
--glass-inner-glow-subtle: inset 0 1px 0 0 oklch(0.5 0.
|
|
273
|
-
|
|
274
|
-
/* Modal —
|
|
275
|
-
--glass-bg-modal: oklch(0.
|
|
276
|
-
|
|
277
|
-
/* Dark mode gradient presets —
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
oklch(0.
|
|
282
|
-
|
|
283
|
-
oklch(0.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
--gradient-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
oklch(0.
|
|
290
|
-
oklch(0.11 0.035 265) 100%
|
|
291
|
-
);
|
|
292
|
-
/* Monitor — black → deep navy */
|
|
293
|
-
--gradient-forest-dawn: linear-gradient(
|
|
294
|
-
135deg,
|
|
295
|
-
oklch(0.09 0.02 270) 0%,
|
|
296
|
-
oklch(0.11 0.035 285) 50%,
|
|
297
|
-
oklch(0.10 0.03 290) 100%
|
|
298
|
-
);
|
|
299
|
-
/* Inbox — velvet → warm navy */
|
|
300
|
-
--gradient-sunset-glow: linear-gradient(
|
|
301
|
-
135deg,
|
|
302
|
-
oklch(0.12 0.04 320) 0%,
|
|
303
|
-
oklch(0.11 0.045 305) 50%,
|
|
304
|
-
oklch(0.12 0.035 290) 100%
|
|
305
|
-
);
|
|
306
|
-
/* Documents — deep navy → indigo */
|
|
307
|
-
--gradient-twilight: linear-gradient(
|
|
308
|
-
135deg,
|
|
309
|
-
oklch(0.11 0.05 290) 0%,
|
|
310
|
-
oklch(0.10 0.045 275) 50%,
|
|
311
|
-
oklch(0.11 0.04 260) 100%
|
|
312
|
-
);
|
|
313
|
-
/* Settings — neutral black with subtle velvet warmth */
|
|
314
|
-
--gradient-neutral: linear-gradient(
|
|
315
|
-
135deg,
|
|
316
|
-
oklch(0.10 0.015 290) 0%,
|
|
317
|
-
oklch(0.095 0.02 300) 50%,
|
|
318
|
-
oklch(0.10 0.015 265) 100%
|
|
319
|
-
);
|
|
254
|
+
/* Dark mode glass overrides — unified hue 250 blue-indigo */
|
|
255
|
+
--glass-bg: oklch(0.16 0.02 250 / 0.45);
|
|
256
|
+
--glass-bg-heavy: oklch(0.19 0.02 250 / 0.55);
|
|
257
|
+
--glass-bg-light: oklch(0.16 0.02 250 / 0.25);
|
|
258
|
+
--glass-bg-subtle: oklch(0.13 0.02 250 / 0.15);
|
|
259
|
+
|
|
260
|
+
/* Glass borders — hue 250 for cohesion */
|
|
261
|
+
--glass-border: oklch(0.25 0.02 250 / 0.3);
|
|
262
|
+
--glass-border-strong: oklch(0.30 0.02 250 / 0.4);
|
|
263
|
+
--glass-border-subtle: oklch(0.25 0.02 250 / 0.18);
|
|
264
|
+
|
|
265
|
+
/* Glass shadows — deeper with hue 250 tint */
|
|
266
|
+
--glass-shadow: 0 8px 32px oklch(0.05 0.02 250 / 0.5);
|
|
267
|
+
--glass-shadow-lg: 0 12px 48px oklch(0.05 0.02 250 / 0.6);
|
|
268
|
+
--glass-shadow-sm: 0 4px 16px oklch(0.05 0.02 250 / 0.35);
|
|
269
|
+
|
|
270
|
+
/* Inner glow — faint hue 250 highlight */
|
|
271
|
+
--glass-inner-glow: inset 0 1px 0 0 oklch(0.5 0.02 250 / 0.15);
|
|
272
|
+
--glass-inner-glow-subtle: inset 0 1px 0 0 oklch(0.5 0.02 250 / 0.1);
|
|
273
|
+
|
|
274
|
+
/* Modal — hue 250 glass */
|
|
275
|
+
--glass-bg-modal: oklch(0.13 0.02 250 / 0.75);
|
|
276
|
+
|
|
277
|
+
/* Dark mode gradient presets — unified hue 250 with subtle variation (248-252) */
|
|
278
|
+
--gradient-morning-sky: linear-gradient(135deg,
|
|
279
|
+
oklch(0.13 0.02 250) 0%, oklch(0.14 0.025 248) 50%, oklch(0.13 0.02 252) 100%);
|
|
280
|
+
--gradient-ocean-mist: linear-gradient(135deg,
|
|
281
|
+
oklch(0.13 0.025 248) 0%, oklch(0.14 0.03 250) 50%, oklch(0.13 0.025 252) 100%);
|
|
282
|
+
--gradient-forest-dawn: linear-gradient(135deg,
|
|
283
|
+
oklch(0.12 0.02 252) 0%, oklch(0.14 0.025 250) 50%, oklch(0.13 0.02 248) 100%);
|
|
284
|
+
--gradient-sunset-glow: linear-gradient(135deg,
|
|
285
|
+
oklch(0.13 0.02 246) 0%, oklch(0.14 0.025 248) 50%, oklch(0.13 0.02 250) 100%);
|
|
286
|
+
--gradient-twilight: linear-gradient(135deg,
|
|
287
|
+
oklch(0.13 0.03 252) 0%, oklch(0.14 0.025 250) 50%, oklch(0.13 0.02 248) 100%);
|
|
288
|
+
--gradient-neutral: linear-gradient(135deg,
|
|
289
|
+
oklch(0.13 0.015 250) 0%, oklch(0.135 0.02 250) 50%, oklch(0.13 0.015 250) 100%);
|
|
320
290
|
}
|
|
321
291
|
|
|
322
292
|
/* =============================================================
|
|
@@ -353,8 +323,8 @@
|
|
|
353
323
|
box-shadow: inset 0 1px 0 0 oklch(0.3 0.04 260 / 0.3), var(--glass-shadow-sm);
|
|
354
324
|
}
|
|
355
325
|
.dark .logo-glass-bg {
|
|
356
|
-
background: oklch(0.
|
|
357
|
-
box-shadow: inset 0 1px 0 0 oklch(0.3 0.
|
|
326
|
+
background: oklch(0.13 0.02 250);
|
|
327
|
+
box-shadow: inset 0 1px 0 0 oklch(0.3 0.02 250 / 0.15), var(--glass-shadow-sm);
|
|
358
328
|
}
|
|
359
329
|
|
|
360
330
|
.glass-sidebar {
|
|
@@ -410,6 +380,25 @@
|
|
|
410
380
|
transition: background 0.3s ease, box-shadow 0.3s ease;
|
|
411
381
|
}
|
|
412
382
|
|
|
383
|
+
.dark [data-slot="card"][data-slot="card"] {
|
|
384
|
+
background:
|
|
385
|
+
linear-gradient(180deg,
|
|
386
|
+
color-mix(in oklab, var(--surface-1) 70%, transparent),
|
|
387
|
+
color-mix(in oklab, var(--background) 88%, transparent)),
|
|
388
|
+
linear-gradient(135deg,
|
|
389
|
+
color-mix(in oklab, var(--primary) 10%, transparent),
|
|
390
|
+
transparent 58%);
|
|
391
|
+
box-shadow: 0 24px 50px color-mix(in oklab, var(--background) 84%, transparent);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.dark .surface-card {
|
|
395
|
+
background:
|
|
396
|
+
linear-gradient(180deg,
|
|
397
|
+
color-mix(in oklab, var(--surface-1) 85%, transparent),
|
|
398
|
+
color-mix(in oklab, var(--surface-2) 90%, transparent));
|
|
399
|
+
box-shadow: 0 12px 28px color-mix(in oklab, var(--background) 70%, transparent);
|
|
400
|
+
}
|
|
401
|
+
|
|
413
402
|
[data-sidebar="sidebar"] {
|
|
414
403
|
-webkit-backdrop-filter: blur(var(--blur-glass-lg));
|
|
415
404
|
backdrop-filter: blur(var(--blur-glass-lg));
|
|
@@ -490,7 +479,7 @@
|
|
|
490
479
|
}
|
|
491
480
|
.dark [data-slot="sheet-overlay"],
|
|
492
481
|
.dark [data-slot="dialog-overlay"] {
|
|
493
|
-
background: oklch(0.05 0.
|
|
482
|
+
background: oklch(0.05 0.02 250 / 0.65);
|
|
494
483
|
}
|
|
495
484
|
|
|
496
485
|
/* Glass borders for table rows — doubled selector for cascade priority over TW4 utilities */
|
|
@@ -516,10 +505,10 @@
|
|
|
516
505
|
width: 2px;
|
|
517
506
|
}
|
|
518
507
|
.dark [data-slot="separator"][data-orientation="horizontal"] {
|
|
519
|
-
background: linear-gradient(to bottom, oklch(0.06 0.02
|
|
508
|
+
background: linear-gradient(to bottom, oklch(0.06 0.02 250 / 0.35), oklch(0.25 0.02 250 / 0.15));
|
|
520
509
|
}
|
|
521
510
|
.dark [data-slot="separator"][data-orientation="vertical"] {
|
|
522
|
-
background: linear-gradient(to right, oklch(0.06 0.02
|
|
511
|
+
background: linear-gradient(to right, oklch(0.06 0.02 250 / 0.35), oklch(0.25 0.02 250 / 0.15));
|
|
523
512
|
}
|
|
524
513
|
|
|
525
514
|
/* Glass buttons — translucent CTAs */
|
|
@@ -571,12 +560,12 @@
|
|
|
571
560
|
|
|
572
561
|
/* Dark mode button overrides */
|
|
573
562
|
.dark [data-slot="button"][data-variant="default"] {
|
|
574
|
-
background: oklch(0.55 0.18
|
|
575
|
-
box-shadow: var(--glass-shadow-sm), inset 0 1px 0 0 oklch(0.7 0.1
|
|
576
|
-
border: 1px solid oklch(0.6 0.12
|
|
563
|
+
background: oklch(0.55 0.18 250 / 0.8);
|
|
564
|
+
box-shadow: var(--glass-shadow-sm), inset 0 1px 0 0 oklch(0.7 0.1 250 / 0.15);
|
|
565
|
+
border: 1px solid oklch(0.6 0.12 250 / 0.25);
|
|
577
566
|
}
|
|
578
567
|
.dark [data-slot="button"][data-variant="default"]:hover {
|
|
579
|
-
background: oklch(0.58 0.18
|
|
568
|
+
background: oklch(0.58 0.18 250 / 0.9);
|
|
580
569
|
}
|
|
581
570
|
.dark [data-slot="button"][data-variant="destructive"] {
|
|
582
571
|
background: oklch(0.55 0.2 25 / 0.8);
|
|
@@ -670,6 +659,40 @@
|
|
|
670
659
|
border: 1px solid color-mix(in oklab, var(--border) 75%, transparent);
|
|
671
660
|
}
|
|
672
661
|
|
|
662
|
+
/* --- Progress slide animation (AI Assist) --- */
|
|
663
|
+
@keyframes progress-slide {
|
|
664
|
+
0% { transform: translateX(-100%); }
|
|
665
|
+
50% { transform: translateX(0%); }
|
|
666
|
+
100% { transform: translateX(100%); }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* --- Card exit animation (ghost card deletion) --- */
|
|
670
|
+
@keyframes card-exit {
|
|
671
|
+
0% {
|
|
672
|
+
opacity: 1;
|
|
673
|
+
transform: scale(1);
|
|
674
|
+
max-height: 300px;
|
|
675
|
+
}
|
|
676
|
+
40% {
|
|
677
|
+
opacity: 0;
|
|
678
|
+
transform: scale(0.97);
|
|
679
|
+
max-height: 300px;
|
|
680
|
+
}
|
|
681
|
+
100% {
|
|
682
|
+
opacity: 0;
|
|
683
|
+
transform: scale(0.95);
|
|
684
|
+
max-height: 0;
|
|
685
|
+
margin-top: 0;
|
|
686
|
+
margin-bottom: 0;
|
|
687
|
+
overflow: hidden;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.animate-card-exit {
|
|
692
|
+
animation: card-exit 500ms ease-out forwards;
|
|
693
|
+
overflow: hidden;
|
|
694
|
+
}
|
|
695
|
+
|
|
673
696
|
/* --- Glass shimmer animation --- */
|
|
674
697
|
@keyframes glass-shimmer {
|
|
675
698
|
0% { background-position: -200% 0; }
|
|
@@ -701,7 +724,7 @@
|
|
|
701
724
|
background: linear-gradient(
|
|
702
725
|
90deg,
|
|
703
726
|
transparent 0%,
|
|
704
|
-
oklch(0.5 0.
|
|
727
|
+
oklch(0.5 0.02 250 / 0.08) 50%,
|
|
705
728
|
transparent 100%
|
|
706
729
|
);
|
|
707
730
|
background-size: 200% 100%;
|
|
@@ -718,16 +741,29 @@ body {
|
|
|
718
741
|
|
|
719
742
|
.dark .glass-sidebar [data-sidebar="sidebar"] {
|
|
720
743
|
box-shadow:
|
|
721
|
-
inset -1px 0 0 0 oklch(0.58 0.
|
|
722
|
-
12px 0 32px oklch(0.03 0.02
|
|
744
|
+
inset -1px 0 0 0 oklch(0.58 0.02 250 / 0.08),
|
|
745
|
+
12px 0 32px oklch(0.03 0.02 250 / 0.32);
|
|
723
746
|
}
|
|
724
747
|
|
|
725
748
|
.dark .surface-page-shell {
|
|
726
749
|
box-shadow:
|
|
727
|
-
0 24px 56px oklch(0.03 0.02
|
|
728
|
-
inset 0 1px 0 0 oklch(0.58 0.
|
|
750
|
+
0 24px 56px oklch(0.03 0.02 250 / 0.44),
|
|
751
|
+
inset 0 1px 0 0 oklch(0.58 0.02 250 / 0.08);
|
|
729
752
|
}
|
|
730
753
|
|
|
731
754
|
.dark .surface-toolbar {
|
|
732
|
-
box-shadow: 0 10px 24px oklch(0.03 0.02
|
|
755
|
+
box-shadow: 0 10px 24px oklch(0.03 0.02 250 / 0.32);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/* Noise grain overlay — subtle texture matching stagent.github.io */
|
|
759
|
+
.dark body::before {
|
|
760
|
+
content: "";
|
|
761
|
+
position: fixed;
|
|
762
|
+
inset: 0;
|
|
763
|
+
pointer-events: none;
|
|
764
|
+
z-index: 9999;
|
|
765
|
+
opacity: 0.03;
|
|
766
|
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
|
|
767
|
+
background-size: 256px 256px;
|
|
768
|
+
mix-blend-mode: overlay;
|
|
733
769
|
}
|
package/src/app/inbox/page.tsx
CHANGED
|
@@ -21,7 +21,7 @@ export default async function InboxPage() {
|
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<div className="gradient-sunset-glow min-h-screen p-4 sm:p-6">
|
|
24
|
-
<div className="surface-page surface-page-shell
|
|
24
|
+
<div className="surface-page surface-page-shell min-h-[calc(100dvh-2rem)] rounded-[30px] p-5 sm:p-6 lg:p-7">
|
|
25
25
|
<div className="mb-5 space-y-1">
|
|
26
26
|
<h1 className="text-2xl font-bold">Inbox</h1>
|
|
27
27
|
<p className="text-sm text-muted-foreground">
|
package/src/app/layout.tsx
CHANGED
|
@@ -35,17 +35,17 @@ const CRITICAL_THEME_CSS = `
|
|
|
35
35
|
}
|
|
36
36
|
html.dark {
|
|
37
37
|
color-scheme: dark;
|
|
38
|
-
--background: oklch(0.
|
|
39
|
-
--foreground: oklch(0.93 0.
|
|
40
|
-
--surface-1: oklch(0.16 0.02
|
|
41
|
-
--surface-2: oklch(0.14 0.
|
|
42
|
-
--border: oklch(0.
|
|
38
|
+
--background: oklch(0.13 0.02 250);
|
|
39
|
+
--foreground: oklch(0.93 0.01 250);
|
|
40
|
+
--surface-1: oklch(0.16 0.02 250 / 0.96);
|
|
41
|
+
--surface-2: oklch(0.14 0.02 250 / 0.9);
|
|
42
|
+
--border: oklch(0.25 0.02 250 / 0.28);
|
|
43
43
|
}
|
|
44
44
|
html { background: var(--background); }
|
|
45
45
|
`.replace(/\s+/g, " ").trim();
|
|
46
46
|
|
|
47
47
|
// Static theme initialization script — no user input, safe from XSS.
|
|
48
|
-
const THEME_INIT_SCRIPT = `(function(){try{var d=document.documentElement;var s=localStorage.getItem('stagent-theme');var t=s==='dark'||s==='light'?s:(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');d.classList.toggle('dark',t==='dark');d.dataset.theme=t;d.style.colorScheme=t;d.style.backgroundColor=t==='dark'?'oklch(0.
|
|
48
|
+
const THEME_INIT_SCRIPT = `(function(){try{var d=document.documentElement;var s=localStorage.getItem('stagent-theme');var t=s==='dark'||s==='light'?s:(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');d.classList.toggle('dark',t==='dark');d.dataset.theme=t;d.style.colorScheme=t;d.style.backgroundColor=t==='dark'?'oklch(0.13 0.02 250)':'oklch(0.98 0.005 260)';document.cookie='stagent-theme='+t+';path=/;max-age=31536000;SameSite=Lax';}catch(e){}})()`;
|
|
49
49
|
|
|
50
50
|
export default function RootLayout({
|
|
51
51
|
children,
|
package/src/app/page.tsx
CHANGED
|
@@ -127,7 +127,7 @@ export default async function HomePage() {
|
|
|
127
127
|
|
|
128
128
|
return (
|
|
129
129
|
<div className="gradient-morning-sky min-h-screen p-4 sm:p-6">
|
|
130
|
-
<div className="surface-page surface-page-shell
|
|
130
|
+
<div className="surface-page surface-page-shell min-h-[calc(100dvh-2rem)] rounded-[30px] p-5 sm:p-6 lg:p-7">
|
|
131
131
|
<Greeting
|
|
132
132
|
runningCount={runningResult.count}
|
|
133
133
|
awaitingCount={awaitingResult.count}
|
|
@@ -25,7 +25,7 @@ export default async function EditProfilePage({
|
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
27
|
<div className="gradient-ocean-mist min-h-screen p-6">
|
|
28
|
-
<div className="
|
|
28
|
+
<div className="">
|
|
29
29
|
<Link href={duplicate === "true" ? "/profiles" : `/profiles/${id}`}>
|
|
30
30
|
<Button variant="ghost" size="sm" className="mb-4">
|
|
31
31
|
<ArrowLeft className="h-4 w-4 mr-1" />
|
|
@@ -19,7 +19,7 @@ export default async function ProfileDetailPage({
|
|
|
19
19
|
|
|
20
20
|
return (
|
|
21
21
|
<div className="gradient-ocean-mist min-h-[100dvh] p-4 sm:p-6">
|
|
22
|
-
<div className="surface-page
|
|
22
|
+
<div className="surface-page rounded-[28px] border border-border/60 p-6 shadow-[0_18px_48px_oklch(0.12_0.02_260_/_0.08)]">
|
|
23
23
|
<Link href="/profiles">
|
|
24
24
|
<Button variant="ghost" size="sm" className="mb-4">
|
|
25
25
|
<ArrowLeft className="h-4 w-4 mr-1" />
|
|
@@ -8,7 +8,7 @@ export const dynamic = "force-dynamic";
|
|
|
8
8
|
export default async function NewProfilePage() {
|
|
9
9
|
return (
|
|
10
10
|
<div className="gradient-ocean-mist min-h-screen p-6">
|
|
11
|
-
<div className="
|
|
11
|
+
<div className="">
|
|
12
12
|
<Link href="/profiles">
|
|
13
13
|
<Button variant="ghost" size="sm" className="mb-4">
|
|
14
14
|
<ArrowLeft className="h-4 w-4 mr-1" />
|
|
@@ -11,7 +11,7 @@ export default async function ProfilesPage() {
|
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
13
|
<div className="gradient-ocean-mist min-h-[100dvh] p-4 sm:p-6">
|
|
14
|
-
<div className="surface-page
|
|
14
|
+
<div className="surface-page rounded-[28px] border border-border/60 p-6 shadow-[0_18px_48px_oklch(0.12_0.02_260_/_0.08)]">
|
|
15
15
|
<ProfileBrowser initialProfiles={profiles} />
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
@@ -24,7 +24,7 @@ export default async function ProjectsPage() {
|
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
26
|
<div className="gradient-ocean-mist min-h-screen p-4 sm:p-6">
|
|
27
|
-
<div className="surface-page surface-page-shell
|
|
27
|
+
<div className="surface-page surface-page-shell min-h-[calc(100dvh-2rem)] rounded-[30px] p-5 sm:p-6 lg:p-7">
|
|
28
28
|
<ProjectList initialProjects={result} />
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
@@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|
|
3
3
|
|
|
4
4
|
export default function SettingsLoading() {
|
|
5
5
|
return (
|
|
6
|
-
<div className="p-6
|
|
6
|
+
<div className="p-6 space-y-6">
|
|
7
7
|
<div className="space-y-2">
|
|
8
8
|
<Skeleton className="h-8 w-32" />
|
|
9
9
|
<Skeleton className="h-4 w-56" />
|