shipwright-cli 2.2.2 → 2.3.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/README.md +12 -11
- package/dashboard/public/index.html +224 -8
- package/dashboard/public/styles.css +1078 -4
- package/dashboard/server.ts +1100 -15
- package/dashboard/src/canvas/interactions.ts +74 -0
- package/dashboard/src/canvas/layout.ts +85 -0
- package/dashboard/src/canvas/overlays.ts +117 -0
- package/dashboard/src/canvas/particles.ts +105 -0
- package/dashboard/src/canvas/renderer.ts +191 -0
- package/dashboard/src/components/charts/bar.ts +54 -0
- package/dashboard/src/components/charts/donut.ts +25 -0
- package/dashboard/src/components/charts/pipeline-rail.ts +105 -0
- package/dashboard/src/components/charts/sparkline.ts +82 -0
- package/dashboard/src/components/header.ts +616 -0
- package/dashboard/src/components/modal.ts +413 -0
- package/dashboard/src/components/terminal.ts +144 -0
- package/dashboard/src/core/api.test.ts +362 -0
- package/dashboard/src/core/api.ts +381 -0
- package/dashboard/src/core/helpers.ts +118 -0
- package/dashboard/src/core/router.test.ts +266 -0
- package/dashboard/src/core/router.ts +190 -0
- package/dashboard/src/core/sse.ts +38 -0
- package/dashboard/src/core/state.test.ts +235 -0
- package/dashboard/src/core/state.ts +150 -0
- package/dashboard/src/core/ws.test.ts +216 -0
- package/dashboard/src/core/ws.ts +143 -0
- package/dashboard/src/design/icons.test.ts +105 -0
- package/dashboard/src/design/icons.ts +131 -0
- package/dashboard/src/design/tokens.test.ts +204 -0
- package/dashboard/src/design/tokens.ts +160 -0
- package/dashboard/src/main.ts +68 -0
- package/dashboard/src/types/api.ts +337 -0
- package/dashboard/src/views/activity.ts +185 -0
- package/dashboard/src/views/agent-cockpit.ts +236 -0
- package/dashboard/src/views/agents.ts +72 -0
- package/dashboard/src/views/fleet-map.ts +299 -0
- package/dashboard/src/views/insights.ts +298 -0
- package/dashboard/src/views/machines.ts +162 -0
- package/dashboard/src/views/metrics.ts +420 -0
- package/dashboard/src/views/overview.ts +409 -0
- package/dashboard/src/views/pipeline-theater.ts +219 -0
- package/dashboard/src/views/pipelines.ts +595 -0
- package/dashboard/src/views/team.ts +362 -0
- package/dashboard/src/views/timeline.ts +389 -0
- package/dashboard/tsconfig.json +21 -0
- package/dashboard/vitest.config.ts +27 -0
- package/docs/AGI-WHATS-NEXT.md +15 -15
- package/package.json +16 -2
- package/scripts/lib/helpers.sh +30 -0
- package/scripts/lib/pipeline-quality-checks.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +59 -0
- package/scripts/sw +86 -167
- package/scripts/sw-activity.sh +1 -1
- package/scripts/sw-adaptive.sh +1 -1
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +14 -6
- package/scripts/sw-autonomous.sh +230 -13
- package/scripts/sw-changelog.sh +2 -2
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +1 -1
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +1 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +2 -2
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1 -1
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +1 -1
- package/scripts/sw-doc-fleet.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +8 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +1 -1
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +1 -1
- package/scripts/sw-feedback.sh +1 -1
- package/scripts/sw-fix.sh +6 -5
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +1 -1
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +5 -2
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +1 -1
- package/scripts/sw-incident.sh +1 -1
- package/scripts/sw-init.sh +112 -9
- package/scripts/sw-instrument.sh +6 -1
- package/scripts/sw-intelligence.sh +5 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +20 -9
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +2 -1
- package/scripts/sw-memory.sh +10 -1
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +4 -1
- package/scripts/sw-otel.sh +1 -1
- package/scripts/sw-oversight.sh +1 -1
- package/scripts/sw-pipeline-composer.sh +3 -1
- package/scripts/sw-pipeline-vitals.sh +4 -6
- package/scripts/sw-pipeline.sh +4 -1
- package/scripts/sw-pm.sh +5 -2
- package/scripts/sw-pr-lifecycle.sh +1 -1
- package/scripts/sw-predictive.sh +4 -1
- package/scripts/sw-prep.sh +3 -2
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +10 -4
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +16 -0
- package/scripts/sw-regression.sh +2 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +7 -5
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +4 -1
- package/scripts/sw-scale.sh +4 -1
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +113 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +2 -1
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +2 -1
- package/scripts/sw-stream.sh +1 -1
- package/scripts/sw-swarm.sh +6 -1
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +3 -2
- package/scripts/sw-tmux-pipeline.sh +2 -1
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker-jira.sh +1 -0
- package/scripts/sw-tracker-linear.sh +1 -0
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-triage.sh +198 -11
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +2 -2
- package/scripts/sw-worktree.sh +1 -1
- package/dashboard/public/app.js +0 -4422
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// WebSocket connection with automatic reconnection, stale data indicator, offline resilience
|
|
2
|
+
|
|
3
|
+
import { store } from "./state";
|
|
4
|
+
import type { FleetState } from "../types/api";
|
|
5
|
+
|
|
6
|
+
let ws: WebSocket | null = null;
|
|
7
|
+
let reconnectDelay = 1000;
|
|
8
|
+
let connectionTimer: ReturnType<typeof setInterval> | null = null;
|
|
9
|
+
let lastDataTime = 0;
|
|
10
|
+
let staleTimer: ReturnType<typeof setInterval> | null = null;
|
|
11
|
+
let reconnectAttempts = 0;
|
|
12
|
+
|
|
13
|
+
function startConnectionTimer(): void {
|
|
14
|
+
stopConnectionTimer();
|
|
15
|
+
connectionTimer = setInterval(() => {
|
|
16
|
+
const connectedAt = store.get("connectedAt");
|
|
17
|
+
if (connectedAt) {
|
|
18
|
+
const elapsed = Math.floor((Date.now() - connectedAt) / 1000);
|
|
19
|
+
const h = String(Math.floor(elapsed / 3600)).padStart(2, "0");
|
|
20
|
+
const m = String(Math.floor((elapsed % 3600) / 60)).padStart(2, "0");
|
|
21
|
+
const s = String(elapsed % 60).padStart(2, "0");
|
|
22
|
+
const el = document.getElementById("connection-text");
|
|
23
|
+
if (el) el.textContent = `LIVE \u2014 ${h}:${m}:${s}`;
|
|
24
|
+
}
|
|
25
|
+
}, 1000);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function stopConnectionTimer(): void {
|
|
29
|
+
if (connectionTimer) {
|
|
30
|
+
clearInterval(connectionTimer);
|
|
31
|
+
connectionTimer = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function startStaleDataTimer(): void {
|
|
36
|
+
if (staleTimer) clearInterval(staleTimer);
|
|
37
|
+
staleTimer = setInterval(() => {
|
|
38
|
+
if (!lastDataTime) return;
|
|
39
|
+
const ageS = Math.floor((Date.now() - lastDataTime) / 1000);
|
|
40
|
+
const banner = document.getElementById("stale-data-banner");
|
|
41
|
+
if (ageS > 30 && banner) {
|
|
42
|
+
banner.style.display = "";
|
|
43
|
+
const ageEl = document.getElementById("stale-data-age");
|
|
44
|
+
if (ageEl) {
|
|
45
|
+
if (ageS < 60) ageEl.textContent = `${ageS}s`;
|
|
46
|
+
else if (ageS < 3600)
|
|
47
|
+
ageEl.textContent = `${Math.floor(ageS / 60)}m ${ageS % 60}s`;
|
|
48
|
+
else
|
|
49
|
+
ageEl.textContent = `${Math.floor(ageS / 3600)}h ${Math.floor((ageS % 3600) / 60)}m`;
|
|
50
|
+
}
|
|
51
|
+
} else if (banner) {
|
|
52
|
+
banner.style.display = "none";
|
|
53
|
+
}
|
|
54
|
+
}, 5000);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function updateConnectionStatus(status: "LIVE" | "OFFLINE"): void {
|
|
58
|
+
const dot = document.getElementById("connection-dot");
|
|
59
|
+
const text = document.getElementById("connection-text");
|
|
60
|
+
if (!dot || !text) return;
|
|
61
|
+
if (status === "LIVE") {
|
|
62
|
+
dot.className = "connection-dot live";
|
|
63
|
+
text.textContent = "LIVE \u2014 00:00:00";
|
|
64
|
+
reconnectAttempts = 0;
|
|
65
|
+
} else {
|
|
66
|
+
dot.className = "connection-dot offline";
|
|
67
|
+
text.textContent = `OFFLINE (retry ${reconnectAttempts})`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function showOfflineBanner(show: boolean): void {
|
|
72
|
+
let banner = document.getElementById("offline-banner");
|
|
73
|
+
if (!banner && show) {
|
|
74
|
+
banner = document.createElement("div");
|
|
75
|
+
banner.id = "offline-banner";
|
|
76
|
+
banner.className = "offline-banner";
|
|
77
|
+
banner.innerHTML =
|
|
78
|
+
`<span class="offline-icon">\u26A0</span>` +
|
|
79
|
+
`<span>Connection lost. Data may be stale.</span>` +
|
|
80
|
+
`<button class="btn-sm" id="manual-reconnect">Reconnect</button>`;
|
|
81
|
+
const main = document.querySelector(".main");
|
|
82
|
+
if (main) main.prepend(banner);
|
|
83
|
+
document
|
|
84
|
+
.getElementById("manual-reconnect")
|
|
85
|
+
?.addEventListener("click", () => {
|
|
86
|
+
if (ws) {
|
|
87
|
+
try {
|
|
88
|
+
ws.close();
|
|
89
|
+
} catch {}
|
|
90
|
+
}
|
|
91
|
+
reconnectDelay = 1000;
|
|
92
|
+
reconnectAttempts = 0;
|
|
93
|
+
connect();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (banner) banner.style.display = show ? "" : "none";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function connect(): void {
|
|
100
|
+
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
101
|
+
const wsUrl = `${protocol}//${location.host}/ws`;
|
|
102
|
+
ws = new WebSocket(wsUrl);
|
|
103
|
+
|
|
104
|
+
ws.onopen = () => {
|
|
105
|
+
reconnectDelay = 1000;
|
|
106
|
+
reconnectAttempts = 0;
|
|
107
|
+
store.update({ connected: true, connectedAt: Date.now() });
|
|
108
|
+
updateConnectionStatus("LIVE");
|
|
109
|
+
startConnectionTimer();
|
|
110
|
+
showOfflineBanner(false);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
ws.onclose = () => {
|
|
114
|
+
store.update({ connected: false, connectedAt: null });
|
|
115
|
+
stopConnectionTimer();
|
|
116
|
+
reconnectAttempts++;
|
|
117
|
+
updateConnectionStatus("OFFLINE");
|
|
118
|
+
showOfflineBanner(true);
|
|
119
|
+
setTimeout(connect, reconnectDelay);
|
|
120
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
ws.onerror = () => {};
|
|
124
|
+
|
|
125
|
+
ws.onmessage = (e: MessageEvent) => {
|
|
126
|
+
try {
|
|
127
|
+
const data: FleetState = JSON.parse(e.data);
|
|
128
|
+
lastDataTime = Date.now();
|
|
129
|
+
store.update({
|
|
130
|
+
fleetState: data,
|
|
131
|
+
firstRender: false,
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error("Failed to parse message:", err);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
startStaleDataTimer();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getWebSocket(): WebSocket | null {
|
|
142
|
+
return ws;
|
|
143
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { icon, iconNames } from "./icons";
|
|
3
|
+
import type { IconName } from "./icons";
|
|
4
|
+
|
|
5
|
+
describe("Icons", () => {
|
|
6
|
+
describe("iconNames", () => {
|
|
7
|
+
it("exports a non-empty list of icon names", () => {
|
|
8
|
+
expect(iconNames.length).toBeGreaterThan(0);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("includes essential navigation icons", () => {
|
|
12
|
+
const essential = [
|
|
13
|
+
"anchor",
|
|
14
|
+
"layout-dashboard",
|
|
15
|
+
"users",
|
|
16
|
+
"activity",
|
|
17
|
+
"server",
|
|
18
|
+
];
|
|
19
|
+
for (const name of essential) {
|
|
20
|
+
expect(iconNames).toContain(name);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("includes status icons", () => {
|
|
25
|
+
const status = [
|
|
26
|
+
"circle-check",
|
|
27
|
+
"circle-x",
|
|
28
|
+
"circle-alert",
|
|
29
|
+
"circle-pause",
|
|
30
|
+
];
|
|
31
|
+
for (const name of status) {
|
|
32
|
+
expect(iconNames).toContain(name);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("includes action icons", () => {
|
|
37
|
+
const actions = ["play", "pause", "send", "plus", "x", "copy"];
|
|
38
|
+
for (const name of actions) {
|
|
39
|
+
expect(iconNames).toContain(name);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("icon()", () => {
|
|
45
|
+
it("returns an SVG string for a valid icon", () => {
|
|
46
|
+
const svg = icon("anchor");
|
|
47
|
+
expect(svg).toContain("<svg");
|
|
48
|
+
expect(svg).toContain("</svg>");
|
|
49
|
+
expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns empty string for unknown icon", () => {
|
|
53
|
+
const svg = icon("nonexistent-icon-name" as IconName);
|
|
54
|
+
expect(svg).toBe("");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("uses default size of 16", () => {
|
|
58
|
+
const svg = icon("anchor");
|
|
59
|
+
expect(svg).toContain('width="16"');
|
|
60
|
+
expect(svg).toContain('height="16"');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("respects custom size", () => {
|
|
64
|
+
const svg = icon("anchor", 24);
|
|
65
|
+
expect(svg).toContain('width="24"');
|
|
66
|
+
expect(svg).toContain('height="24"');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("applies color attribute when provided", () => {
|
|
70
|
+
const svg = icon("anchor", 16, "#ff0000");
|
|
71
|
+
expect(svg).toContain('color="#ff0000"');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("omits color attribute when not provided", () => {
|
|
75
|
+
const svg = icon("anchor");
|
|
76
|
+
expect(svg).not.toContain("color=");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("always includes stroke attributes", () => {
|
|
80
|
+
const svg = icon("anchor");
|
|
81
|
+
expect(svg).toContain('stroke="currentColor"');
|
|
82
|
+
expect(svg).toContain('stroke-width="2"');
|
|
83
|
+
expect(svg).toContain('stroke-linecap="round"');
|
|
84
|
+
expect(svg).toContain('stroke-linejoin="round"');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("includes fill=none", () => {
|
|
88
|
+
const svg = icon("anchor");
|
|
89
|
+
expect(svg).toContain('fill="none"');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("uses 24x24 viewBox", () => {
|
|
93
|
+
const svg = icon("anchor");
|
|
94
|
+
expect(svg).toContain('viewBox="0 0 24 24"');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders every registered icon without error", () => {
|
|
98
|
+
for (const name of iconNames) {
|
|
99
|
+
const svg = icon(name);
|
|
100
|
+
expect(svg).toContain("<svg");
|
|
101
|
+
expect(svg).toContain("</svg>");
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Lucide Icons - inlined SVG strings
|
|
2
|
+
// Each icon is a function returning an SVG string with configurable size and color.
|
|
3
|
+
|
|
4
|
+
const iconPaths: Record<string, string> = {
|
|
5
|
+
// Navigation
|
|
6
|
+
anchor:
|
|
7
|
+
'<path d="M12 22V8"/><path d="M5 12H2a10 10 0 0 0 20 0h-3"/><circle cx="12" cy="5" r="3"/>',
|
|
8
|
+
"layout-dashboard":
|
|
9
|
+
'<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/>',
|
|
10
|
+
users:
|
|
11
|
+
'<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
12
|
+
"git-branch":
|
|
13
|
+
'<line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>',
|
|
14
|
+
clock:
|
|
15
|
+
'<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
16
|
+
activity:
|
|
17
|
+
'<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/>',
|
|
18
|
+
"bar-chart-3":
|
|
19
|
+
'<path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16h8"/><path d="M7 11h12"/><path d="M7 6h3"/>',
|
|
20
|
+
server:
|
|
21
|
+
'<rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/>',
|
|
22
|
+
lightbulb:
|
|
23
|
+
'<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
|
|
24
|
+
"users-round":
|
|
25
|
+
'<path d="M18 21a8 8 0 0 0-16 0"/><circle cx="10" cy="8" r="5"/><path d="M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3"/>',
|
|
26
|
+
|
|
27
|
+
// Status
|
|
28
|
+
"circle-check": '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
|
|
29
|
+
"circle-x":
|
|
30
|
+
'<circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/>',
|
|
31
|
+
"circle-alert":
|
|
32
|
+
'<circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/>',
|
|
33
|
+
"circle-pause":
|
|
34
|
+
'<circle cx="12" cy="12" r="10"/><line x1="10" x2="10" y1="15" y2="9"/><line x1="14" x2="14" y1="15" y2="9"/>',
|
|
35
|
+
"circle-play":
|
|
36
|
+
'<circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/>',
|
|
37
|
+
loader:
|
|
38
|
+
'<path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/>',
|
|
39
|
+
signal:
|
|
40
|
+
'<path d="M2 20h.01"/><path d="M7 20v-4"/><path d="M12 20v-8"/><path d="M17 20V8"/><path d="M22 4v16"/>',
|
|
41
|
+
|
|
42
|
+
// Actions
|
|
43
|
+
play: '<polygon points="6 3 20 12 6 21 6 3"/>',
|
|
44
|
+
pause:
|
|
45
|
+
'<rect width="4" height="16" x="6" y="4"/><rect width="4" height="16" x="14" y="4"/>',
|
|
46
|
+
square: '<rect width="18" height="18" x="3" y="3" rx="2"/>',
|
|
47
|
+
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
|
|
48
|
+
"message-square":
|
|
49
|
+
'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
|
50
|
+
"trash-2":
|
|
51
|
+
'<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>',
|
|
52
|
+
plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
|
|
53
|
+
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
54
|
+
copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
|
|
55
|
+
"external-link":
|
|
56
|
+
'<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
|
|
57
|
+
"chevron-down": '<path d="m6 9 6 6 6-6"/>',
|
|
58
|
+
"chevron-right": '<path d="m9 18 6-6-6-6"/>',
|
|
59
|
+
"chevron-left": '<path d="m15 18-6-6 6-6"/>',
|
|
60
|
+
|
|
61
|
+
// Data
|
|
62
|
+
cpu: '<rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/>',
|
|
63
|
+
"memory-stick":
|
|
64
|
+
'<path d="M6 19v-3"/><path d="M10 19v-3"/><path d="M14 19v-3"/><path d="M18 19v-3"/><path d="M8 11V9"/><path d="M16 11V9"/><path d="M12 11V9"/><path d="M2 15h20"/><path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V15H2v-3.063a2 2 0 0 0 0-3.837Z"/>',
|
|
65
|
+
"dollar-sign":
|
|
66
|
+
'<line x1="12" x2="12" y1="2" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>',
|
|
67
|
+
zap: '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>',
|
|
68
|
+
gauge: '<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>',
|
|
69
|
+
timer:
|
|
70
|
+
'<line x1="10" x2="14" y1="2" y2="2"/><line x1="12" x2="15" y1="14" y2="11"/><circle cx="12" cy="14" r="8"/>',
|
|
71
|
+
"trending-up":
|
|
72
|
+
'<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>',
|
|
73
|
+
"trending-down":
|
|
74
|
+
'<polyline points="22 17 13.5 8.5 8.5 13.5 2 7"/><polyline points="16 17 22 17 22 11"/>',
|
|
75
|
+
|
|
76
|
+
// Fleet
|
|
77
|
+
ship: '<path d="M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1 .6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/><path d="M19.38 20A11.6 11.6 0 0 0 21 14l-9-4-9 4c0 2.9.94 5.34 2.81 7.76"/><path d="M19 13V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6"/><path d="M12 10v4"/><path d="M12 2v3"/>',
|
|
78
|
+
radar:
|
|
79
|
+
'<path d="M19.07 4.93A10 10 0 0 0 6.99 3.34"/><path d="M4 6h.01"/><path d="M2.29 9.62A10 10 0 1 0 21.31 8.35"/><path d="M16.24 7.76A6 6 0 1 0 8.23 16.67"/><path d="M12 18h.01"/><path d="M17.99 11.66A6 6 0 0 1 15.77 16.67"/><circle cx="12" cy="12" r="2"/><path d="m13.41 10.59 5.66-5.66"/>',
|
|
80
|
+
map: '<path d="M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z"/><path d="M15 5.764v15"/><path d="M9 3.236v15"/>',
|
|
81
|
+
globe:
|
|
82
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
|
|
83
|
+
terminal:
|
|
84
|
+
'<polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/>',
|
|
85
|
+
"file-diff":
|
|
86
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M9 10h6"/><path d="M12 13V7"/><path d="M9 17h6"/>',
|
|
87
|
+
"git-pull-request":
|
|
88
|
+
'<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><line x1="6" x2="6" y1="9" y2="21"/>',
|
|
89
|
+
"shield-alert":
|
|
90
|
+
'<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="M12 8v4"/><path d="M12 16h.01"/>',
|
|
91
|
+
"octagon-alert":
|
|
92
|
+
'<path d="M12 16h.01"/><path d="M12 8v4"/><path d="M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"/>',
|
|
93
|
+
"refresh-cw":
|
|
94
|
+
'<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>',
|
|
95
|
+
eye: '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>',
|
|
96
|
+
brain:
|
|
97
|
+
'<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"/><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/><path d="M3.477 10.896a4 4 0 0 1 .585-.396"/><path d="M19.938 10.5a4 4 0 0 1 .585.396"/><path d="M6 18a4 4 0 0 1-1.967-.516"/><path d="M19.967 17.484A4 4 0 0 1 18 18"/>',
|
|
98
|
+
"shield-check":
|
|
99
|
+
'<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
|
|
100
|
+
webhook:
|
|
101
|
+
'<path d="M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2"/><path d="m6 17 3.13-5.78c.53-.97.43-2.22-.26-3.07a4 4 0 0 1 6.72-4.07"/><path d="m11.44 11.79.87-.47c.98-.53 2.22-.43 3.07.26a4 4 0 0 1 2.72 6.93"/>',
|
|
102
|
+
"clipboard-list":
|
|
103
|
+
'<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/>',
|
|
104
|
+
"check-circle":
|
|
105
|
+
'<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/>',
|
|
106
|
+
"user-check":
|
|
107
|
+
'<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><polyline points="16 11 18 13 22 9"/>',
|
|
108
|
+
"mail-plus":
|
|
109
|
+
'<path d="M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/><path d="M19 16v6"/><path d="M16 19h6"/>',
|
|
110
|
+
"scroll-text":
|
|
111
|
+
'<path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2"/>',
|
|
112
|
+
settings:
|
|
113
|
+
'<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
|
|
114
|
+
"log-out":
|
|
115
|
+
'<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type IconName = keyof typeof iconPaths;
|
|
119
|
+
|
|
120
|
+
export function icon(
|
|
121
|
+
name: IconName,
|
|
122
|
+
size: number = 16,
|
|
123
|
+
color?: string,
|
|
124
|
+
): string {
|
|
125
|
+
const paths = iconPaths[name];
|
|
126
|
+
if (!paths) return "";
|
|
127
|
+
const c = color ? ` color="${color}"` : "";
|
|
128
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"${c}>${paths}</svg>`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const iconNames = Object.keys(iconPaths) as IconName[];
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
colors,
|
|
4
|
+
fonts,
|
|
5
|
+
typeScale,
|
|
6
|
+
spacing,
|
|
7
|
+
radius,
|
|
8
|
+
shadows,
|
|
9
|
+
duration,
|
|
10
|
+
easing,
|
|
11
|
+
zIndex,
|
|
12
|
+
STAGES,
|
|
13
|
+
STAGE_SHORT,
|
|
14
|
+
STAGE_COLORS,
|
|
15
|
+
STAGE_HEX,
|
|
16
|
+
} from "./tokens";
|
|
17
|
+
import type { StageName } from "./tokens";
|
|
18
|
+
|
|
19
|
+
describe("Design Tokens", () => {
|
|
20
|
+
describe("colors", () => {
|
|
21
|
+
it("has all background colors", () => {
|
|
22
|
+
expect(colors.bg).toHaveProperty("abyss");
|
|
23
|
+
expect(colors.bg).toHaveProperty("deep");
|
|
24
|
+
expect(colors.bg).toHaveProperty("ocean");
|
|
25
|
+
expect(colors.bg).toHaveProperty("surface");
|
|
26
|
+
expect(colors.bg).toHaveProperty("foam");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("has accent colors as valid hex/rgba", () => {
|
|
30
|
+
expect(colors.accent.cyan).toMatch(/^#[0-9a-f]{6}$/i);
|
|
31
|
+
expect(colors.accent.purple).toMatch(/^#[0-9a-f]{6}$/i);
|
|
32
|
+
expect(colors.accent.blue).toMatch(/^#[0-9a-f]{6}$/i);
|
|
33
|
+
expect(colors.accent.cyanGlow).toMatch(/^rgba\(/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("has semantic colors", () => {
|
|
37
|
+
expect(colors.semantic.success).toMatch(/^#/);
|
|
38
|
+
expect(colors.semantic.warning).toMatch(/^#/);
|
|
39
|
+
expect(colors.semantic.error).toMatch(/^#/);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("has text colors", () => {
|
|
43
|
+
expect(colors.text.primary).toMatch(/^#/);
|
|
44
|
+
expect(colors.text.secondary).toMatch(/^#/);
|
|
45
|
+
expect(colors.text.muted).toMatch(/^#/);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("fonts", () => {
|
|
50
|
+
it("defines display, body, and mono font stacks", () => {
|
|
51
|
+
expect(fonts.display).toContain("serif");
|
|
52
|
+
expect(fonts.body).toContain("sans-serif");
|
|
53
|
+
expect(fonts.mono).toContain("monospace");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("typeScale", () => {
|
|
58
|
+
it("has all scale levels", () => {
|
|
59
|
+
const levels = [
|
|
60
|
+
"display",
|
|
61
|
+
"heading",
|
|
62
|
+
"title",
|
|
63
|
+
"body",
|
|
64
|
+
"caption",
|
|
65
|
+
"tiny",
|
|
66
|
+
"mono",
|
|
67
|
+
"monoSm",
|
|
68
|
+
];
|
|
69
|
+
for (const level of levels) {
|
|
70
|
+
const entry = typeScale[level as keyof typeof typeScale];
|
|
71
|
+
expect(entry).toHaveProperty("size");
|
|
72
|
+
expect(entry).toHaveProperty("weight");
|
|
73
|
+
expect(entry).toHaveProperty("family");
|
|
74
|
+
expect(entry.size).toBeGreaterThan(0);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("sizes are ordered display > heading > title > body > caption > tiny", () => {
|
|
79
|
+
expect(typeScale.display.size).toBeGreaterThan(typeScale.heading.size);
|
|
80
|
+
expect(typeScale.heading.size).toBeGreaterThan(typeScale.title.size);
|
|
81
|
+
expect(typeScale.title.size).toBeGreaterThan(typeScale.body.size);
|
|
82
|
+
expect(typeScale.body.size).toBeGreaterThan(typeScale.caption.size);
|
|
83
|
+
expect(typeScale.caption.size).toBeGreaterThan(typeScale.tiny.size);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("spacing", () => {
|
|
88
|
+
it("defines a spacing scale", () => {
|
|
89
|
+
expect(spacing[0]).toBe(0);
|
|
90
|
+
expect(spacing[1]).toBe(4);
|
|
91
|
+
expect(spacing[2]).toBe(8);
|
|
92
|
+
expect(spacing[4]).toBe(16);
|
|
93
|
+
expect(spacing[8]).toBe(32);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("has increasing values", () => {
|
|
97
|
+
const keys = Object.keys(spacing)
|
|
98
|
+
.map(Number)
|
|
99
|
+
.sort((a, b) => a - b);
|
|
100
|
+
for (let i = 1; i < keys.length; i++) {
|
|
101
|
+
expect(spacing[keys[i]]).toBeGreaterThanOrEqual(spacing[keys[i - 1]]);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("radius", () => {
|
|
107
|
+
it("has sm < md < lg < xl < full", () => {
|
|
108
|
+
expect(radius.sm).toBeLessThan(radius.md);
|
|
109
|
+
expect(radius.md).toBeLessThan(radius.lg);
|
|
110
|
+
expect(radius.lg).toBeLessThan(radius.xl);
|
|
111
|
+
expect(radius.xl).toBeLessThan(radius.full);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("shadows", () => {
|
|
116
|
+
it("has glow shadows for each semantic color", () => {
|
|
117
|
+
expect(shadows.glow.cyan).toContain("rgba");
|
|
118
|
+
expect(shadows.glow.purple).toContain("rgba");
|
|
119
|
+
expect(shadows.glow.success).toContain("rgba");
|
|
120
|
+
expect(shadows.glow.error).toContain("rgba");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("has an elevated shadow", () => {
|
|
124
|
+
expect(shadows.elevated).toContain("rgba");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("duration", () => {
|
|
129
|
+
it("has increasing animation durations", () => {
|
|
130
|
+
expect(duration.fast).toBeLessThan(duration.base);
|
|
131
|
+
expect(duration.base).toBeLessThan(duration.slow);
|
|
132
|
+
expect(duration.slow).toBeLessThan(duration.glacial);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("easing", () => {
|
|
137
|
+
it("defines easing curves", () => {
|
|
138
|
+
expect(easing.default).toBe("ease");
|
|
139
|
+
expect(easing.smooth).toContain("cubic-bezier");
|
|
140
|
+
expect(easing.spring).toContain("cubic-bezier");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("zIndex", () => {
|
|
145
|
+
it("has increasing z-index values", () => {
|
|
146
|
+
expect(zIndex.base).toBeLessThan(zIndex.dropdown);
|
|
147
|
+
expect(zIndex.dropdown).toBeLessThan(zIndex.sticky);
|
|
148
|
+
expect(zIndex.sticky).toBeLessThan(zIndex.overlay);
|
|
149
|
+
expect(zIndex.overlay).toBeLessThan(zIndex.modal);
|
|
150
|
+
expect(zIndex.modal).toBeLessThan(zIndex.toast);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("STAGES", () => {
|
|
155
|
+
it("defines the pipeline stage sequence", () => {
|
|
156
|
+
expect(STAGES).toHaveLength(11);
|
|
157
|
+
expect(STAGES[0]).toBe("intake");
|
|
158
|
+
expect(STAGES[STAGES.length - 1]).toBe("monitor");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("includes all expected stages", () => {
|
|
162
|
+
const expected = [
|
|
163
|
+
"intake",
|
|
164
|
+
"plan",
|
|
165
|
+
"design",
|
|
166
|
+
"build",
|
|
167
|
+
"test",
|
|
168
|
+
"review",
|
|
169
|
+
"compound_quality",
|
|
170
|
+
"pr",
|
|
171
|
+
"merge",
|
|
172
|
+
"deploy",
|
|
173
|
+
"monitor",
|
|
174
|
+
];
|
|
175
|
+
expect([...STAGES]).toEqual(expected);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("STAGE_SHORT", () => {
|
|
180
|
+
it("maps every stage to a short code", () => {
|
|
181
|
+
for (const stage of STAGES) {
|
|
182
|
+
expect(STAGE_SHORT[stage]).toBeDefined();
|
|
183
|
+
expect(STAGE_SHORT[stage].length).toBeLessThanOrEqual(3);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("STAGE_COLORS", () => {
|
|
189
|
+
it("has a color class for each stage", () => {
|
|
190
|
+
expect(STAGE_COLORS).toHaveLength(STAGES.length);
|
|
191
|
+
for (const cls of STAGE_COLORS) {
|
|
192
|
+
expect(cls).toMatch(/^c-/);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("STAGE_HEX", () => {
|
|
198
|
+
it("maps every stage to a hex color", () => {
|
|
199
|
+
for (const stage of STAGES) {
|
|
200
|
+
expect(STAGE_HEX[stage]).toMatch(/^#[0-9a-f]{6}$/i);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|