feed-the-machine 1.5.0 → 1.6.0
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/LICENSE +21 -21
- package/README.md +170 -170
- package/bin/generate-manifest.mjs +463 -463
- package/bin/install.mjs +491 -491
- package/docs/HOOKS.md +243 -243
- package/docs/INBOX.md +233 -233
- package/ftm/SKILL.md +122 -122
- package/ftm-audit/SKILL.md +623 -541
- package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
- package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
- package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
- package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
- package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
- package/ftm-audit/scripts/run-knip.sh +23 -23
- package/ftm-audit.yml +2 -2
- package/ftm-brainstorm/SKILL.md +498 -498
- package/ftm-brainstorm/evals/evals.json +100 -100
- package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
- package/ftm-brainstorm/references/agent-prompts.md +224 -224
- package/ftm-brainstorm/references/plan-template.md +121 -121
- package/ftm-brainstorm.yml +2 -2
- package/ftm-browse/SKILL.md +454 -454
- package/ftm-browse/daemon/browser-manager.ts +206 -206
- package/ftm-browse/daemon/bun.lock +30 -30
- package/ftm-browse/daemon/cli.ts +347 -347
- package/ftm-browse/daemon/commands.ts +410 -410
- package/ftm-browse/daemon/main.ts +357 -357
- package/ftm-browse/daemon/package.json +17 -17
- package/ftm-browse/daemon/server.ts +189 -189
- package/ftm-browse/daemon/snapshot.ts +519 -519
- package/ftm-browse/daemon/tsconfig.json +22 -22
- package/ftm-browse.yml +4 -4
- package/ftm-capture/SKILL.md +370 -370
- package/ftm-capture.yml +4 -4
- package/ftm-codex-gate/SKILL.md +361 -361
- package/ftm-codex-gate.yml +2 -2
- package/ftm-config/SKILL.md +345 -345
- package/ftm-config.default.yml +82 -80
- package/ftm-config.yml +2 -2
- package/ftm-council/SKILL.md +416 -416
- package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
- package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
- package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
- package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
- package/ftm-council.yml +2 -2
- package/ftm-dashboard/SKILL.md +163 -163
- package/ftm-dashboard.yml +4 -4
- package/ftm-debug/SKILL.md +1037 -1037
- package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
- package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
- package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
- package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
- package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
- package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
- package/ftm-debug.yml +2 -2
- package/ftm-diagram/SKILL.md +277 -277
- package/ftm-diagram.yml +2 -2
- package/ftm-executor/SKILL.md +777 -767
- package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
- package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
- package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
- package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
- package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +72 -72
- package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
- package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
- package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
- package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -44
- package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
- package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
- package/ftm-executor/runtime/package.json +8 -8
- package/ftm-executor.yml +2 -2
- package/ftm-git/SKILL.md +441 -441
- package/ftm-git/evals/evals.json +26 -26
- package/ftm-git/evals/promptfoo.yaml +75 -75
- package/ftm-git/hooks/post-commit-experience.sh +92 -92
- package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
- package/ftm-git/references/protocols/REMEDIATION.md +139 -139
- package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
- package/ftm-git.yml +2 -2
- package/ftm-inbox/backend/adapters/_retry.py +64 -64
- package/ftm-inbox/backend/adapters/base.py +230 -230
- package/ftm-inbox/backend/adapters/freshservice.py +104 -104
- package/ftm-inbox/backend/adapters/gmail.py +125 -125
- package/ftm-inbox/backend/adapters/jira.py +136 -136
- package/ftm-inbox/backend/adapters/registry.py +192 -192
- package/ftm-inbox/backend/adapters/slack.py +110 -110
- package/ftm-inbox/backend/db/connection.py +54 -54
- package/ftm-inbox/backend/db/schema.py +78 -78
- package/ftm-inbox/backend/executor/__init__.py +7 -7
- package/ftm-inbox/backend/executor/engine.py +149 -149
- package/ftm-inbox/backend/executor/step_runner.py +98 -98
- package/ftm-inbox/backend/main.py +103 -103
- package/ftm-inbox/backend/models/__init__.py +1 -1
- package/ftm-inbox/backend/models/unified_task.py +36 -36
- package/ftm-inbox/backend/planner/__init__.py +6 -6
- package/ftm-inbox/backend/planner/generator.py +127 -127
- package/ftm-inbox/backend/planner/schema.py +34 -34
- package/ftm-inbox/backend/requirements.txt +5 -5
- package/ftm-inbox/backend/routes/execute.py +186 -186
- package/ftm-inbox/backend/routes/health.py +52 -52
- package/ftm-inbox/backend/routes/inbox.py +68 -68
- package/ftm-inbox/backend/routes/plan.py +271 -271
- package/ftm-inbox/bin/launchagent.mjs +91 -91
- package/ftm-inbox/bin/setup.mjs +188 -188
- package/ftm-inbox/bin/start.sh +10 -10
- package/ftm-inbox/bin/status.sh +17 -17
- package/ftm-inbox/bin/stop.sh +8 -8
- package/ftm-inbox/config.example.yml +55 -55
- package/ftm-inbox/package-lock.json +2898 -2898
- package/ftm-inbox/package.json +26 -26
- package/ftm-inbox/postcss.config.js +6 -6
- package/ftm-inbox/src/app.css +199 -199
- package/ftm-inbox/src/app.html +18 -18
- package/ftm-inbox/src/lib/api.ts +166 -166
- package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
- package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
- package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
- package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
- package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
- package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
- package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
- package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
- package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
- package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
- package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
- package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
- package/ftm-inbox/src/lib/theme.ts +47 -47
- package/ftm-inbox/src/routes/+layout.svelte +76 -76
- package/ftm-inbox/src/routes/+page.svelte +401 -401
- package/ftm-inbox/svelte.config.js +12 -12
- package/ftm-inbox/tailwind.config.ts +63 -63
- package/ftm-inbox/tsconfig.json +13 -13
- package/ftm-inbox/vite.config.ts +6 -6
- package/ftm-intent/SKILL.md +241 -241
- package/ftm-intent.yml +2 -2
- package/ftm-manifest.json +3794 -3794
- package/ftm-map/SKILL.md +291 -291
- package/ftm-map/scripts/db.py +712 -712
- package/ftm-map/scripts/index.py +415 -415
- package/ftm-map/scripts/parser.py +224 -224
- package/ftm-map/scripts/queries/go-tags.scm +20 -20
- package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
- package/ftm-map/scripts/queries/python-tags.scm +31 -31
- package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
- package/ftm-map/scripts/queries/rust-tags.scm +37 -37
- package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
- package/ftm-map/scripts/query.py +301 -301
- package/ftm-map/scripts/ranker.py +377 -377
- package/ftm-map/scripts/requirements.txt +5 -5
- package/ftm-map/scripts/setup-hooks.sh +27 -27
- package/ftm-map/scripts/setup.sh +56 -56
- package/ftm-map/scripts/test_db.py +364 -364
- package/ftm-map/scripts/test_parser.py +174 -174
- package/ftm-map/scripts/test_query.py +183 -183
- package/ftm-map/scripts/test_ranker.py +199 -199
- package/ftm-map/scripts/views.py +591 -591
- package/ftm-map.yml +2 -2
- package/ftm-mind/SKILL.md +1943 -1943
- package/ftm-mind/evals/promptfoo.yaml +142 -142
- package/ftm-mind/references/blackboard-schema.md +328 -328
- package/ftm-mind/references/complexity-guide.md +110 -110
- package/ftm-mind/references/event-registry.md +319 -319
- package/ftm-mind/references/mcp-inventory.md +296 -296
- package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
- package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
- package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
- package/ftm-mind/references/reflexion-protocol.md +249 -249
- package/ftm-mind/references/routing/SCENARIOS.md +22 -22
- package/ftm-mind/references/routing-scenarios.md +35 -35
- package/ftm-mind.yml +2 -2
- package/ftm-pause/SKILL.md +395 -395
- package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
- package/ftm-pause/references/protocols/VALIDATION.md +80 -80
- package/ftm-pause.yml +2 -2
- package/ftm-researcher/SKILL.md +275 -275
- package/ftm-researcher/evals/agent-diversity.yaml +17 -17
- package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
- package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
- package/ftm-researcher/references/adaptive-search.md +116 -116
- package/ftm-researcher/references/agent-prompts.md +193 -193
- package/ftm-researcher/references/council-integration.md +193 -193
- package/ftm-researcher/references/output-format.md +203 -203
- package/ftm-researcher/references/synthesis-pipeline.md +165 -165
- package/ftm-researcher/scripts/score_credibility.py +234 -234
- package/ftm-researcher/scripts/validate_research.py +92 -92
- package/ftm-researcher.yml +2 -2
- package/ftm-resume/SKILL.md +518 -518
- package/ftm-resume/references/protocols/VALIDATION.md +172 -172
- package/ftm-resume.yml +2 -2
- package/ftm-retro/SKILL.md +380 -380
- package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
- package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
- package/ftm-retro.yml +2 -2
- package/ftm-routine/SKILL.md +170 -170
- package/ftm-routine.yml +4 -4
- package/ftm-state/blackboard/capabilities.json +5 -5
- package/ftm-state/blackboard/capabilities.schema.json +27 -27
- package/ftm-state/blackboard/context.json +23 -23
- package/ftm-state/blackboard/experiences/index.json +9 -9
- package/ftm-state/blackboard/patterns.json +6 -6
- package/ftm-state/schemas/context.schema.json +130 -130
- package/ftm-state/schemas/experience-index.schema.json +77 -77
- package/ftm-state/schemas/experience.schema.json +78 -78
- package/ftm-state/schemas/patterns.schema.json +44 -44
- package/ftm-upgrade/SKILL.md +194 -194
- package/ftm-upgrade/scripts/check-version.sh +76 -76
- package/ftm-upgrade/scripts/upgrade.sh +143 -143
- package/ftm-upgrade.yml +2 -2
- package/ftm-verify.yml +2 -2
- package/ftm.yml +2 -2
- package/hooks/ftm-blackboard-enforcer.sh +93 -93
- package/hooks/ftm-discovery-reminder.sh +90 -90
- package/hooks/ftm-drafts-gate.sh +61 -61
- package/hooks/ftm-event-logger.mjs +107 -107
- package/hooks/ftm-map-autodetect.sh +79 -79
- package/hooks/ftm-pending-sync-check.sh +22 -22
- package/hooks/ftm-plan-gate.sh +92 -92
- package/hooks/ftm-post-commit-trigger.sh +57 -57
- package/hooks/settings-template.json +81 -81
- package/install.sh +363 -363
- package/package.json +84 -84
- package/uninstall.sh +25 -25
|
@@ -1,401 +1,401 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import KawaiiCard from '$lib/components/ui/KawaiiCard.svelte';
|
|
3
|
-
import StatusBadge from '$lib/components/ui/StatusBadge.svelte';
|
|
4
|
-
import PillButton from '$lib/components/ui/PillButton.svelte';
|
|
5
|
-
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
|
6
|
-
import StreamDrawer from '$lib/components/ui/StreamDrawer.svelte';
|
|
7
|
-
import InboxFeed from '$lib/components/InboxFeed.svelte';
|
|
8
|
-
import PlanView from '$lib/components/PlanView.svelte';
|
|
9
|
-
import type { UnifiedTask, Plan } from '$lib/api';
|
|
10
|
-
import { generatePlan, getPlan } from '$lib/api';
|
|
11
|
-
type StatusBadgeStatus = 'pending' | 'planning' | 'approved' | 'executing' | 'complete' | 'failed';
|
|
12
|
-
|
|
13
|
-
// Map arbitrary task status string to a valid StatusBadge value
|
|
14
|
-
function taskStatusBadge(s: string): StatusBadgeStatus {
|
|
15
|
-
const valid: StatusBadgeStatus[] = ['pending', 'planning', 'approved', 'executing', 'complete', 'failed'];
|
|
16
|
-
return (valid.includes(s as StatusBadgeStatus) ? s : 'pending') as StatusBadgeStatus;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const API_BASE = 'http://localhost:8042';
|
|
20
|
-
|
|
21
|
-
const sourceAccent: Record<string, 'blue' | 'green' | 'yellow' | 'coral'> = {
|
|
22
|
-
jira: 'blue',
|
|
23
|
-
freshservice: 'green',
|
|
24
|
-
slack: 'yellow',
|
|
25
|
-
gmail: 'coral'
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
let selectedTask: UnifiedTask | null = null;
|
|
29
|
-
let currentPlan: Plan | null = null;
|
|
30
|
-
let planLoading = false;
|
|
31
|
-
let drawerOpen = false;
|
|
32
|
-
let drawerLines: string[] = [];
|
|
33
|
-
let auditEntries: { time: string; level: string; msg: string }[] = [];
|
|
34
|
-
|
|
35
|
-
function addAudit(level: 'info' | 'warn' | 'success' | 'error', msg: string) {
|
|
36
|
-
auditEntries = [
|
|
37
|
-
...auditEntries,
|
|
38
|
-
{ time: new Date().toLocaleTimeString('en-GB', { hour12: false }), level, msg }
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function handleSelectTask(e: CustomEvent<UnifiedTask>) {
|
|
43
|
-
selectedTask = e.detail;
|
|
44
|
-
currentPlan = null;
|
|
45
|
-
// Load existing plan for this task if one exists
|
|
46
|
-
try {
|
|
47
|
-
currentPlan = await getPlan(e.detail.id);
|
|
48
|
-
} catch {
|
|
49
|
-
// No plan yet — that's fine
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function handleGeneratePlan(e: CustomEvent<UnifiedTask>) {
|
|
54
|
-
selectedTask = e.detail;
|
|
55
|
-
currentPlan = null;
|
|
56
|
-
planLoading = true;
|
|
57
|
-
drawerLines = [];
|
|
58
|
-
drawerOpen = true;
|
|
59
|
-
|
|
60
|
-
const task = e.detail;
|
|
61
|
-
addAudit('info', `Plan generation started for: ${task.title}`);
|
|
62
|
-
drawerLines = [...drawerLines, `[${new Date().toLocaleTimeString()}] Generate plan: ${task.title}`];
|
|
63
|
-
|
|
64
|
-
// Use SSE stream for live output in the drawer
|
|
65
|
-
try {
|
|
66
|
-
const evtSource = new EventSource(`${API_BASE}/api/tasks/${task.id}/plan-stream`);
|
|
67
|
-
|
|
68
|
-
evtSource.onmessage = (event) => {
|
|
69
|
-
try {
|
|
70
|
-
const msg = JSON.parse(event.data);
|
|
71
|
-
if (msg.type === 'chunk') {
|
|
72
|
-
drawerLines = [...drawerLines, msg.text.trimEnd()];
|
|
73
|
-
} else if (msg.type === 'done') {
|
|
74
|
-
currentPlan = msg.plan as Plan;
|
|
75
|
-
planLoading = false;
|
|
76
|
-
addAudit('success', `Plan ready: ${currentPlan.steps.length} steps`);
|
|
77
|
-
drawerLines = [...drawerLines, `Plan ready — ${currentPlan.steps.length} steps generated.`];
|
|
78
|
-
evtSource.close();
|
|
79
|
-
} else if (msg.type === 'error') {
|
|
80
|
-
addAudit('error', `Plan failed: ${msg.message}`);
|
|
81
|
-
drawerLines = [...drawerLines, `Error: ${msg.message}`];
|
|
82
|
-
planLoading = false;
|
|
83
|
-
evtSource.close();
|
|
84
|
-
}
|
|
85
|
-
} catch {
|
|
86
|
-
// Ignore malformed SSE frames
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
evtSource.onerror = () => {
|
|
91
|
-
planLoading = false;
|
|
92
|
-
addAudit('warn', 'SSE stream closed unexpectedly');
|
|
93
|
-
evtSource.close();
|
|
94
|
-
};
|
|
95
|
-
} catch (err) {
|
|
96
|
-
// Fallback: direct POST without streaming
|
|
97
|
-
try {
|
|
98
|
-
const plan = await generatePlan(task.id);
|
|
99
|
-
currentPlan = plan;
|
|
100
|
-
addAudit('success', `Plan ready: ${plan.steps.length} steps`);
|
|
101
|
-
} catch (genErr) {
|
|
102
|
-
addAudit('error', `Plan generation failed: ${genErr}`);
|
|
103
|
-
} finally {
|
|
104
|
-
planLoading = false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function handlePlanUpdated(e: CustomEvent<Plan>) {
|
|
110
|
-
currentPlan = e.detail;
|
|
111
|
-
addAudit('info', `Plan updated — status: ${e.detail.status}`);
|
|
112
|
-
}
|
|
113
|
-
</script>
|
|
114
|
-
|
|
115
|
-
<!-- Three-column layout + bottom drawer -->
|
|
116
|
-
<div class="layout-grid">
|
|
117
|
-
<!-- Left: Task Inbox -->
|
|
118
|
-
<aside class="sidebar sidebar-left" aria-label="Task inbox">
|
|
119
|
-
<div class="sidebar-header">
|
|
120
|
-
<h2 class="sidebar-title">Inbox</h2>
|
|
121
|
-
</div>
|
|
122
|
-
<div class="sidebar-body">
|
|
123
|
-
<InboxFeed
|
|
124
|
-
selectedTaskId={selectedTask?.id ?? null}
|
|
125
|
-
on:selectTask={handleSelectTask}
|
|
126
|
-
on:generatePlan={handleGeneratePlan}
|
|
127
|
-
/>
|
|
128
|
-
</div>
|
|
129
|
-
</aside>
|
|
130
|
-
|
|
131
|
-
<!-- Center: Plan Viewer -->
|
|
132
|
-
<section class="center-panel" aria-label="Plan viewer">
|
|
133
|
-
{#if selectedTask}
|
|
134
|
-
<div class="plan-viewer">
|
|
135
|
-
<div class="plan-header">
|
|
136
|
-
<div class="plan-header-top">
|
|
137
|
-
<span class="plan-id">{selectedTask.source}:{selectedTask.source_id}</span>
|
|
138
|
-
<StatusBadge status={taskStatusBadge(selectedTask.status)} />
|
|
139
|
-
</div>
|
|
140
|
-
<h1 class="plan-title">{selectedTask.title}</h1>
|
|
141
|
-
{#if selectedTask.body}
|
|
142
|
-
<p class="plan-body">{selectedTask.body}</p>
|
|
143
|
-
{/if}
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
<KawaiiCard accent={sourceAccent[selectedTask.source] ?? 'green'}>
|
|
147
|
-
<span slot="header" class="card-label">Execution Plan</span>
|
|
148
|
-
|
|
149
|
-
<PlanView
|
|
150
|
-
plan={currentPlan}
|
|
151
|
-
loading={planLoading}
|
|
152
|
-
on:planUpdated={handlePlanUpdated}
|
|
153
|
-
/>
|
|
154
|
-
|
|
155
|
-
<div slot="footer" class="plan-actions">
|
|
156
|
-
<PillButton
|
|
157
|
-
variant="primary"
|
|
158
|
-
size="sm"
|
|
159
|
-
disabled={planLoading}
|
|
160
|
-
on:click={() => { if (selectedTask) handleGeneratePlan(new CustomEvent('generatePlan', { detail: selectedTask })); }}
|
|
161
|
-
>
|
|
162
|
-
{planLoading ? 'Generating…' : currentPlan ? 'Regenerate Plan' : 'Generate Plan'}
|
|
163
|
-
</PillButton>
|
|
164
|
-
{#if selectedTask.source_url}
|
|
165
|
-
<PillButton variant="ghost" size="sm" on:click={() => window.open(selectedTask?.source_url ?? '', '_blank')}>
|
|
166
|
-
Open Source
|
|
167
|
-
</PillButton>
|
|
168
|
-
{/if}
|
|
169
|
-
</div>
|
|
170
|
-
</KawaiiCard>
|
|
171
|
-
</div>
|
|
172
|
-
{:else}
|
|
173
|
-
<EmptyState
|
|
174
|
-
emoji="🗂️"
|
|
175
|
-
title="Select a task"
|
|
176
|
-
message="Choose a task from the inbox to view its plan."
|
|
177
|
-
/>
|
|
178
|
-
{/if}
|
|
179
|
-
</section>
|
|
180
|
-
|
|
181
|
-
<!-- Right: Audit Log -->
|
|
182
|
-
<aside class="sidebar sidebar-right" aria-label="Audit log">
|
|
183
|
-
<div class="sidebar-header">
|
|
184
|
-
<h2 class="sidebar-title">Audit Log</h2>
|
|
185
|
-
{#if auditEntries.length > 0}
|
|
186
|
-
<span class="sidebar-count">{auditEntries.length}</span>
|
|
187
|
-
{/if}
|
|
188
|
-
</div>
|
|
189
|
-
<div class="sidebar-body">
|
|
190
|
-
{#if auditEntries.length === 0}
|
|
191
|
-
<EmptyState
|
|
192
|
-
emoji="📋"
|
|
193
|
-
title="No events yet"
|
|
194
|
-
message="Audit events will appear here."
|
|
195
|
-
/>
|
|
196
|
-
{:else}
|
|
197
|
-
<div class="audit-list">
|
|
198
|
-
{#each auditEntries as entry, i (i)}
|
|
199
|
-
<div class="audit-entry audit-{entry.level}">
|
|
200
|
-
<span class="audit-time">{entry.time}</span>
|
|
201
|
-
<span class="audit-msg">{entry.msg}</span>
|
|
202
|
-
</div>
|
|
203
|
-
{/each}
|
|
204
|
-
</div>
|
|
205
|
-
{/if}
|
|
206
|
-
</div>
|
|
207
|
-
</aside>
|
|
208
|
-
</div>
|
|
209
|
-
|
|
210
|
-
<!-- Bottom drawer for streaming agent output -->
|
|
211
|
-
<StreamDrawer bind:open={drawerOpen} lines={drawerLines} />
|
|
212
|
-
|
|
213
|
-
<style>
|
|
214
|
-
/* ─── Three-column layout ─── */
|
|
215
|
-
.layout-grid {
|
|
216
|
-
display: flex;
|
|
217
|
-
flex: 1;
|
|
218
|
-
height: calc(100vh - 57px - 36px); /* minus nav height minus drawer handle */
|
|
219
|
-
overflow: hidden;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.sidebar {
|
|
223
|
-
display: flex;
|
|
224
|
-
flex-direction: column;
|
|
225
|
-
background: var(--bg-sidebar);
|
|
226
|
-
overflow: hidden;
|
|
227
|
-
flex-shrink: 0;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.sidebar-left {
|
|
231
|
-
width: 280px;
|
|
232
|
-
border-right: 1px solid var(--border-card);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.sidebar-right {
|
|
236
|
-
width: 320px;
|
|
237
|
-
border-left: 1px solid var(--border-card);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.sidebar-header {
|
|
241
|
-
display: flex;
|
|
242
|
-
align-items: center;
|
|
243
|
-
justify-content: space-between;
|
|
244
|
-
padding: 0.75rem 1rem 0.5rem;
|
|
245
|
-
border-bottom: 1px solid var(--border-card);
|
|
246
|
-
background: var(--bg-card);
|
|
247
|
-
flex-shrink: 0;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.sidebar-title {
|
|
251
|
-
font-size: 0.72rem;
|
|
252
|
-
font-weight: 800;
|
|
253
|
-
letter-spacing: 0.08em;
|
|
254
|
-
text-transform: uppercase;
|
|
255
|
-
color: var(--text-muted);
|
|
256
|
-
margin: 0;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.sidebar-count {
|
|
260
|
-
font-size: 0.68rem;
|
|
261
|
-
font-weight: 800;
|
|
262
|
-
background: var(--border-card);
|
|
263
|
-
color: var(--text-muted);
|
|
264
|
-
padding: 2px 7px;
|
|
265
|
-
border-radius: 9999px;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.sidebar-body {
|
|
269
|
-
flex: 1;
|
|
270
|
-
overflow-y: auto;
|
|
271
|
-
padding: 0.75rem;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
.center-panel {
|
|
275
|
-
flex: 1;
|
|
276
|
-
overflow-y: auto;
|
|
277
|
-
padding: 1.25rem;
|
|
278
|
-
min-width: 0;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/* ─── Plan viewer ─── */
|
|
282
|
-
.plan-viewer {
|
|
283
|
-
display: flex;
|
|
284
|
-
flex-direction: column;
|
|
285
|
-
gap: 1rem;
|
|
286
|
-
max-width: 680px;
|
|
287
|
-
margin: 0 auto;
|
|
288
|
-
animation: fadeUp 0.3s ease-out both;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
@keyframes fadeUp {
|
|
292
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
293
|
-
to { opacity: 1; transform: translateY(0); }
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.plan-header {
|
|
297
|
-
display: flex;
|
|
298
|
-
flex-direction: column;
|
|
299
|
-
gap: 0.4rem;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.plan-header-top {
|
|
303
|
-
display: flex;
|
|
304
|
-
align-items: center;
|
|
305
|
-
gap: 0.75rem;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
.plan-id {
|
|
309
|
-
font-size: 0.75rem;
|
|
310
|
-
font-weight: 800;
|
|
311
|
-
letter-spacing: 0.06em;
|
|
312
|
-
color: var(--text-muted);
|
|
313
|
-
font-family: 'Menlo', monospace;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
.plan-title {
|
|
317
|
-
font-size: 1.25rem;
|
|
318
|
-
font-weight: 800;
|
|
319
|
-
color: var(--text-primary);
|
|
320
|
-
line-height: 1.3;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
.plan-body {
|
|
324
|
-
font-size: 0.85rem;
|
|
325
|
-
color: var(--text-secondary);
|
|
326
|
-
line-height: 1.5;
|
|
327
|
-
margin: 0;
|
|
328
|
-
max-height: 100px;
|
|
329
|
-
overflow-y: auto;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.card-label {
|
|
333
|
-
font-size: 0.72rem;
|
|
334
|
-
font-weight: 800;
|
|
335
|
-
text-transform: uppercase;
|
|
336
|
-
letter-spacing: 0.08em;
|
|
337
|
-
color: var(--text-muted);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
.plan-actions {
|
|
342
|
-
display: flex;
|
|
343
|
-
gap: 0.5rem;
|
|
344
|
-
flex-wrap: wrap;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/* ─── Audit log ─── */
|
|
348
|
-
.audit-list {
|
|
349
|
-
display: flex;
|
|
350
|
-
flex-direction: column;
|
|
351
|
-
gap: 0.25rem;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
.audit-entry {
|
|
355
|
-
display: flex;
|
|
356
|
-
flex-direction: column;
|
|
357
|
-
gap: 0.15rem;
|
|
358
|
-
padding: 0.4rem 0.5rem;
|
|
359
|
-
border-radius: 8px;
|
|
360
|
-
font-size: 0.72rem;
|
|
361
|
-
border-left: 3px solid transparent;
|
|
362
|
-
transition: background 0.1s;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.audit-entry:hover { background: rgba(76, 175, 80, 0.04); }
|
|
366
|
-
|
|
367
|
-
.audit-info { border-left-color: #66bb6a; }
|
|
368
|
-
.audit-warn { border-left-color: #ffd54f; background: rgba(255, 213, 79, 0.06); }
|
|
369
|
-
.audit-success { border-left-color: #4caf50; background: rgba(76, 175, 80, 0.06); }
|
|
370
|
-
.audit-error { border-left-color: #ef5350; background: rgba(239, 83, 80, 0.06); }
|
|
371
|
-
|
|
372
|
-
.audit-time {
|
|
373
|
-
font-family: 'Menlo', monospace;
|
|
374
|
-
font-size: 0.65rem;
|
|
375
|
-
color: var(--text-muted);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.audit-msg {
|
|
379
|
-
color: var(--text-secondary);
|
|
380
|
-
font-weight: 600;
|
|
381
|
-
line-height: 1.4;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/* ─── Mobile responsive ─── */
|
|
385
|
-
@media (max-width: 768px) {
|
|
386
|
-
.layout-grid {
|
|
387
|
-
flex-direction: column;
|
|
388
|
-
height: auto;
|
|
389
|
-
overflow: visible;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.sidebar-left,
|
|
393
|
-
.sidebar-right {
|
|
394
|
-
width: 100%;
|
|
395
|
-
border-right: none;
|
|
396
|
-
border-left: none;
|
|
397
|
-
border-bottom: 1px solid var(--border-card);
|
|
398
|
-
max-height: 40vh;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import KawaiiCard from '$lib/components/ui/KawaiiCard.svelte';
|
|
3
|
+
import StatusBadge from '$lib/components/ui/StatusBadge.svelte';
|
|
4
|
+
import PillButton from '$lib/components/ui/PillButton.svelte';
|
|
5
|
+
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
|
6
|
+
import StreamDrawer from '$lib/components/ui/StreamDrawer.svelte';
|
|
7
|
+
import InboxFeed from '$lib/components/InboxFeed.svelte';
|
|
8
|
+
import PlanView from '$lib/components/PlanView.svelte';
|
|
9
|
+
import type { UnifiedTask, Plan } from '$lib/api';
|
|
10
|
+
import { generatePlan, getPlan } from '$lib/api';
|
|
11
|
+
type StatusBadgeStatus = 'pending' | 'planning' | 'approved' | 'executing' | 'complete' | 'failed';
|
|
12
|
+
|
|
13
|
+
// Map arbitrary task status string to a valid StatusBadge value
|
|
14
|
+
function taskStatusBadge(s: string): StatusBadgeStatus {
|
|
15
|
+
const valid: StatusBadgeStatus[] = ['pending', 'planning', 'approved', 'executing', 'complete', 'failed'];
|
|
16
|
+
return (valid.includes(s as StatusBadgeStatus) ? s : 'pending') as StatusBadgeStatus;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const API_BASE = 'http://localhost:8042';
|
|
20
|
+
|
|
21
|
+
const sourceAccent: Record<string, 'blue' | 'green' | 'yellow' | 'coral'> = {
|
|
22
|
+
jira: 'blue',
|
|
23
|
+
freshservice: 'green',
|
|
24
|
+
slack: 'yellow',
|
|
25
|
+
gmail: 'coral'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let selectedTask: UnifiedTask | null = null;
|
|
29
|
+
let currentPlan: Plan | null = null;
|
|
30
|
+
let planLoading = false;
|
|
31
|
+
let drawerOpen = false;
|
|
32
|
+
let drawerLines: string[] = [];
|
|
33
|
+
let auditEntries: { time: string; level: string; msg: string }[] = [];
|
|
34
|
+
|
|
35
|
+
function addAudit(level: 'info' | 'warn' | 'success' | 'error', msg: string) {
|
|
36
|
+
auditEntries = [
|
|
37
|
+
...auditEntries,
|
|
38
|
+
{ time: new Date().toLocaleTimeString('en-GB', { hour12: false }), level, msg }
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function handleSelectTask(e: CustomEvent<UnifiedTask>) {
|
|
43
|
+
selectedTask = e.detail;
|
|
44
|
+
currentPlan = null;
|
|
45
|
+
// Load existing plan for this task if one exists
|
|
46
|
+
try {
|
|
47
|
+
currentPlan = await getPlan(e.detail.id);
|
|
48
|
+
} catch {
|
|
49
|
+
// No plan yet — that's fine
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function handleGeneratePlan(e: CustomEvent<UnifiedTask>) {
|
|
54
|
+
selectedTask = e.detail;
|
|
55
|
+
currentPlan = null;
|
|
56
|
+
planLoading = true;
|
|
57
|
+
drawerLines = [];
|
|
58
|
+
drawerOpen = true;
|
|
59
|
+
|
|
60
|
+
const task = e.detail;
|
|
61
|
+
addAudit('info', `Plan generation started for: ${task.title}`);
|
|
62
|
+
drawerLines = [...drawerLines, `[${new Date().toLocaleTimeString()}] Generate plan: ${task.title}`];
|
|
63
|
+
|
|
64
|
+
// Use SSE stream for live output in the drawer
|
|
65
|
+
try {
|
|
66
|
+
const evtSource = new EventSource(`${API_BASE}/api/tasks/${task.id}/plan-stream`);
|
|
67
|
+
|
|
68
|
+
evtSource.onmessage = (event) => {
|
|
69
|
+
try {
|
|
70
|
+
const msg = JSON.parse(event.data);
|
|
71
|
+
if (msg.type === 'chunk') {
|
|
72
|
+
drawerLines = [...drawerLines, msg.text.trimEnd()];
|
|
73
|
+
} else if (msg.type === 'done') {
|
|
74
|
+
currentPlan = msg.plan as Plan;
|
|
75
|
+
planLoading = false;
|
|
76
|
+
addAudit('success', `Plan ready: ${currentPlan.steps.length} steps`);
|
|
77
|
+
drawerLines = [...drawerLines, `Plan ready — ${currentPlan.steps.length} steps generated.`];
|
|
78
|
+
evtSource.close();
|
|
79
|
+
} else if (msg.type === 'error') {
|
|
80
|
+
addAudit('error', `Plan failed: ${msg.message}`);
|
|
81
|
+
drawerLines = [...drawerLines, `Error: ${msg.message}`];
|
|
82
|
+
planLoading = false;
|
|
83
|
+
evtSource.close();
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Ignore malformed SSE frames
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
evtSource.onerror = () => {
|
|
91
|
+
planLoading = false;
|
|
92
|
+
addAudit('warn', 'SSE stream closed unexpectedly');
|
|
93
|
+
evtSource.close();
|
|
94
|
+
};
|
|
95
|
+
} catch (err) {
|
|
96
|
+
// Fallback: direct POST without streaming
|
|
97
|
+
try {
|
|
98
|
+
const plan = await generatePlan(task.id);
|
|
99
|
+
currentPlan = plan;
|
|
100
|
+
addAudit('success', `Plan ready: ${plan.steps.length} steps`);
|
|
101
|
+
} catch (genErr) {
|
|
102
|
+
addAudit('error', `Plan generation failed: ${genErr}`);
|
|
103
|
+
} finally {
|
|
104
|
+
planLoading = false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handlePlanUpdated(e: CustomEvent<Plan>) {
|
|
110
|
+
currentPlan = e.detail;
|
|
111
|
+
addAudit('info', `Plan updated — status: ${e.detail.status}`);
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<!-- Three-column layout + bottom drawer -->
|
|
116
|
+
<div class="layout-grid">
|
|
117
|
+
<!-- Left: Task Inbox -->
|
|
118
|
+
<aside class="sidebar sidebar-left" aria-label="Task inbox">
|
|
119
|
+
<div class="sidebar-header">
|
|
120
|
+
<h2 class="sidebar-title">Inbox</h2>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="sidebar-body">
|
|
123
|
+
<InboxFeed
|
|
124
|
+
selectedTaskId={selectedTask?.id ?? null}
|
|
125
|
+
on:selectTask={handleSelectTask}
|
|
126
|
+
on:generatePlan={handleGeneratePlan}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</aside>
|
|
130
|
+
|
|
131
|
+
<!-- Center: Plan Viewer -->
|
|
132
|
+
<section class="center-panel" aria-label="Plan viewer">
|
|
133
|
+
{#if selectedTask}
|
|
134
|
+
<div class="plan-viewer">
|
|
135
|
+
<div class="plan-header">
|
|
136
|
+
<div class="plan-header-top">
|
|
137
|
+
<span class="plan-id">{selectedTask.source}:{selectedTask.source_id}</span>
|
|
138
|
+
<StatusBadge status={taskStatusBadge(selectedTask.status)} />
|
|
139
|
+
</div>
|
|
140
|
+
<h1 class="plan-title">{selectedTask.title}</h1>
|
|
141
|
+
{#if selectedTask.body}
|
|
142
|
+
<p class="plan-body">{selectedTask.body}</p>
|
|
143
|
+
{/if}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<KawaiiCard accent={sourceAccent[selectedTask.source] ?? 'green'}>
|
|
147
|
+
<span slot="header" class="card-label">Execution Plan</span>
|
|
148
|
+
|
|
149
|
+
<PlanView
|
|
150
|
+
plan={currentPlan}
|
|
151
|
+
loading={planLoading}
|
|
152
|
+
on:planUpdated={handlePlanUpdated}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<div slot="footer" class="plan-actions">
|
|
156
|
+
<PillButton
|
|
157
|
+
variant="primary"
|
|
158
|
+
size="sm"
|
|
159
|
+
disabled={planLoading}
|
|
160
|
+
on:click={() => { if (selectedTask) handleGeneratePlan(new CustomEvent('generatePlan', { detail: selectedTask })); }}
|
|
161
|
+
>
|
|
162
|
+
{planLoading ? 'Generating…' : currentPlan ? 'Regenerate Plan' : 'Generate Plan'}
|
|
163
|
+
</PillButton>
|
|
164
|
+
{#if selectedTask.source_url}
|
|
165
|
+
<PillButton variant="ghost" size="sm" on:click={() => window.open(selectedTask?.source_url ?? '', '_blank')}>
|
|
166
|
+
Open Source
|
|
167
|
+
</PillButton>
|
|
168
|
+
{/if}
|
|
169
|
+
</div>
|
|
170
|
+
</KawaiiCard>
|
|
171
|
+
</div>
|
|
172
|
+
{:else}
|
|
173
|
+
<EmptyState
|
|
174
|
+
emoji="🗂️"
|
|
175
|
+
title="Select a task"
|
|
176
|
+
message="Choose a task from the inbox to view its plan."
|
|
177
|
+
/>
|
|
178
|
+
{/if}
|
|
179
|
+
</section>
|
|
180
|
+
|
|
181
|
+
<!-- Right: Audit Log -->
|
|
182
|
+
<aside class="sidebar sidebar-right" aria-label="Audit log">
|
|
183
|
+
<div class="sidebar-header">
|
|
184
|
+
<h2 class="sidebar-title">Audit Log</h2>
|
|
185
|
+
{#if auditEntries.length > 0}
|
|
186
|
+
<span class="sidebar-count">{auditEntries.length}</span>
|
|
187
|
+
{/if}
|
|
188
|
+
</div>
|
|
189
|
+
<div class="sidebar-body">
|
|
190
|
+
{#if auditEntries.length === 0}
|
|
191
|
+
<EmptyState
|
|
192
|
+
emoji="📋"
|
|
193
|
+
title="No events yet"
|
|
194
|
+
message="Audit events will appear here."
|
|
195
|
+
/>
|
|
196
|
+
{:else}
|
|
197
|
+
<div class="audit-list">
|
|
198
|
+
{#each auditEntries as entry, i (i)}
|
|
199
|
+
<div class="audit-entry audit-{entry.level}">
|
|
200
|
+
<span class="audit-time">{entry.time}</span>
|
|
201
|
+
<span class="audit-msg">{entry.msg}</span>
|
|
202
|
+
</div>
|
|
203
|
+
{/each}
|
|
204
|
+
</div>
|
|
205
|
+
{/if}
|
|
206
|
+
</div>
|
|
207
|
+
</aside>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<!-- Bottom drawer for streaming agent output -->
|
|
211
|
+
<StreamDrawer bind:open={drawerOpen} lines={drawerLines} />
|
|
212
|
+
|
|
213
|
+
<style>
|
|
214
|
+
/* ─── Three-column layout ─── */
|
|
215
|
+
.layout-grid {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex: 1;
|
|
218
|
+
height: calc(100vh - 57px - 36px); /* minus nav height minus drawer handle */
|
|
219
|
+
overflow: hidden;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.sidebar {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
background: var(--bg-sidebar);
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
flex-shrink: 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.sidebar-left {
|
|
231
|
+
width: 280px;
|
|
232
|
+
border-right: 1px solid var(--border-card);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.sidebar-right {
|
|
236
|
+
width: 320px;
|
|
237
|
+
border-left: 1px solid var(--border-card);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.sidebar-header {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
justify-content: space-between;
|
|
244
|
+
padding: 0.75rem 1rem 0.5rem;
|
|
245
|
+
border-bottom: 1px solid var(--border-card);
|
|
246
|
+
background: var(--bg-card);
|
|
247
|
+
flex-shrink: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.sidebar-title {
|
|
251
|
+
font-size: 0.72rem;
|
|
252
|
+
font-weight: 800;
|
|
253
|
+
letter-spacing: 0.08em;
|
|
254
|
+
text-transform: uppercase;
|
|
255
|
+
color: var(--text-muted);
|
|
256
|
+
margin: 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.sidebar-count {
|
|
260
|
+
font-size: 0.68rem;
|
|
261
|
+
font-weight: 800;
|
|
262
|
+
background: var(--border-card);
|
|
263
|
+
color: var(--text-muted);
|
|
264
|
+
padding: 2px 7px;
|
|
265
|
+
border-radius: 9999px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.sidebar-body {
|
|
269
|
+
flex: 1;
|
|
270
|
+
overflow-y: auto;
|
|
271
|
+
padding: 0.75rem;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.center-panel {
|
|
275
|
+
flex: 1;
|
|
276
|
+
overflow-y: auto;
|
|
277
|
+
padding: 1.25rem;
|
|
278
|
+
min-width: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* ─── Plan viewer ─── */
|
|
282
|
+
.plan-viewer {
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
gap: 1rem;
|
|
286
|
+
max-width: 680px;
|
|
287
|
+
margin: 0 auto;
|
|
288
|
+
animation: fadeUp 0.3s ease-out both;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@keyframes fadeUp {
|
|
292
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
293
|
+
to { opacity: 1; transform: translateY(0); }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.plan-header {
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
299
|
+
gap: 0.4rem;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.plan-header-top {
|
|
303
|
+
display: flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
gap: 0.75rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.plan-id {
|
|
309
|
+
font-size: 0.75rem;
|
|
310
|
+
font-weight: 800;
|
|
311
|
+
letter-spacing: 0.06em;
|
|
312
|
+
color: var(--text-muted);
|
|
313
|
+
font-family: 'Menlo', monospace;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.plan-title {
|
|
317
|
+
font-size: 1.25rem;
|
|
318
|
+
font-weight: 800;
|
|
319
|
+
color: var(--text-primary);
|
|
320
|
+
line-height: 1.3;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.plan-body {
|
|
324
|
+
font-size: 0.85rem;
|
|
325
|
+
color: var(--text-secondary);
|
|
326
|
+
line-height: 1.5;
|
|
327
|
+
margin: 0;
|
|
328
|
+
max-height: 100px;
|
|
329
|
+
overflow-y: auto;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.card-label {
|
|
333
|
+
font-size: 0.72rem;
|
|
334
|
+
font-weight: 800;
|
|
335
|
+
text-transform: uppercase;
|
|
336
|
+
letter-spacing: 0.08em;
|
|
337
|
+
color: var(--text-muted);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
.plan-actions {
|
|
342
|
+
display: flex;
|
|
343
|
+
gap: 0.5rem;
|
|
344
|
+
flex-wrap: wrap;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* ─── Audit log ─── */
|
|
348
|
+
.audit-list {
|
|
349
|
+
display: flex;
|
|
350
|
+
flex-direction: column;
|
|
351
|
+
gap: 0.25rem;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.audit-entry {
|
|
355
|
+
display: flex;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
gap: 0.15rem;
|
|
358
|
+
padding: 0.4rem 0.5rem;
|
|
359
|
+
border-radius: 8px;
|
|
360
|
+
font-size: 0.72rem;
|
|
361
|
+
border-left: 3px solid transparent;
|
|
362
|
+
transition: background 0.1s;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.audit-entry:hover { background: rgba(76, 175, 80, 0.04); }
|
|
366
|
+
|
|
367
|
+
.audit-info { border-left-color: #66bb6a; }
|
|
368
|
+
.audit-warn { border-left-color: #ffd54f; background: rgba(255, 213, 79, 0.06); }
|
|
369
|
+
.audit-success { border-left-color: #4caf50; background: rgba(76, 175, 80, 0.06); }
|
|
370
|
+
.audit-error { border-left-color: #ef5350; background: rgba(239, 83, 80, 0.06); }
|
|
371
|
+
|
|
372
|
+
.audit-time {
|
|
373
|
+
font-family: 'Menlo', monospace;
|
|
374
|
+
font-size: 0.65rem;
|
|
375
|
+
color: var(--text-muted);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.audit-msg {
|
|
379
|
+
color: var(--text-secondary);
|
|
380
|
+
font-weight: 600;
|
|
381
|
+
line-height: 1.4;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* ─── Mobile responsive ─── */
|
|
385
|
+
@media (max-width: 768px) {
|
|
386
|
+
.layout-grid {
|
|
387
|
+
flex-direction: column;
|
|
388
|
+
height: auto;
|
|
389
|
+
overflow: visible;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.sidebar-left,
|
|
393
|
+
.sidebar-right {
|
|
394
|
+
width: 100%;
|
|
395
|
+
border-right: none;
|
|
396
|
+
border-left: none;
|
|
397
|
+
border-bottom: 1px solid var(--border-card);
|
|
398
|
+
max-height: 40vh;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
</style>
|