feat: add worker console timeline

This commit is contained in:
Keisuke Hirata 2026-07-15 02:46:45 +09:00
parent e298856c85
commit f9f1f85b07
No known key found for this signature in database
10 changed files with 3141 additions and 2455 deletions

View File

@ -6,14 +6,17 @@
"npm:@sveltejs/adapter-static@3.0.9": "3.0.9_@sveltejs+kit@2.49.4__@sveltejs+vite-plugin-svelte@6.2.1___svelte@5.45.6___vite@7.2.7__svelte@5.45.6__typescript@5.9.3__vite@7.2.7_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.45.6__vite@7.2.7_svelte@5.45.6_typescript@5.9.3_vite@7.2.7",
"npm:@sveltejs/kit@2.49.4": "2.49.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.45.6__vite@7.2.7_svelte@5.45.6_typescript@5.9.3_vite@7.2.7",
"npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.45.6_vite@7.2.7",
"npm:@tailwindcss/vite@^4.1.17": "4.2.2_vite@7.2.7",
"npm:clsx@2.1.1": "2.1.1",
"npm:cookie@0.6.0": "0.6.0",
"npm:decodal-codemirror@0.1.2": "0.1.2",
"npm:devalue@5.6.4": "5.6.4",
"npm:set-cookie-parser@2.7.2": "2.7.2",
"npm:shiki@3.13.0": "3.13.0",
"npm:sv@*": "0.10.8",
"npm:svelte-check@4.3.4": "4.3.4_svelte@5.45.6_typescript@5.9.3",
"npm:svelte@5.45.6": "5.45.6",
"npm:tailwindcss@^4.1.17": "4.2.2",
"npm:typescript@5.9.3": "5.9.3",
"npm:vite@7.2.7": "7.2.7"
},
@ -455,6 +458,103 @@
"vitefu"
]
},
"@tailwindcss/node@4.2.2": {
"integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
"dependencies": [
"@jridgewell/remapping",
"enhanced-resolve",
"jiti",
"lightningcss",
"magic-string",
"source-map-js",
"tailwindcss"
]
},
"@tailwindcss/oxide-android-arm64@4.2.2": {
"integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
"os": ["android"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-darwin-arm64@4.2.2": {
"integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-darwin-x64@4.2.2": {
"integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-freebsd-x64@4.2.2": {
"integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2": {
"integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
"os": ["linux"],
"cpu": ["arm"]
},
"@tailwindcss/oxide-linux-arm64-gnu@4.2.2": {
"integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-linux-arm64-musl@4.2.2": {
"integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-linux-x64-gnu@4.2.2": {
"integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-linux-x64-musl@4.2.2": {
"integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
"os": ["linux"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-wasm32-wasi@4.2.2": {
"integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
"cpu": ["wasm32"]
},
"@tailwindcss/oxide-win32-arm64-msvc@4.2.2": {
"integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-win32-x64-msvc@4.2.2": {
"integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
"os": ["win32"],
"cpu": ["x64"]
},
"@tailwindcss/oxide@4.2.2": {
"integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
"optionalDependencies": [
"@tailwindcss/oxide-android-arm64",
"@tailwindcss/oxide-darwin-arm64",
"@tailwindcss/oxide-darwin-x64",
"@tailwindcss/oxide-freebsd-x64",
"@tailwindcss/oxide-linux-arm-gnueabihf",
"@tailwindcss/oxide-linux-arm64-gnu",
"@tailwindcss/oxide-linux-arm64-musl",
"@tailwindcss/oxide-linux-x64-gnu",
"@tailwindcss/oxide-linux-x64-musl",
"@tailwindcss/oxide-wasm32-wasi",
"@tailwindcss/oxide-win32-arm64-msvc",
"@tailwindcss/oxide-win32-x64-msvc"
]
},
"@tailwindcss/vite@4.2.2_vite@7.2.7": {
"integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
"dependencies": [
"@tailwindcss/node",
"@tailwindcss/oxide",
"tailwindcss",
"vite"
]
},
"@types/cookie@0.6.0": {
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
@ -539,6 +639,9 @@
"dequal@2.0.3": {
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
},
"detect-libc@2.1.2": {
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="
},
"devalue@5.6.4": {
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="
},
@ -548,6 +651,13 @@
"dequal"
]
},
"enhanced-resolve@5.20.1": {
"integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
"dependencies": [
"graceful-fs",
"tapable"
]
},
"esbuild@0.25.12": {
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"optionalDependencies": [
@ -605,6 +715,9 @@
"os": ["darwin"],
"scripts": true
},
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"hast-util-to-html@9.0.5": {
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"dependencies": [
@ -636,9 +749,87 @@
"@types/estree"
]
},
"jiti@2.6.1": {
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"bin": true
},
"kleur@4.1.5": {
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
},
"lightningcss-android-arm64@1.32.0": {
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"os": ["android"],
"cpu": ["arm64"]
},
"lightningcss-darwin-arm64@1.32.0": {
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"lightningcss-darwin-x64@1.32.0": {
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"os": ["darwin"],
"cpu": ["x64"]
},
"lightningcss-freebsd-x64@1.32.0": {
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"lightningcss-linux-arm-gnueabihf@1.32.0": {
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"os": ["linux"],
"cpu": ["arm"]
},
"lightningcss-linux-arm64-gnu@1.32.0": {
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"os": ["linux"],
"cpu": ["arm64"]
},
"lightningcss-linux-arm64-musl@1.32.0": {
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"os": ["linux"],
"cpu": ["arm64"]
},
"lightningcss-linux-x64-gnu@1.32.0": {
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"os": ["linux"],
"cpu": ["x64"]
},
"lightningcss-linux-x64-musl@1.32.0": {
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"os": ["linux"],
"cpu": ["x64"]
},
"lightningcss-win32-arm64-msvc@1.32.0": {
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"os": ["win32"],
"cpu": ["arm64"]
},
"lightningcss-win32-x64-msvc@1.32.0": {
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"os": ["win32"],
"cpu": ["x64"]
},
"lightningcss@1.32.0": {
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"dependencies": [
"detect-libc"
],
"optionalDependencies": [
"lightningcss-android-arm64",
"lightningcss-darwin-arm64",
"lightningcss-darwin-x64",
"lightningcss-freebsd-x64",
"lightningcss-linux-arm-gnueabihf",
"lightningcss-linux-arm64-gnu",
"lightningcss-linux-arm64-musl",
"lightningcss-linux-x64-gnu",
"lightningcss-linux-x64-musl",
"lightningcss-win32-arm64-msvc",
"lightningcss-win32-x64-msvc"
]
},
"locate-character@3.0.0": {
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
},
@ -829,6 +1020,10 @@
"style-mod@4.1.3": {
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
},
"sv@0.10.8": {
"integrity": "sha512-3wUiQfoBTQ7V9SxYA8/XBRQiUDV1FmiEYmb/GLfwlSJBPJbOyT9cvZPeane8j9GLrA/VBaKBWAP6NgULyuTRwA==",
"bin": true
},
"svelte-check@4.3.4_svelte@5.45.6_typescript@5.9.3": {
"integrity": "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==",
"dependencies": [
@ -862,6 +1057,12 @@
"zimmerframe"
]
},
"tailwindcss@4.2.2": {
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="
},
"tapable@2.3.2": {
"integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="
},
"tinyglobby@0.2.15": {
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dependencies": [
@ -977,6 +1178,12 @@
"npm:svelte@5.45.6",
"npm:typescript@5.9.3",
"npm:vite@7.2.7"
]
],
"packageJson": {
"dependencies": [
"npm:@tailwindcss/vite@^4.1.17",
"npm:tailwindcss@^4.1.17"
]
}
}
}

View File

@ -2,5 +2,9 @@
"name": "@yoi/workspace-web",
"version": "0.0.0",
"private": true,
"type": "module"
"type": "module",
"devDependencies": {
"@tailwindcss/vite": "^4.1.17",
"tailwindcss": "^4.1.17"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,11 @@
}
</script>
<li class={`console-line ${lineClass(item)} ${toolClass(item)}`} class:error-line={item.error}>
<li
class={`console-line ${lineClass(item)} ${toolClass(item)}`}
class:error-line={item.error}
data-console-line-id={item.id}
>
{#if shouldRenderHeading(item)}
<div class="message-heading">
<span>{item.title}</span>
@ -79,3 +83,200 @@
</details>
{/if}
</li>
<style>
.console-line {
min-width: 0;
padding: 0.2rem 0;
background: transparent;
}
.console-line.error-line {
color: var(--danger);
}
.console-line.user {
color: var(--tui-green);
}
.console-line.assistant {
color: var(--text-strong);
}
.console-line.thinking .message-heading {
color: var(--tui-magenta);
font-style: italic;
}
.console-line.thinking .console-plain-text {
color: var(--tui-dark-gray);
font-style: italic;
}
.console-plain-text {
margin: 0.45rem 0;
color: inherit;
line-height: 1.55;
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.console-plain-text:first-child {
margin-top: 0;
}
.console-plain-text:last-child {
margin-bottom: 0;
}
.console-line.tool-bash .console-plain-text {
display: block;
max-width: 100%;
min-width: 0;
font-family: var(--font-mono);
overflow-x: auto;
white-space: pre;
}
.console-line.tool .console-plain-text {
color: var(--tui-gray);
}
.tool-summary {
display: flex;
align-items: baseline;
gap: 0;
color: var(--text-muted);
font-size: 0.88rem;
font-weight: 750;
}
.tool-summary small {
margin-left: var(--space-2);
color: var(--text-muted);
font-size: 0.74rem;
font-weight: 700;
text-transform: uppercase;
}
.tool-label {
flex: 0 0 auto;
color: var(--tui-cyan);
white-space: nowrap;
}
.tool-separator {
flex: 0 0 auto;
white-space: nowrap;
}
.tool-suffix {
flex: 1 1 auto;
min-width: 0;
overflow-wrap: anywhere;
}
.tool-separator,
.tool-suffix {
color: var(--tui-dark-gray);
}
.tool-state-error .tool-suffix {
color: var(--tui-red);
}
.tool-state-running .tool-suffix,
.tool-state-streaming_args .tool-suffix,
.tool-state-pending .tool-suffix {
color: var(--tui-yellow);
}
.tool-state-done .tool-suffix {
color: var(--tui-dark-gray);
}
.message-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
color: var(--text-muted);
font-size: 0.78rem;
font-weight: 750;
}
.message-heading.streaming-heading {
justify-content: flex-start;
}
.message-heading small {
margin: 0;
color: var(--text-muted);
font-size: 0.74rem;
font-weight: 700;
text-transform: uppercase;
}
.console-diff {
background: color-mix(in oklch, var(--bg-raised) 85%, black);
border: 1px solid var(--line);
border-radius: 0.65rem;
color: var(--text);
font-size: 0.78rem;
line-height: 1.45;
margin: 0.6rem 0 0;
overflow-x: auto;
padding: 0.45rem 0;
}
.diff-line {
display: grid;
grid-template-columns: 3.2rem 3.2rem 1.4rem minmax(0, 1fr);
min-width: max-content;
}
.diff-line.add {
background: color-mix(in oklch, var(--tui-green) 18%, transparent);
color: color-mix(in oklch, var(--tui-green) 75%, white);
}
.diff-line.remove {
background: color-mix(in oklch, var(--tui-red) 18%, transparent);
color: color-mix(in oklch, var(--tui-red) 72%, white);
}
.diff-line.context {
color: var(--text-muted);
}
.diff-gutter,
.diff-marker {
color: var(--tui-gray);
padding: 0 0.5rem;
text-align: right;
user-select: none;
}
.diff-content {
padding-right: 0.75rem;
white-space: pre;
}
:global(.console-line pre) {
max-width: 100%;
margin: 0;
overflow-x: auto;
white-space: pre-wrap;
color: var(--code);
}
.message-detail {
color: var(--text-muted);
font-size: 0.84rem;
}
.message-detail summary {
cursor: pointer;
font-weight: 800;
}
</style>

View File

@ -0,0 +1,170 @@
<script lang="ts">
type TimelineKind = 'turn' | 'assistant';
export type TimelineMark = {
id: string;
lineId: string;
label: string;
detail: string;
timeLabel: string;
position: number;
sourcePosition: number;
kind: TimelineKind;
};
type Props = {
marks: TimelineMark[];
thumbStyle: string;
axisStyle: string;
onRailClick: (event: MouseEvent) => void;
onMarkClick: (mark: TimelineMark) => void;
};
let { marks, thumbStyle, axisStyle, onRailClick, onMarkClick }: Props = $props();
</script>
<aside class="console-timeline" aria-label="Console timeline">
<div class="timeline-axis" style={axisStyle}>
<button
type="button"
class="timeline-rail"
aria-label="Jump within console scrollback"
onclick={onRailClick}
>
<span class="timeline-thumb" style={thumbStyle}></span>
</button>
<div class="timeline-marks" aria-label="Timeline marks">
{#each marks as mark (mark.id)}
<button
type="button"
class={`timeline-mark ${mark.kind}`}
style={`top: ${mark.position}px`}
aria-label={mark.detail ? `${mark.label}: ${mark.detail}` : mark.label}
onclick={() => onMarkClick(mark)}
>
<span class="timeline-dot"></span>
<span class="timeline-card">
<span>{mark.label}</span>
</span>
</button>
{/each}
</div>
</div>
</aside>
<style>
.console-timeline {
--timeline-axis-padding: 3rem;
position: relative;
grid-column: 2;
grid-row: 2;
width: 100%;
min-width: 0;
min-height: 0;
height: 100%;
opacity: 1;
}
.timeline-axis {
position: absolute;
right: 0;
left: 0;
}
.timeline-rail,
.timeline-marks {
position: absolute;
top: 0;
bottom: 0;
left: 0;
}
.timeline-rail {
width: 0.35rem;
border: 0;
border-radius: 999px;
background: color-mix(in srgb, var(--line) 70%, transparent);
cursor: pointer;
padding: 0;
}
.timeline-thumb {
position: absolute;
left: 0;
width: 100%;
min-height: 1.25rem;
border-radius: inherit;
background: var(--tui-cyan);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--tui-cyan) 30%, transparent);
}
.timeline-marks {
width: 12rem;
pointer-events: none;
}
.timeline-mark {
position: absolute;
left: 0;
display: flex;
width: 11.5rem;
align-items: center;
gap: var(--space-2);
border: 0;
background: transparent;
color: var(--text-muted);
cursor: pointer;
padding: 0;
pointer-events: auto;
transform: translateY(-50%);
}
.timeline-dot {
width: 0.6rem;
height: 0.6rem;
flex: 0 0 auto;
border: 1px solid var(--bg);
border-radius: 999px;
background: var(--tui-gray);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--line) 80%, transparent);
}
.timeline-mark.turn .timeline-dot {
background: var(--tui-blue);
}
.timeline-mark.assistant .timeline-dot {
background: var(--tui-green);
}
.timeline-card {
display: grid;
width: 10rem;
min-width: 0;
grid-template-columns: minmax(0, 1fr);
justify-items: start;
text-align: left;
border: 1px solid var(--line);
border-radius: 10px;
background: color-mix(in srgb, var(--bg-panel) 92%, transparent);
box-shadow: var(--shadow-soft);
opacity: 1;
padding: 0.25rem 0.45rem;
transform: none;
}
.timeline-card span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.timeline-mark.turn .timeline-card {
border-color: color-mix(in srgb, var(--tui-blue) 55%, var(--line));
}
.timeline-mark.assistant .timeline-card {
border-color: color-mix(in srgb, var(--tui-green) 55%, var(--line));
}
</style>

View File

@ -37,3 +37,63 @@
<p>{text}</p>
{/if}
</div>
<style>
.rich-markdown {
color: inherit;
line-height: 1.55;
}
:global(.rich-markdown > :first-child) {
margin-top: 0;
}
:global(.rich-markdown > :last-child) {
margin-bottom: 0;
}
:global(.rich-markdown p),
:global(.rich-markdown ul),
:global(.rich-markdown blockquote),
:global(.rich-markdown pre) {
margin: 0.45rem 0;
}
:global(.rich-markdown ul) {
padding-left: 1.2rem;
}
:global(.rich-markdown h1),
:global(.rich-markdown h2),
:global(.rich-markdown h3),
:global(.rich-markdown h4) {
margin: 0.7rem 0 0.35rem;
color: var(--text-strong);
font-size: 1rem;
}
:global(.rich-markdown blockquote) {
border-left: 2px solid var(--tui-dark-gray);
color: var(--text-muted);
padding-left: 0.8rem;
}
:global(.rich-markdown :not(pre) > code) {
background: color-mix(in oklch, var(--bg-raised) 80%, var(--tui-blue));
border: 1px solid var(--line);
border-radius: 0.35rem;
color: var(--tui-blue);
padding: 0.05rem 0.28rem;
}
:global(.rich-markdown a) {
color: var(--tui-cyan);
}
:global(.rich-markdown .shiki) {
border: 1px solid var(--line);
border-radius: 0.65rem;
overflow: auto;
padding: 0.75rem;
}
</style>

View File

@ -94,7 +94,11 @@ export function workerConsolePath(
);
}
export type ConsoleEventInput = { eventId: string; event: ProtocolEvent };
export type ConsoleEventInput = {
eventId: string;
event: ProtocolEvent;
observedAtMs?: number;
};
export function emptyConsoleProjection(): ConsoleProjection {
return {

View File

@ -94,6 +94,33 @@ Deno.test("Worker Console renders markdown only for message rows", async () => {
);
});
Deno.test("Worker Console exposes a foldable timeline beside the scroll body", async () => {
const consolePage = await Deno.readTextFile(
new URL(
"./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
import.meta.url,
),
);
const consoleLine = await Deno.readTextFile(
new URL("./ConsoleLineItem.svelte", import.meta.url),
);
const consoleTimeline = await Deno.readTextFile(
new URL("./ConsoleTimeline.svelte", import.meta.url),
);
assert(
consoleTimeline.includes('class="console-timeline"') &&
consolePage.includes("timelineMarks") &&
consolePage.includes("jumpToTimelineMark") &&
consoleLine.includes("data-console-line-id={item.id}") &&
consolePage.includes("class:timeline-open={timelineOpen}") &&
consolePage.includes("class=\"timeline-fold\"") &&
consolePage.includes("{#if timelineOpen}") &&
consoleTimeline.includes(".timeline-thumb") &&
consoleTimeline.includes(".timeline-card"),
"Worker Console should expose a foldable timeline with scroll and line jump markers",
);
});
Deno.test("Worker Console composer fits to content without manual resize", async () => {
const consolePage = await Deno.readTextFile(
new URL(
@ -101,13 +128,11 @@ Deno.test("Worker Console composer fits to content without manual resize", async
import.meta.url,
),
);
const css = await Deno.readTextFile(new URL("../../app.css", import.meta.url));
assert(
consolePage.includes("use:fitTextarea={{ value: draft, maxRows: 10 }}") &&
css.includes(".console-composer textarea") &&
css.includes("resize: none") &&
css.includes("overflow-y: hidden"),
consolePage.includes(".console-composer textarea") &&
consolePage.includes("resize: none") &&
consolePage.includes("overflow-y: hidden"),
"Console composer should autosize to content, cap at ten rows, and disable manual resize",
);
});
@ -221,9 +246,9 @@ Deno.test("Worker Console page is routed by runtime_id and worker_id through bac
);
assert(
consolePage.includes("workspaceApiPath(workspaceId, path)") &&
consolePage.includes(
"workerApiPath(`/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}`)",
),
consolePage.includes("workerApiPath(") &&
consolePage.includes("`/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(") &&
consolePage.includes("target.workerId"),
"Worker detail should use the scoped backend Worker detail API",
);
assert(
@ -242,9 +267,8 @@ Deno.test("Worker Console page is routed by runtime_id and worker_id through bac
"reload token advancement should not synchronously read and write the rune state",
);
assert(
consolePage.includes(
"advanceReloadToken();\n void loadConsoleData(target);",
) &&
consolePage.includes("advanceReloadToken();") &&
consolePage.includes("void loadConsoleData(target);") &&
!consolePage.includes("void refreshConsole();\n });\n\n $effect"),
"target-change effect should load data without depending on manual refresh state reads",
);

View File

@ -1,10 +1,14 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
plugins: [tailwindcss(), sveltekit()],
server: {
allowedHosts: ['develop.hareworks.net'],
watch: { ignored: ['**/.tmp*', '**/.tmp*/**'] },
proxy: {
'/api': {
target: 'http://127.0.0.1:8787',