@ryanfw/prompt-orchestration-pipeline 0.0.1
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 -0
- package/README.md +290 -0
- package/package.json +51 -0
- package/src/api/index.js +220 -0
- package/src/cli/index.js +70 -0
- package/src/core/config.js +345 -0
- package/src/core/environment.js +56 -0
- package/src/core/orchestrator.js +335 -0
- package/src/core/pipeline-runner.js +182 -0
- package/src/core/retry.js +83 -0
- package/src/core/task-runner.js +305 -0
- package/src/core/validation.js +100 -0
- package/src/llm/README.md +345 -0
- package/src/llm/index.js +320 -0
- package/src/providers/anthropic.js +117 -0
- package/src/providers/base.js +71 -0
- package/src/providers/deepseek.js +122 -0
- package/src/providers/openai.js +314 -0
- package/src/ui/README.md +86 -0
- package/src/ui/public/app.js +260 -0
- package/src/ui/public/index.html +53 -0
- package/src/ui/public/style.css +341 -0
- package/src/ui/server.js +230 -0
- package/src/ui/state.js +67 -0
- package/src/ui/watcher.js +85 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend application for Pipeline Orchestrator UI
|
|
3
|
+
* Handles SSE connection, state updates, and UI rendering
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// DOM elements
|
|
7
|
+
const elements = {
|
|
8
|
+
changeCount: document.getElementById("changeCount"),
|
|
9
|
+
updatedAt: document.getElementById("updatedAt"),
|
|
10
|
+
changesList: document.getElementById("changesList"),
|
|
11
|
+
watchedPaths: document.getElementById("watchedPaths"),
|
|
12
|
+
connectionStatus: document.getElementById("connectionStatus"),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Connection state
|
|
16
|
+
let eventSource = null;
|
|
17
|
+
let reconnectTimer = null;
|
|
18
|
+
const RECONNECT_DELAY = 3000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Format timestamp as relative time
|
|
22
|
+
*/
|
|
23
|
+
function formatRelativeTime(timestamp) {
|
|
24
|
+
if (!timestamp) return "Never";
|
|
25
|
+
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const then = new Date(timestamp);
|
|
28
|
+
const diffMs = now - then;
|
|
29
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
30
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
31
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
32
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
33
|
+
|
|
34
|
+
if (diffSec < 10) return "Just now";
|
|
35
|
+
if (diffSec < 60) return `${diffSec} seconds ago`;
|
|
36
|
+
if (diffMin === 1) return "1 minute ago";
|
|
37
|
+
if (diffMin < 60) return `${diffMin} minutes ago`;
|
|
38
|
+
if (diffHour === 1) return "1 hour ago";
|
|
39
|
+
if (diffHour < 24) return `${diffHour} hours ago`;
|
|
40
|
+
if (diffDay === 1) return "1 day ago";
|
|
41
|
+
return `${diffDay} days ago`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format absolute timestamp
|
|
46
|
+
*/
|
|
47
|
+
function formatAbsoluteTime(timestamp) {
|
|
48
|
+
if (!timestamp) return "Never";
|
|
49
|
+
const date = new Date(timestamp);
|
|
50
|
+
return date.toLocaleString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Update connection status indicator
|
|
55
|
+
*/
|
|
56
|
+
function updateConnectionStatus(status) {
|
|
57
|
+
const statusDot = elements.connectionStatus.querySelector(".status-dot");
|
|
58
|
+
const statusText = elements.connectionStatus.querySelector(".status-text");
|
|
59
|
+
|
|
60
|
+
statusDot.className = "status-dot";
|
|
61
|
+
|
|
62
|
+
switch (status) {
|
|
63
|
+
case "connected":
|
|
64
|
+
statusDot.classList.add("connected");
|
|
65
|
+
statusText.textContent = "Connected";
|
|
66
|
+
break;
|
|
67
|
+
case "reconnecting":
|
|
68
|
+
statusDot.classList.add("reconnecting");
|
|
69
|
+
statusText.textContent = "Reconnecting...";
|
|
70
|
+
break;
|
|
71
|
+
case "disconnected":
|
|
72
|
+
statusDot.classList.add("disconnected");
|
|
73
|
+
statusText.textContent = "Disconnected";
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
statusText.textContent = "Connecting...";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render the UI with current state
|
|
82
|
+
*/
|
|
83
|
+
function renderState(state) {
|
|
84
|
+
// Update change count
|
|
85
|
+
elements.changeCount.textContent = state.changeCount || 0;
|
|
86
|
+
|
|
87
|
+
// Update last updated time
|
|
88
|
+
elements.updatedAt.textContent = formatRelativeTime(state.updatedAt);
|
|
89
|
+
elements.updatedAt.title = formatAbsoluteTime(state.updatedAt);
|
|
90
|
+
|
|
91
|
+
// Update watched paths
|
|
92
|
+
if (state.watchedPaths && state.watchedPaths.length > 0) {
|
|
93
|
+
const pathsSpan = elements.watchedPaths.querySelector(".paths");
|
|
94
|
+
pathsSpan.textContent = state.watchedPaths.join(", ");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Render recent changes
|
|
98
|
+
if (state.recentChanges && state.recentChanges.length > 0) {
|
|
99
|
+
elements.changesList.innerHTML = state.recentChanges
|
|
100
|
+
.map((change) => {
|
|
101
|
+
const typeClass = `change-type-${change.type}`;
|
|
102
|
+
const typeLabel =
|
|
103
|
+
change.type.charAt(0).toUpperCase() + change.type.slice(1);
|
|
104
|
+
const relativeTime = formatRelativeTime(change.timestamp);
|
|
105
|
+
const absoluteTime = formatAbsoluteTime(change.timestamp);
|
|
106
|
+
|
|
107
|
+
return `
|
|
108
|
+
<div class="change-item">
|
|
109
|
+
<div class="change-header">
|
|
110
|
+
<span class="change-type ${typeClass}">${typeLabel}</span>
|
|
111
|
+
<span class="change-time" title="${absoluteTime}">${relativeTime}</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="change-path">${escapeHtml(change.path)}</div>
|
|
114
|
+
</div>
|
|
115
|
+
`;
|
|
116
|
+
})
|
|
117
|
+
.join("");
|
|
118
|
+
} else {
|
|
119
|
+
elements.changesList.innerHTML =
|
|
120
|
+
'<div class="empty-state">No changes yet. Modify files in watched directories to see updates.</div>';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Escape HTML to prevent XSS
|
|
126
|
+
*/
|
|
127
|
+
function escapeHtml(text) {
|
|
128
|
+
const div = document.createElement("div");
|
|
129
|
+
div.textContent = text;
|
|
130
|
+
return div.innerHTML;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Fetch initial state from API
|
|
135
|
+
*/
|
|
136
|
+
async function fetchInitialState() {
|
|
137
|
+
try {
|
|
138
|
+
const response = await fetch("/api/state");
|
|
139
|
+
if (!response.ok) throw new Error("Failed to fetch state");
|
|
140
|
+
const state = await response.json();
|
|
141
|
+
renderState(state);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("Error fetching initial state:", error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Connect to SSE endpoint
|
|
149
|
+
*/
|
|
150
|
+
function connectSSE() {
|
|
151
|
+
// Clean up existing connection
|
|
152
|
+
if (eventSource) {
|
|
153
|
+
eventSource.close();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Clear reconnect timer
|
|
157
|
+
if (reconnectTimer) {
|
|
158
|
+
clearTimeout(reconnectTimer);
|
|
159
|
+
reconnectTimer = null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
updateConnectionStatus("connecting");
|
|
163
|
+
|
|
164
|
+
// Create new EventSource
|
|
165
|
+
eventSource = new EventSource("/api/events");
|
|
166
|
+
|
|
167
|
+
// Handle state updates
|
|
168
|
+
eventSource.addEventListener("state", (event) => {
|
|
169
|
+
try {
|
|
170
|
+
const state = JSON.parse(event.data);
|
|
171
|
+
renderState(state);
|
|
172
|
+
updateConnectionStatus("connected");
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error("Error parsing state event:", error);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Handle connection open
|
|
179
|
+
eventSource.addEventListener("open", () => {
|
|
180
|
+
console.log("SSE connection established");
|
|
181
|
+
updateConnectionStatus("connected");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Handle errors
|
|
185
|
+
eventSource.addEventListener("error", (error) => {
|
|
186
|
+
console.error("SSE connection error:", error);
|
|
187
|
+
|
|
188
|
+
if (eventSource.readyState === EventSource.CLOSED) {
|
|
189
|
+
updateConnectionStatus("disconnected");
|
|
190
|
+
scheduleReconnect();
|
|
191
|
+
} else {
|
|
192
|
+
updateConnectionStatus("reconnecting");
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Schedule reconnection attempt
|
|
199
|
+
*/
|
|
200
|
+
function scheduleReconnect() {
|
|
201
|
+
if (reconnectTimer) return;
|
|
202
|
+
|
|
203
|
+
updateConnectionStatus("reconnecting");
|
|
204
|
+
|
|
205
|
+
reconnectTimer = setTimeout(() => {
|
|
206
|
+
console.log("Attempting to reconnect...");
|
|
207
|
+
connectSSE();
|
|
208
|
+
}, RECONNECT_DELAY);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Initialize the application
|
|
213
|
+
*/
|
|
214
|
+
async function init() {
|
|
215
|
+
console.log("Initializing Pipeline Orchestrator UI...");
|
|
216
|
+
|
|
217
|
+
// Fetch initial state
|
|
218
|
+
await fetchInitialState();
|
|
219
|
+
|
|
220
|
+
// Connect to SSE
|
|
221
|
+
connectSSE();
|
|
222
|
+
|
|
223
|
+
// Update relative times every 10 seconds
|
|
224
|
+
setInterval(() => {
|
|
225
|
+
const updatedAt = elements.updatedAt.textContent;
|
|
226
|
+
if (updatedAt !== "Never") {
|
|
227
|
+
const timestamp = elements.updatedAt.title;
|
|
228
|
+
if (timestamp) {
|
|
229
|
+
elements.updatedAt.textContent = formatRelativeTime(
|
|
230
|
+
new Date(timestamp)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Update change times
|
|
236
|
+
document.querySelectorAll(".change-time").forEach((el) => {
|
|
237
|
+
const absoluteTime = el.title;
|
|
238
|
+
if (absoluteTime) {
|
|
239
|
+
el.textContent = formatRelativeTime(new Date(absoluteTime));
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}, 10000);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Start the application when DOM is ready
|
|
246
|
+
if (document.readyState === "loading") {
|
|
247
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
248
|
+
} else {
|
|
249
|
+
init();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Clean up on page unload
|
|
253
|
+
window.addEventListener("beforeunload", () => {
|
|
254
|
+
if (eventSource) {
|
|
255
|
+
eventSource.close();
|
|
256
|
+
}
|
|
257
|
+
if (reconnectTimer) {
|
|
258
|
+
clearTimeout(reconnectTimer);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Pipeline Orchestrator - File Watcher</title>
|
|
7
|
+
<link rel="stylesheet" href="/style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<header>
|
|
12
|
+
<h1>Pipeline Orchestrator</h1>
|
|
13
|
+
<div class="connection-status" id="connectionStatus">
|
|
14
|
+
<span class="status-dot"></span>
|
|
15
|
+
<span class="status-text">Connecting...</span>
|
|
16
|
+
</div>
|
|
17
|
+
</header>
|
|
18
|
+
|
|
19
|
+
<main>
|
|
20
|
+
<section class="stats-section">
|
|
21
|
+
<div class="stat-card">
|
|
22
|
+
<div class="stat-label">Total Changes</div>
|
|
23
|
+
<div class="stat-value" id="changeCount">0</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="stat-card">
|
|
26
|
+
<div class="stat-label">Last Updated</div>
|
|
27
|
+
<div class="stat-value stat-time" id="updatedAt">Never</div>
|
|
28
|
+
</div>
|
|
29
|
+
</section>
|
|
30
|
+
|
|
31
|
+
<section class="changes-section">
|
|
32
|
+
<h2>Recent Changes</h2>
|
|
33
|
+
<div class="watched-paths" id="watchedPaths">
|
|
34
|
+
<span class="label">Watching:</span>
|
|
35
|
+
<span class="paths">Loading...</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="changes-list" id="changesList">
|
|
38
|
+
<div class="empty-state">
|
|
39
|
+
No changes yet. Modify files in watched directories to see
|
|
40
|
+
updates.
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
</main>
|
|
45
|
+
|
|
46
|
+
<footer>
|
|
47
|
+
<p>Watching for file changes in real-time via Server-Sent Events</p>
|
|
48
|
+
</footer>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<script src="/app.js"></script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Orchestrator UI Styles
|
|
3
|
+
* Modern, clean, dark-mode friendly design
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--bg-primary: #0f172a;
|
|
8
|
+
--bg-secondary: #1e293b;
|
|
9
|
+
--bg-tertiary: #334155;
|
|
10
|
+
--text-primary: #f1f5f9;
|
|
11
|
+
--text-secondary: #cbd5e1;
|
|
12
|
+
--text-muted: #94a3b8;
|
|
13
|
+
--border-color: #334155;
|
|
14
|
+
--accent-blue: #3b82f6;
|
|
15
|
+
--accent-green: #10b981;
|
|
16
|
+
--accent-yellow: #f59e0b;
|
|
17
|
+
--accent-red: #ef4444;
|
|
18
|
+
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* {
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
font-family:
|
|
29
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
|
30
|
+
Arial, sans-serif;
|
|
31
|
+
background: var(--bg-primary);
|
|
32
|
+
color: var(--text-primary);
|
|
33
|
+
line-height: 1.6;
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.container {
|
|
38
|
+
max-width: 1200px;
|
|
39
|
+
margin: 0 auto;
|
|
40
|
+
padding: 2rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Header */
|
|
44
|
+
header {
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
align-items: center;
|
|
48
|
+
margin-bottom: 3rem;
|
|
49
|
+
padding-bottom: 1.5rem;
|
|
50
|
+
border-bottom: 2px solid var(--border-color);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
h1 {
|
|
54
|
+
font-size: 2rem;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
color: var(--text-primary);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
h2 {
|
|
60
|
+
font-size: 1.5rem;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
margin-bottom: 1rem;
|
|
63
|
+
color: var(--text-primary);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Connection Status */
|
|
67
|
+
.connection-status {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 0.5rem;
|
|
71
|
+
padding: 0.5rem 1rem;
|
|
72
|
+
background: var(--bg-secondary);
|
|
73
|
+
border-radius: 0.5rem;
|
|
74
|
+
border: 1px solid var(--border-color);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.status-dot {
|
|
78
|
+
width: 10px;
|
|
79
|
+
height: 10px;
|
|
80
|
+
border-radius: 50%;
|
|
81
|
+
background: var(--text-muted);
|
|
82
|
+
animation: pulse 2s ease-in-out infinite;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.status-dot.connected {
|
|
86
|
+
background: var(--accent-green);
|
|
87
|
+
animation: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.status-dot.reconnecting {
|
|
91
|
+
background: var(--accent-yellow);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.status-dot.disconnected {
|
|
95
|
+
background: var(--accent-red);
|
|
96
|
+
animation: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.status-text {
|
|
100
|
+
font-size: 0.875rem;
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
color: var(--text-secondary);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@keyframes pulse {
|
|
106
|
+
0%,
|
|
107
|
+
100% {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
}
|
|
110
|
+
50% {
|
|
111
|
+
opacity: 0.5;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Stats Section */
|
|
116
|
+
.stats-section {
|
|
117
|
+
display: grid;
|
|
118
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
119
|
+
gap: 1.5rem;
|
|
120
|
+
margin-bottom: 3rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.stat-card {
|
|
124
|
+
background: var(--bg-secondary);
|
|
125
|
+
padding: 2rem;
|
|
126
|
+
border-radius: 0.75rem;
|
|
127
|
+
border: 1px solid var(--border-color);
|
|
128
|
+
box-shadow: var(--shadow);
|
|
129
|
+
transition:
|
|
130
|
+
transform 0.2s ease,
|
|
131
|
+
box-shadow 0.2s ease;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.stat-card:hover {
|
|
135
|
+
transform: translateY(-2px);
|
|
136
|
+
box-shadow: 0 8px 12px -2px rgba(0, 0, 0, 0.4);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.stat-label {
|
|
140
|
+
font-size: 0.875rem;
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
color: var(--text-muted);
|
|
143
|
+
text-transform: uppercase;
|
|
144
|
+
letter-spacing: 0.05em;
|
|
145
|
+
margin-bottom: 0.5rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.stat-value {
|
|
149
|
+
font-size: 3rem;
|
|
150
|
+
font-weight: 700;
|
|
151
|
+
color: var(--accent-blue);
|
|
152
|
+
line-height: 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.stat-value.stat-time {
|
|
156
|
+
font-size: 1.5rem;
|
|
157
|
+
color: var(--text-primary);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Changes Section */
|
|
161
|
+
.changes-section {
|
|
162
|
+
background: var(--bg-secondary);
|
|
163
|
+
padding: 2rem;
|
|
164
|
+
border-radius: 0.75rem;
|
|
165
|
+
border: 1px solid var(--border-color);
|
|
166
|
+
box-shadow: var(--shadow);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.watched-paths {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
gap: 0.5rem;
|
|
173
|
+
margin-bottom: 1.5rem;
|
|
174
|
+
padding: 0.75rem 1rem;
|
|
175
|
+
background: var(--bg-tertiary);
|
|
176
|
+
border-radius: 0.5rem;
|
|
177
|
+
font-size: 0.875rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.watched-paths .label {
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
color: var(--text-secondary);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.watched-paths .paths {
|
|
186
|
+
color: var(--accent-blue);
|
|
187
|
+
font-family: "Monaco", "Courier New", monospace;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Changes List */
|
|
191
|
+
.changes-list {
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
gap: 0.75rem;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.empty-state {
|
|
198
|
+
padding: 3rem 2rem;
|
|
199
|
+
text-align: center;
|
|
200
|
+
color: var(--text-muted);
|
|
201
|
+
font-size: 0.875rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.change-item {
|
|
205
|
+
background: var(--bg-tertiary);
|
|
206
|
+
padding: 1rem;
|
|
207
|
+
border-radius: 0.5rem;
|
|
208
|
+
border: 1px solid var(--border-color);
|
|
209
|
+
transition:
|
|
210
|
+
background 0.2s ease,
|
|
211
|
+
transform 0.2s ease;
|
|
212
|
+
animation: slideIn 0.3s ease;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.change-item:hover {
|
|
216
|
+
background: #3f4b5e;
|
|
217
|
+
transform: translateX(4px);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@keyframes slideIn {
|
|
221
|
+
from {
|
|
222
|
+
opacity: 0;
|
|
223
|
+
transform: translateY(-10px);
|
|
224
|
+
}
|
|
225
|
+
to {
|
|
226
|
+
opacity: 1;
|
|
227
|
+
transform: translateY(0);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.change-header {
|
|
232
|
+
display: flex;
|
|
233
|
+
justify-content: space-between;
|
|
234
|
+
align-items: center;
|
|
235
|
+
margin-bottom: 0.5rem;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.change-type {
|
|
239
|
+
display: inline-block;
|
|
240
|
+
padding: 0.25rem 0.75rem;
|
|
241
|
+
border-radius: 0.25rem;
|
|
242
|
+
font-size: 0.75rem;
|
|
243
|
+
font-weight: 600;
|
|
244
|
+
text-transform: uppercase;
|
|
245
|
+
letter-spacing: 0.05em;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.change-type-created {
|
|
249
|
+
background: rgba(16, 185, 129, 0.2);
|
|
250
|
+
color: var(--accent-green);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.change-type-modified {
|
|
254
|
+
background: rgba(59, 130, 246, 0.2);
|
|
255
|
+
color: var(--accent-blue);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.change-type-deleted {
|
|
259
|
+
background: rgba(239, 68, 68, 0.2);
|
|
260
|
+
color: var(--accent-red);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.change-time {
|
|
264
|
+
font-size: 0.75rem;
|
|
265
|
+
color: var(--text-muted);
|
|
266
|
+
cursor: help;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.change-path {
|
|
270
|
+
font-family: "Monaco", "Courier New", monospace;
|
|
271
|
+
font-size: 0.875rem;
|
|
272
|
+
color: var(--text-secondary);
|
|
273
|
+
word-break: break-all;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Footer */
|
|
277
|
+
footer {
|
|
278
|
+
margin-top: 3rem;
|
|
279
|
+
padding-top: 2rem;
|
|
280
|
+
border-top: 1px solid var(--border-color);
|
|
281
|
+
text-align: center;
|
|
282
|
+
color: var(--text-muted);
|
|
283
|
+
font-size: 0.875rem;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Responsive Design */
|
|
287
|
+
@media (max-width: 768px) {
|
|
288
|
+
.container {
|
|
289
|
+
padding: 1rem;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
header {
|
|
293
|
+
flex-direction: column;
|
|
294
|
+
gap: 1rem;
|
|
295
|
+
align-items: flex-start;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
h1 {
|
|
299
|
+
font-size: 1.5rem;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.stats-section {
|
|
303
|
+
grid-template-columns: 1fr;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.stat-value {
|
|
307
|
+
font-size: 2.5rem;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.watched-paths {
|
|
311
|
+
flex-direction: column;
|
|
312
|
+
align-items: flex-start;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* Accessibility */
|
|
317
|
+
@media (prefers-reduced-motion: reduce) {
|
|
318
|
+
* {
|
|
319
|
+
animation-duration: 0.01ms !important;
|
|
320
|
+
animation-iteration-count: 1 !important;
|
|
321
|
+
transition-duration: 0.01ms !important;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Light mode support (optional) */
|
|
326
|
+
@media (prefers-color-scheme: light) {
|
|
327
|
+
:root {
|
|
328
|
+
--bg-primary: #f8fafc;
|
|
329
|
+
--bg-secondary: #ffffff;
|
|
330
|
+
--bg-tertiary: #f1f5f9;
|
|
331
|
+
--text-primary: #0f172a;
|
|
332
|
+
--text-secondary: #475569;
|
|
333
|
+
--text-muted: #64748b;
|
|
334
|
+
--border-color: #e2e8f0;
|
|
335
|
+
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.change-item:hover {
|
|
339
|
+
background: #e2e8f0;
|
|
340
|
+
}
|
|
341
|
+
}
|