webui: convert sketch-container-status to use tailwind
Convert sketch-container-status component from shadowDOM CSS to Tailwind classes
while preserving the original visual styling and functionality.
- Inherit from SketchTailwindElement instead of LitElement to disable shadowDOM
- Replace static styles CSS with equivalent Tailwind utility classes
- Update pulse animation to use document-level CSS since Tailwind doesn't support custom keyframes inline
- Convert all layout, typography, color, and spacing properties to Tailwind equivalents
- Update DOM queries from shadowRoot to direct element queries due to disabled shadowDOM
- Preserve all interactive functionality including info panel expansion and commit copying
- Maintain visual consistency with original design including hover states and transitions
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s7f97a079a6dd14c1k
diff --git a/webui/src/tailwind.css b/webui/src/tailwind.css
index 05a41c8..0f64b00 100644
--- a/webui/src/tailwind.css
+++ b/webui/src/tailwind.css
@@ -1,4 +1,4 @@
-/*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */
+/*! tailwindcss v4.1.10 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
@@ -17,14 +17,9 @@
--color-orange-500: oklch(70.5% 0.213 47.604);
--color-orange-800: oklch(47% 0.157 37.304);
--color-green-600: oklch(62.7% 0.194 149.214);
- --color-blue-50: oklch(97% 0.014 254.604);
- --color-blue-100: oklch(93.2% 0.032 255.585);
- --color-blue-400: oklch(70.7% 0.165 254.624);
--color-blue-500: oklch(62.3% 0.214 259.815);
--color-blue-600: oklch(54.6% 0.245 262.881);
- --color-blue-700: oklch(48.8% 0.243 264.376);
--color-blue-800: oklch(42.4% 0.199 265.638);
- --color-gray-50: oklch(98.5% 0.002 247.839);
--color-gray-100: oklch(96.7% 0.003 264.542);
--color-gray-200: oklch(92.8% 0.006 264.531);
--color-gray-300: oklch(87.2% 0.01 258.338);
@@ -49,9 +44,9 @@
--font-weight-semibold: 600;
--font-weight-bold: 700;
--leading-relaxed: 1.625;
+ --radius-sm: 0.25rem;
--radius-lg: 0.5rem;
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
- --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
@@ -295,9 +290,6 @@
.top-full {
top: 100%;
}
- .right-0 {
- right: calc(var(--spacing) * 0);
- }
.right-4 {
right: calc(var(--spacing) * 4);
}
@@ -310,6 +302,24 @@
.col-span-full {
grid-column: 1 / -1;
}
+ .\!container {
+ width: 100% !important;
+ @media (width >= 40rem) {
+ max-width: 40rem !important;
+ }
+ @media (width >= 48rem) {
+ max-width: 48rem !important;
+ }
+ @media (width >= 64rem) {
+ max-width: 64rem !important;
+ }
+ @media (width >= 80rem) {
+ max-width: 80rem !important;
+ }
+ @media (width >= 96rem) {
+ max-width: 96rem !important;
+ }
+ }
.container {
width: 100%;
@media (width >= 40rem) {
@@ -358,8 +368,8 @@
.mr-12 {
margin-right: calc(var(--spacing) * 12);
}
- .mr-96 {
- margin-right: calc(var(--spacing) * 96);
+ .mr-\[400px\] {
+ margin-right: 400px;
}
.mb-0 {
margin-bottom: calc(var(--spacing) * 0);
@@ -376,6 +386,9 @@
.block {
display: block;
}
+ .contents {
+ display: contents;
+ }
.flex {
display: flex;
}
@@ -385,6 +398,9 @@
.hidden {
display: none;
}
+ .inline {
+ display: inline;
+ }
.inline-flex {
display: inline-flex;
}
@@ -409,6 +425,9 @@
.min-h-0 {
min-height: calc(var(--spacing) * 0);
}
+ .w-0\.5 {
+ width: calc(var(--spacing) * 0.5);
+ }
.w-4 {
width: calc(var(--spacing) * 4);
}
@@ -418,14 +437,14 @@
.w-6 {
width: calc(var(--spacing) * 6);
}
- .w-96 {
- width: calc(var(--spacing) * 96);
+ .w-\[400px\] {
+ width: 400px;
}
- .w-\[calc\(100\%-2\.5rem\)\] {
- width: calc(100% - 2.5rem);
+ .w-\[calc\(100\%-40px\)\] {
+ width: calc(100% - 40px);
}
- .w-\[calc\(100\%-24rem\)\] {
- width: calc(100% - 24rem);
+ .w-\[calc\(100\%-400px\)\] {
+ width: calc(100% - 400px);
}
.w-full {
width: 100%;
@@ -445,8 +464,8 @@
.min-w-24 {
min-width: calc(var(--spacing) * 24);
}
- .min-w-96 {
- min-width: calc(var(--spacing) * 96);
+ .min-w-max {
+ min-width: max-content;
}
.flex-1 {
flex: 1;
@@ -460,13 +479,16 @@
.flex-grow {
flex-grow: 1;
}
+ .origin-center {
+ transform-origin: center;
+ }
+ .rotate-45 {
+ rotate: 45deg;
+ }
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,)
var(--tw-skew-x,) var(--tw-skew-y,);
}
- .animate-pulse {
- animation: var(--animate-pulse);
- }
.cursor-default {
cursor: default;
}
@@ -545,6 +567,9 @@
.rounded-lg {
border-radius: var(--radius-lg);
}
+ .rounded-sm {
+ border-radius: var(--radius-sm);
+ }
.border {
border-style: var(--tw-border-style);
border-width: 1px;
@@ -553,22 +578,10 @@
border-top-style: var(--tw-border-style);
border-top-width: 1px;
}
- .border-r {
- border-right-style: var(--tw-border-style);
- border-right-width: 1px;
- }
.border-b {
border-bottom-style: var(--tw-border-style);
border-bottom-width: 1px;
}
- .border-b-2 {
- border-bottom-style: var(--tw-border-style);
- border-bottom-width: 2px;
- }
- .border-l {
- border-left-style: var(--tw-border-style);
- border-left-width: 1px;
- }
.border-l-4 {
border-left-style: var(--tw-border-style);
border-left-width: 4px;
@@ -577,8 +590,8 @@
--tw-border-style: none;
border-style: none;
}
- .border-blue-400 {
- border-color: var(--color-blue-400);
+ .border-blue-600 {
+ border-color: var(--color-blue-600);
}
.border-gray-200 {
border-color: var(--color-gray-200);
@@ -589,30 +602,18 @@
.border-orange-500 {
border-color: var(--color-orange-500);
}
- .border-transparent {
- border-color: transparent;
- }
- .border-b-blue-500 {
- border-bottom-color: var(--color-blue-500);
- }
- .bg-blue-50 {
- background-color: var(--color-blue-50);
- }
- .bg-blue-100 {
- background-color: var(--color-blue-100);
- }
.bg-blue-500 {
background-color: var(--color-blue-500);
}
- .bg-blue-700 {
- background-color: var(--color-blue-700);
- }
- .bg-gray-50 {
- background-color: var(--color-gray-50);
+ .bg-blue-600 {
+ background-color: var(--color-blue-600);
}
.bg-gray-100 {
background-color: var(--color-gray-100);
}
+ .bg-gray-200 {
+ background-color: var(--color-gray-200);
+ }
.bg-gray-600 {
background-color: var(--color-gray-600);
}
@@ -622,15 +623,18 @@
.bg-red-600 {
background-color: var(--color-red-600);
}
- .bg-transparent {
- background-color: transparent;
- }
.bg-white {
background-color: var(--color-white);
}
.p-0 {
padding: calc(var(--spacing) * 0);
}
+ .p-3 {
+ padding: calc(var(--spacing) * 3);
+ }
+ .p-4 {
+ padding: calc(var(--spacing) * 4);
+ }
.px-1\.5 {
padding-inline: calc(var(--spacing) * 1.5);
}
@@ -640,12 +644,6 @@
.px-2\.5 {
padding-inline: calc(var(--spacing) * 2.5);
}
- .px-3 {
- padding-inline: calc(var(--spacing) * 3);
- }
- .px-4 {
- padding-inline: calc(var(--spacing) * 4);
- }
.px-5 {
padding-inline: calc(var(--spacing) * 5);
}
@@ -658,12 +656,6 @@
.py-1\.5 {
padding-block: calc(var(--spacing) * 1.5);
}
- .py-2 {
- padding-block: calc(var(--spacing) * 2);
- }
- .py-2\.5 {
- padding-block: calc(var(--spacing) * 2.5);
- }
.pt-0 {
padding-top: calc(var(--spacing) * 0);
}
@@ -691,10 +683,6 @@
.font-sans {
font-family: var(--font-sans);
}
- .text-base {
- font-size: var(--text-base);
- line-height: var(--tw-leading, var(--text-base--line-height));
- }
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
@@ -736,33 +724,24 @@
.whitespace-nowrap {
white-space: nowrap;
}
- .text-blue-500 {
- color: var(--color-blue-500);
- }
.text-blue-600 {
color: var(--color-blue-600);
}
- .text-gray-400 {
- color: var(--color-gray-400);
- }
.text-gray-500 {
color: var(--color-gray-500);
}
.text-gray-600 {
color: var(--color-gray-600);
}
- .text-gray-600\/85 {
- color: color-mix(in srgb, oklch(44.6% 0.03 256.802) 85%, transparent);
- @supports (color: color-mix(in lab, red, red)) {
- color: color-mix(in oklab, var(--color-gray-600) 85%, transparent);
- }
- }
.text-gray-800 {
color: var(--color-gray-800);
}
.text-green-600 {
color: var(--color-green-600);
}
+ .text-inherit {
+ color: inherit;
+ }
.text-orange-800 {
color: var(--color-orange-800);
}
@@ -800,10 +779,10 @@
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
- .shadow-sm {
+ .shadow-md {
--tw-shadow:
- 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)),
- 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)),
+ 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow:
var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -886,6 +865,25 @@
);
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
+ .transition-colors {
+ transition-property:
+ color, background-color, border-color, outline-color,
+ text-decoration-color, fill, stroke, --tw-gradient-from,
+ --tw-gradient-via, --tw-gradient-to;
+ transition-timing-function: var(
+ --tw-ease,
+ var(--default-transition-timing-function)
+ );
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .transition-opacity {
+ transition-property: opacity;
+ transition-timing-function: var(
+ --tw-ease,
+ var(--default-transition-timing-function)
+ );
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
.duration-200 {
--tw-duration: 200ms;
transition-duration: 200ms;
@@ -894,13 +892,6 @@
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
- .group-hover\:opacity-100 {
- &:is(:where(.group):hover *) {
- @media (hover: hover) {
- opacity: 100%;
- }
- }
- }
.hover\:bg-blue-800 {
&:hover {
@media (hover: hover) {
@@ -943,6 +934,20 @@
}
}
}
+ .hover\:opacity-80 {
+ &:hover {
+ @media (hover: hover) {
+ opacity: 80%;
+ }
+ }
+ }
+ .hover\:opacity-100 {
+ &:hover {
+ @media (hover: hover) {
+ opacity: 100%;
+ }
+ }
+ }
.disabled\:cursor-not-allowed {
&:disabled {
cursor: not-allowed;
@@ -963,108 +968,101 @@
opacity: 70%;
}
}
- .disabled\:hover\:bg-red-300 {
- &:disabled {
- &:hover {
- @media (hover: hover) {
- background-color: var(--color-red-300);
- }
- }
+ .sm\:h-4 {
+ @media (width >= 40rem) {
+ height: calc(var(--spacing) * 4);
}
}
- .max-xl\:hidden {
- @media (width < 80rem) {
- display: none;
+ .sm\:w-4 {
+ @media (width >= 40rem) {
+ width: calc(var(--spacing) * 4);
}
}
- .max-xl\:px-1\.5 {
- @media (width < 80rem) {
- padding-inline: calc(var(--spacing) * 1.5);
+ .sm\:max-w-\[60\%\] {
+ @media (width >= 40rem) {
+ max-width: 60%;
}
}
- .max-xl\:px-2\.5 {
- @media (width < 80rem) {
- padding-inline: calc(var(--spacing) * 2.5);
+ .sm\:text-sm {
+ @media (width >= 40rem) {
+ font-size: var(--text-sm);
+ line-height: var(--tw-leading, var(--text-sm--line-height));
}
}
- .max-xl\:py-1\.5 {
- @media (width < 80rem) {
- padding-block: calc(var(--spacing) * 1.5);
- }
- }
- .max-md\:mr-0 {
- @media (width < 48rem) {
+ .md\:mr-0 {
+ @media (width >= 48rem) {
margin-right: calc(var(--spacing) * 0);
}
}
- .max-md\:hidden {
- @media (width < 48rem) {
+ .md\:hidden {
+ @media (width >= 48rem) {
display: none;
}
}
- .max-md\:w-full {
- @media (width < 48rem) {
+ .md\:h-\[18px\] {
+ @media (width >= 48rem) {
+ height: 18px;
+ }
+ }
+ .md\:w-\[18px\] {
+ @media (width >= 48rem) {
+ width: 18px;
+ }
+ }
+ .md\:w-full {
+ @media (width >= 48rem) {
width: 100%;
}
}
- .md\:mr-72 {
+ .md\:max-w-1\/2 {
@media (width >= 48rem) {
- margin-right: calc(var(--spacing) * 72);
+ max-width: calc(1 / 2 * 100%);
}
}
- .md\:w-72 {
+ .md\:text-base {
@media (width >= 48rem) {
- width: calc(var(--spacing) * 72);
+ font-size: var(--text-base);
+ line-height: var(--tw-leading, var(--text-base--line-height));
}
}
- .md\:w-\[calc\(100\%-18rem\)\] {
- @media (width >= 48rem) {
- width: calc(100% - 18rem);
- }
- }
- .lg\:mr-80 {
+ .lg\:mr-\[300px\] {
@media (width >= 64rem) {
- margin-right: calc(var(--spacing) * 80);
+ margin-right: 300px;
}
}
- .lg\:w-80 {
+ .lg\:w-\[300px\] {
@media (width >= 64rem) {
- width: calc(var(--spacing) * 80);
+ width: 300px;
}
}
- .lg\:w-\[calc\(100\%-20rem\)\] {
+ .lg\:w-\[calc\(100\%-300px\)\] {
@media (width >= 64rem) {
- width: calc(100% - 20rem);
+ width: calc(100% - 300px);
}
}
- .xl\:mr-96 {
+ .xl\:mr-\[350px\] {
@media (width >= 80rem) {
- margin-right: calc(var(--spacing) * 96);
+ margin-right: 350px;
}
}
- .xl\:inline {
+ .xl\:hidden {
@media (width >= 80rem) {
- display: inline;
+ display: none;
}
}
- .xl\:w-96 {
+ .xl\:w-\[350px\] {
@media (width >= 80rem) {
- width: calc(var(--spacing) * 96);
+ width: 350px;
}
}
- .xl\:w-\[calc\(100\%-24rem\)\] {
+ .xl\:w-\[calc\(100\%-350px\)\] {
@media (width >= 80rem) {
- width: calc(100% - 24rem);
+ width: calc(100% - 350px);
}
}
- .xl\:px-2\.5 {
+ .xl\:px-1\.5 {
@media (width >= 80rem) {
- padding-inline: calc(var(--spacing) * 2.5);
- }
- }
- .xl\:py-1 {
- @media (width >= 80rem) {
- padding-block: calc(var(--spacing) * 1);
+ padding-inline: calc(var(--spacing) * 1.5);
}
}
}
@@ -1268,11 +1266,6 @@
syntax: "*";
inherits: false;
}
-@keyframes pulse {
- 50% {
- opacity: 0.5;
- }
-}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or
((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
diff --git a/webui/src/web-components/demo/sketch-container-status.demo.html b/webui/src/web-components/demo/sketch-container-status.demo.html
index 0945d70..3b5725b 100644
--- a/webui/src/web-components/demo/sketch-container-status.demo.html
+++ b/webui/src/web-components/demo/sketch-container-status.demo.html
@@ -2,6 +2,7 @@
<head>
<title>sketch-container-status demo</title>
<link rel="stylesheet" href="demo.css" />
+ <link rel="stylesheet" href="/src/tailwind.css" />
<script type="module" src="../sketch-container-status.ts"></script>
<script>
diff --git a/webui/src/web-components/sketch-container-status.ts b/webui/src/web-components/sketch-container-status.ts
index 088244f..8071783 100644
--- a/webui/src/web-components/sketch-container-status.ts
+++ b/webui/src/web-components/sketch-container-status.ts
@@ -1,10 +1,11 @@
import { State, AgentMessage } from "../types";
-import { LitElement, css, html } from "lit";
+import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { formatNumber } from "../utils";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
@customElement("sketch-container-status")
-export class SketchContainerStatus extends LitElement {
+export class SketchContainerStatus extends SketchTailwindElement {
// Header bar: Container status details
@property()
@@ -19,361 +20,28 @@
@state()
lastCommitCopied: boolean = false;
- // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
- // Note that these styles only apply to the scope of this web component's
- // shadow DOM node, so they won't leak out or collide with CSS declared in
- // other components or the containing web page (...unless you want it to do that).
- static styles = css`
- /* Last commit display styling */
- .last-commit {
- display: flex;
- flex-direction: column;
- padding: 3px 8px;
- cursor: pointer;
- position: relative;
- margin: 4px 0;
- transition: color 0.2s ease;
+ // CSS animations that can't be easily replaced with Tailwind
+ connectedCallback() {
+ super.connectedCallback();
+ // Add custom CSS animations to the document head if not already present
+ if (!document.querySelector("#container-status-animations")) {
+ const style = document.createElement("style");
+ style.id = "container-status-animations";
+ style.textContent = `
+ @keyframes pulse-custom {
+ 0% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(1.05); opacity: 0.8; }
+ 100% { transform: scale(1); opacity: 1; }
+ }
+ .pulse-custom {
+ animation: pulse-custom 1.5s ease-in-out;
+ background-color: rgba(38, 132, 255, 0.1);
+ border-radius: 3px;
+ }
+ `;
+ document.head.appendChild(style);
}
-
- .last-commit:hover {
- color: #0366d6;
- }
-
- /* Pulse animation for new commits */
- @keyframes pulse {
- 0% {
- transform: scale(1);
- opacity: 1;
- }
- 50% {
- transform: scale(1.05);
- opacity: 0.8;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
- }
-
- .pulse {
- animation: pulse 1.5s ease-in-out;
- background-color: rgba(38, 132, 255, 0.1);
- border-radius: 3px;
- }
-
- .last-commit-title {
- color: #666;
- font-family: system-ui, sans-serif;
- font-size: 11px;
- font-weight: 500;
- line-height: 1.2;
- }
-
- .last-commit-hash {
- font-family: monospace;
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- /* Styles for the last commit in main grid */
- .last-commit-column {
- justify-content: flex-start;
- }
-
- .info-label {
- color: #666;
- font-family: system-ui, sans-serif;
- font-size: 11px;
- font-weight: 500;
- }
-
- .last-commit-main {
- cursor: pointer;
- position: relative;
- padding-top: 0;
- }
-
- .last-commit-main:hover {
- color: #0366d6;
- }
-
- .main-grid-commit {
- font-family: monospace;
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .commit-hash-indicator {
- color: #666;
- }
-
- .commit-branch-indicator {
- color: #28a745;
- }
-
- .no-commit-indicator {
- color: #999;
- font-style: italic;
- font-size: 12px;
- }
-
- .copied-indicator {
- position: absolute;
- top: 0;
- left: 0;
- background: rgba(0, 0, 0, 0.7);
- color: white;
- padding: 2px 6px;
- border-radius: 3px;
- font-size: 11px;
- pointer-events: none;
- z-index: 10;
- }
-
- .copy-icon {
- margin-left: 4px;
- opacity: 0.7;
- }
-
- .copy-icon svg {
- vertical-align: middle;
- }
-
- .last-commit-main:hover .copy-icon {
- opacity: 1;
- }
-
- .info-container {
- display: flex;
- align-items: center;
- position: relative;
- }
-
- .info-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- background: #f9f9f9;
- border-radius: 4px;
- padding: 4px 10px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- flex: 1;
- }
-
- .info-expanded {
- position: absolute;
- top: 100%;
- right: 0;
- z-index: 10;
- min-width: 400px;
- background: white;
- border-radius: 8px;
- padding: 10px 15px;
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
- margin-top: 5px;
- display: none;
- }
-
- .info-expanded.active {
- display: block;
- }
-
- .info-item {
- display: flex;
- align-items: center;
- white-space: nowrap;
- margin-right: 10px;
- font-size: 13px;
- }
-
- .info-label {
- font-size: 11px;
- color: #555;
- margin-right: 3px;
- font-weight: 500;
- }
-
- .info-value {
- font-size: 11px;
- font-weight: 600;
- word-break: break-all;
- }
-
- [title] {
- cursor: default;
- }
-
- .info-item a {
- --tw-text-opacity: 1;
- color: rgb(37 99 235 / var(--tw-text-opacity, 1));
- text-decoration: inherit;
- }
-
- .info-toggle {
- margin-left: 8px;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f0f0f0;
- border: 1px solid #ddd;
- cursor: pointer;
- font-weight: bold;
- font-style: italic;
- color: #555;
- transition: all 0.2s ease;
- }
-
- .info-toggle:hover {
- background: #e0e0e0;
- }
-
- .info-toggle.active {
- background: #4a90e2;
- color: white;
- border-color: #3a80d2;
- }
-
- .main-info-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- width: 100%;
- }
-
- .info-column {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
-
- .detailed-info-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 8px;
- margin-top: 10px;
- }
-
- .ssh-section {
- margin-top: 10px;
- padding-top: 10px;
- border-top: 1px solid #eee;
- }
-
- .ssh-command {
- display: flex;
- align-items: center;
- margin-bottom: 8px;
- gap: 10px;
- }
-
- .ssh-command-text {
- font-family: monospace;
- font-size: 12px;
- background: #f5f5f5;
- padding: 4px 8px;
- border-radius: 4px;
- border: 1px solid #e0e0e0;
- flex-grow: 1;
- }
-
- .copy-button {
- background: #f0f0f0;
- border: 1px solid #ddd;
- border-radius: 4px;
- padding: 3px 6px;
- font-size: 11px;
- cursor: pointer;
- transition: all 0.2s;
- }
-
- .copy-button:hover {
- background: #e0e0e0;
- }
-
- .ssh-warning {
- background: #fff3e0;
- border-left: 3px solid #ff9800;
- padding: 8px 12px;
- margin-top: 8px;
- font-size: 12px;
- color: #e65100;
- }
-
- .vscode-link {
- color: white;
- text-decoration: none;
- background-color: #0066b8;
- padding: 4px 8px;
- border-radius: 4px;
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- transition: all 0.2s ease;
- }
-
- .vscode-link:hover {
- background-color: #005091;
- }
-
- .vscode-icon {
- width: 16px;
- height: 16px;
- }
-
- .github-link {
- color: #2962ff;
- text-decoration: none;
- }
-
- .github-link:hover {
- text-decoration: underline;
- }
-
- .commit-info-container {
- display: flex;
- align-items: center;
- gap: 6px;
- }
-
- .commit-info-container .copy-icon {
- opacity: 0.7;
- display: flex;
- align-items: center;
- }
-
- .commit-info-container .copy-icon svg {
- vertical-align: middle;
- }
-
- .commit-info-container:hover .copy-icon {
- opacity: 1;
- }
-
- .octocat-link {
- color: #586069;
- text-decoration: none;
- display: flex;
- align-items: center;
- transition: color 0.2s ease;
- }
-
- .octocat-link:hover {
- color: #0366d6;
- }
-
- .octocat-icon {
- width: 16px;
- height: 16px;
- }
- `;
+ }
constructor() {
super();
@@ -427,15 +95,14 @@
if (hasChanged) {
// Find the last commit element
setTimeout(() => {
- const lastCommitEl =
- this.shadowRoot?.querySelector(".last-commit-main");
+ const lastCommitEl = this.querySelector(".last-commit-main");
if (lastCommitEl) {
// Add the pulse class
- lastCommitEl.classList.add("pulse");
+ lastCommitEl.classList.add("pulse-custom");
// Remove the pulse class after animation completes
setTimeout(() => {
- lastCommitEl.classList.remove("pulse");
+ lastCommitEl.classList.remove("pulse-custom");
}, 1500);
}
}, 0);
@@ -523,18 +190,6 @@
return `Outside: ${outsideWorkingDir}, Inside: ${insideWorkingDir}`;
}
- // See https://lit.dev/docs/components/lifecycle/
- connectedCallback() {
- super.connectedCallback();
- // register event listeners
- }
-
- // See https://lit.dev/docs/components/lifecycle/
- disconnectedCallback() {
- super.disconnectedCallback();
- // unregister event listeners
- }
-
copyToClipboard(text: string) {
navigator.clipboard
.writeText(text)
@@ -623,9 +278,11 @@
if (!this.state?.ssh_available) {
return html`
- <div class="ssh-section">
+ <div class="mt-2.5 pt-2.5 border-t border-gray-300">
<h3>Connect to Container</h3>
- <div class="ssh-warning">
+ <div
+ class="bg-orange-50 border-l-4 border-orange-500 p-3 mt-2 text-xs text-orange-800"
+ >
SSH connections are not available:
${this.state?.ssh_error || "SSH configuration is missing"}
</div>
@@ -634,30 +291,42 @@
}
return html`
- <div class="ssh-section">
+ <div class="mt-2.5 pt-2.5 border-t border-gray-300">
<h3>Connect to Container</h3>
- <div class="ssh-command">
- <div class="ssh-command-text">${sshCommand}</div>
+ <div class="flex items-center mb-2 gap-2.5">
+ <div
+ class="font-mono text-xs bg-gray-100 px-2 py-1 rounded border border-gray-300 flex-grow"
+ >
+ ${sshCommand}
+ </div>
<button
- class="copy-button"
+ class="bg-gray-100 border border-gray-300 rounded px-1.5 py-0.5 text-xs cursor-pointer transition-colors hover:bg-gray-200"
@click=${() => this.copyToClipboard(sshCommand)}
>
Copy
</button>
</div>
- <div class="ssh-command">
- <div class="ssh-command-text">${vscodeCommand}</div>
+ <div class="flex items-center mb-2 gap-2.5">
+ <div
+ class="font-mono text-xs bg-gray-100 px-2 py-1 rounded border border-gray-300 flex-grow"
+ >
+ ${vscodeCommand}
+ </div>
<button
- class="copy-button"
+ class="bg-gray-100 border border-gray-300 rounded px-1.5 py-0.5 text-xs cursor-pointer transition-colors hover:bg-gray-200"
@click=${() => this.copyToClipboard(vscodeCommand)}
>
Copy
</button>
</div>
- <div class="ssh-command">
- <a href="${vscodeURL}" class="vscode-link" title="${vscodeURL}">
+ <div class="flex items-center mb-2 gap-2.5">
+ <a
+ href="${vscodeURL}"
+ class="text-white no-underline bg-blue-500 px-2 py-1 rounded flex items-center gap-1.5 text-xs transition-colors hover:bg-blue-800"
+ title="${vscodeURL}"
+ >
<svg
- class="vscode-icon"
+ class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
@@ -679,151 +348,166 @@
render() {
return html`
- <div class="info-container">
+ <div class="flex items-center relative">
<!-- Main visible info in two columns - github/hostname/dir and last commit -->
- <div class="main-info-grid">
- <!-- First column: GitHub repo (or hostname) and working dir -->
- <div class="info-column">
- <div class="info-item">
- ${(() => {
- const github = this.formatGitHubRepo(this.state?.git_origin);
- if (github) {
- return html`
- <a
- href="${github.url}"
- target="_blank"
- rel="noopener noreferrer"
- class="github-link"
- title="${this.state?.git_origin}"
- >
- ${github.formatted}
- </a>
- `;
- } else {
- return html`
- <span
- id="hostname"
- class="info-value"
- title="${this.getHostnameTooltip()}"
- >
- ${this.formatHostname()}
- </span>
- `;
- }
- })()}
+ <div class="flex flex-wrap gap-2 px-2.5 py-1 flex-1">
+ <div class="grid grid-cols-2 gap-2.5 w-full">
+ <!-- First column: GitHub repo (or hostname) and working dir -->
+ <div class="flex flex-col gap-0.5">
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ ${(() => {
+ const github = this.formatGitHubRepo(this.state?.git_origin);
+ if (github) {
+ return html`
+ <a
+ href="${github.url}"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="github-link text-blue-600 no-underline hover:underline"
+ title="${this.state?.git_origin}"
+ >
+ ${github.formatted}
+ </a>
+ `;
+ } else {
+ return html`
+ <span
+ id="hostname"
+ class="text-xs font-semibold break-all cursor-default"
+ title="${this.getHostnameTooltip()}"
+ >
+ ${this.formatHostname()}
+ </span>
+ `;
+ }
+ })()}
+ </div>
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span
+ id="workingDir"
+ class="text-xs font-semibold break-all cursor-default"
+ title="${this.getWorkingDirTooltip()}"
+ >
+ ${this.formatWorkingDir()}
+ </span>
+ </div>
</div>
- <div class="info-item">
- <span
- id="workingDir"
- class="info-value"
- title="${this.getWorkingDirTooltip()}"
- >
- ${this.formatWorkingDir()}
- </span>
- </div>
- </div>
- <!-- Second column: Last Commit -->
- <div class="info-column last-commit-column">
- <div class="info-item">
- <span class="info-label">Last Commit</span>
- </div>
- <div
- class="info-item last-commit-main"
- @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
- title="Click to copy"
- >
- ${this.lastCommit
- ? this.lastCommit.pushedBranch
- ? (() => {
- const githubLink = this.getGitHubBranchLink(
- this.lastCommit.pushedBranch,
- );
- return html`
- <div class="commit-info-container">
- <span
- class="commit-branch-indicator main-grid-commit"
- title="Click to copy: ${this.lastCommit
- .pushedBranch}"
- @click=${(e) => this.copyCommitInfo(e)}
- >${this.lastCommit.pushedBranch}</span
- >
- <span class="copy-icon">
- ${this.lastCommitCopied
- ? html`<svg
- xmlns="http://www.w3.org/2000/svg"
- width="16"
- height="16"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
+ <!-- Second column: Last Commit -->
+ <div class="flex flex-col gap-0.5 justify-start">
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 font-medium"
+ >Last Commit</span
+ >
+ </div>
+ <div
+ class="flex items-center whitespace-nowrap mr-2.5 text-xs cursor-pointer relative pt-0 last-commit-main hover:text-blue-600"
+ @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
+ title="Click to copy"
+ >
+ ${this.lastCommit
+ ? this.lastCommit.pushedBranch
+ ? (() => {
+ const githubLink = this.getGitHubBranchLink(
+ this.lastCommit.pushedBranch,
+ );
+ return html`
+ <div class="flex items-center gap-1.5">
+ <span
+ class="text-green-600 font-mono text-xs whitespace-nowrap overflow-hidden text-ellipsis"
+ title="Click to copy: ${this.lastCommit
+ .pushedBranch}"
+ @click=${(e) => this.copyCommitInfo(e)}
+ >${this.lastCommit.pushedBranch}</span
+ >
+ <span
+ class="ml-1 opacity-70 flex items-center hover:opacity-100"
+ >
+ ${this.lastCommitCopied
+ ? html`<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="16"
+ height="16"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ class="align-middle"
+ >
+ <path d="M20 6L9 17l-5-5"></path>
+ </svg>`
+ : html`<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="16"
+ height="16"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ class="align-middle"
+ >
+ <rect
+ x="9"
+ y="9"
+ width="13"
+ height="13"
+ rx="2"
+ ry="2"
+ ></rect>
+ <path
+ d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
+ ></path>
+ </svg>`}
+ </span>
+ ${githubLink
+ ? html`<a
+ href="${githubLink}"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="text-gray-600 no-underline flex items-center transition-colors hover:text-blue-600"
+ title="Open ${this.lastCommit
+ .pushedBranch} on GitHub"
+ @click=${(e) => e.stopPropagation()}
>
- <path d="M20 6L9 17l-5-5"></path>
- </svg>`
- : html`<svg
- xmlns="http://www.w3.org/2000/svg"
- width="16"
- height="16"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- >
- <rect
- x="9"
- y="9"
- width="13"
- height="13"
- rx="2"
- ry="2"
- ></rect>
- <path
- d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
- ></path>
- </svg>`}
- </span>
- ${githubLink
- ? html`<a
- href="${githubLink}"
- target="_blank"
- rel="noopener noreferrer"
- class="octocat-link"
- title="Open ${this.lastCommit
- .pushedBranch} on GitHub"
- @click=${(e) => e.stopPropagation()}
- >
- <svg
- class="octocat-icon"
- viewBox="0 0 16 16"
- width="16"
- height="16"
- >
- <path
- fill="currentColor"
- d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
- />
- </svg>
- </a>`
- : ""}
- </div>
- `;
- })()
- : html`<span class="commit-hash-indicator main-grid-commit"
- >${this.lastCommit.hash.substring(0, 8)}</span
- >`
- : html`<span class="no-commit-indicator">N/A</span>`}
+ <svg
+ class="w-4 h-4"
+ viewBox="0 0 16 16"
+ width="16"
+ height="16"
+ >
+ <path
+ fill="currentColor"
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
+ />
+ </svg>
+ </a>`
+ : ""}
+ </div>
+ `;
+ })()
+ : html`<span
+ class="text-gray-600 font-mono text-xs whitespace-nowrap overflow-hidden text-ellipsis"
+ >${this.lastCommit.hash.substring(0, 8)}</span
+ >`
+ : html`<span class="text-gray-500 italic text-xs">N/A</span>`}
+ </div>
</div>
</div>
</div>
<!-- Info toggle button -->
<button
- class="info-toggle ${this.showDetails ? "active" : ""}"
+ class="info-toggle ml-2 w-6 h-6 rounded-full flex items-center justify-center ${this
+ .showDetails
+ ? "bg-blue-500 text-white border-blue-600"
+ : "bg-gray-100 text-gray-600 border-gray-300"} border cursor-pointer font-bold italic transition-all hover:${this
+ .showDetails
+ ? "bg-blue-600"
+ : "bg-gray-200"}"
@click=${this._toggleInfoDetails}
title="Show/hide details"
>
@@ -831,33 +515,45 @@
</button>
<!-- Expanded info panel -->
- <div class="info-expanded ${this.showDetails ? "active" : ""}">
+ <div
+ class="${this.showDetails
+ ? "block"
+ : "hidden"} absolute min-w-max top-full z-10 bg-white rounded-lg p-4 shadow-lg mt-1.5"
+ >
<!-- Last Commit section moved to main grid -->
- <div class="detailed-info-grid">
- <div class="info-item">
- <span class="info-label">Commit:</span>
- <span id="initialCommit" class="info-value"
+ <div
+ class="grid grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-2 mt-2.5"
+ >
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Commit:</span
+ >
+ <span id="initialCommit" class="text-xs font-semibold break-all"
>${this.state?.initial_commit?.substring(0, 8)}</span
>
</div>
- <div class="info-item">
- <span class="info-label">Msgs:</span>
- <span id="messageCount" class="info-value"
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 mr-1 font-medium">Msgs:</span>
+ <span id="messageCount" class="text-xs font-semibold break-all"
>${this.state?.message_count}</span
>
</div>
- <div class="info-item">
- <span class="info-label">Session ID:</span>
- <span id="sessionId" class="info-value"
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Session ID:</span
+ >
+ <span id="sessionId" class="text-xs font-semibold break-all"
>${this.state?.session_id || "N/A"}</span
>
</div>
- <div class="info-item">
- <span class="info-label">Hostname:</span>
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Hostname:</span
+ >
<span
id="hostnameDetail"
- class="info-value"
+ class="text-xs font-semibold break-all cursor-default"
title="${this.getHostnameTooltip()}"
>
${this.formatHostname()}
@@ -865,17 +561,25 @@
</div>
${this.state?.agent_state
? html`
- <div class="info-item">
- <span class="info-label">Agent State:</span>
- <span id="agentState" class="info-value"
+ <div
+ class="flex items-center whitespace-nowrap mr-2.5 text-xs"
+ >
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Agent State:</span
+ >
+ <span
+ id="agentState"
+ class="text-xs font-semibold break-all"
>${this.state?.agent_state}</span
>
</div>
`
: ""}
- <div class="info-item">
- <span class="info-label">Input tokens:</span>
- <span id="inputTokens" class="info-value"
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Input tokens:</span
+ >
+ <span id="inputTokens" class="text-xs font-semibold break-all"
>${formatNumber(
(this.state?.total_usage?.input_tokens || 0) +
(this.state?.total_usage?.cache_read_input_tokens || 0) +
@@ -883,17 +587,23 @@
)}</span
>
</div>
- <div class="info-item">
- <span class="info-label">Output tokens:</span>
- <span id="outputTokens" class="info-value"
+ <div class="flex items-center whitespace-nowrap mr-2.5 text-xs">
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Output tokens:</span
+ >
+ <span id="outputTokens" class="text-xs font-semibold break-all"
>${formatNumber(this.state?.total_usage?.output_tokens)}</span
>
</div>
${(this.state?.total_usage?.total_cost_usd || 0) > 0
? html`
- <div class="info-item">
- <span class="info-label">Total cost:</span>
- <span id="totalCost" class="info-value cost"
+ <div
+ class="flex items-center whitespace-nowrap mr-2.5 text-xs"
+ >
+ <span class="text-xs text-gray-600 mr-1 font-medium"
+ >Total cost:</span
+ >
+ <span id="totalCost" class="text-xs font-semibold break-all"
>$${(this.state?.total_usage?.total_cost_usd).toFixed(
2,
)}</span
@@ -902,10 +612,13 @@
`
: ""}
<div
- class="info-item"
- style="grid-column: 1 / -1; margin-top: 5px; border-top: 1px solid #eee; padding-top: 5px;"
+ class="flex items-center whitespace-nowrap mr-2.5 text-xs col-span-full mt-1.5 border-t border-gray-300 pt-1.5"
>
- <a href="logs">Logs</a> (<a href="download">Download</a>)
+ <a href="logs" class="text-blue-600">Logs</a> (<a
+ href="download"
+ class="text-blue-600"
+ >Download</a
+ >)
</div>
</div>