Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 1 | /* The settings window, in the launcher's colours (launcher.css): rows of |
| 2 | settings under small headings, each with its switch on the right. */ | |
| 3 | :root { | |
| 4 | --bg: #0f0f11; | |
| 5 | --fg: #ededf0; | |
| 6 | --muted: #9a9aa6; | |
| 7 | --line: #26262c; | |
| 8 | --surface: #17171b; | |
| 9 | --accent: #b6a8ff; | |
| 10 | --accent-ink: #15112e; | |
| 11 | --error: #ff8a8a; | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 12 | /* States, as dots and labels: running, on its way, needs a look, failing. */ |
| 13 | --ok: #4ade80; | |
| 14 | --working: #60a5fa; | |
| 15 | --warning: #fbbf24; | |
| 16 | --raised: #1e1e24; | |
| 17 | --line-strong: #34343c; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 18 | color-scheme: dark; |
| 19 | } | |
| 20 | * { | |
| 21 | box-sizing: border-box; | |
| 22 | } | |
| 23 | html, | |
| 24 | body { | |
| 25 | margin: 0; | |
| 26 | } | |
| 27 | body { | |
| 28 | background: var(--bg); | |
| 29 | color: var(--fg); | |
| 30 | font: 14px/1.45 system-ui, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; | |
| 31 | -webkit-font-smoothing: antialiased; | |
| 32 | user-select: none; | |
| 33 | cursor: default; | |
| 34 | } | |
| 35 | /* g1t's own title bar, when the window has no system frame: fixed at the top, 36px tall. */ | |
| 36 | .titlebar { | |
| 37 | position: fixed; | |
| 38 | inset: 0 0 auto 0; | |
| 39 | z-index: 10; | |
| 40 | height: 36px; | |
| 41 | display: flex; | |
| 42 | align-items: center; | |
| 43 | justify-content: space-between; | |
| 44 | padding-left: 14px; | |
| 45 | background: #050507; | |
| 46 | color: var(--muted); | |
| 47 | font-size: 12px; | |
| 48 | font-weight: 500; | |
| 49 | } | |
| 50 | .titlebar[hidden] { | |
| 51 | display: none; | |
| 52 | } | |
| 53 | .mac .titlebar { | |
| 54 | padding-left: 78px; | |
| 55 | } | |
| 56 | .mac .titlebar .caption { | |
| 57 | display: none; | |
| 58 | } | |
| 59 | .titlebar .caption { | |
| 60 | all: unset; | |
| 61 | display: inline-flex; | |
| 62 | align-items: center; | |
| 63 | justify-content: center; | |
| 64 | width: 46px; | |
| 65 | height: 36px; | |
| 66 | } | |
| 67 | .titlebar .caption:hover { | |
| 68 | background: #c42b1c; | |
| 69 | color: #fff; | |
| 70 | } | |
| 71 | /* Under it, the page scrolls on its own, so its scrollbar starts below the bar. */ | |
| 72 | body.framed .page { | |
| 73 | position: fixed; | |
| 74 | inset: 36px 0 0 0; | |
| 75 | overflow-y: auto; | |
| 76 | } | |
| 77 | main { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 78 | max-width: 38rem; |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 79 | margin: 0 auto; |
| 80 | padding: 1.5rem 1.25rem 1.25rem; | |
| 81 | } | |
| 82 | h1 { | |
| 83 | font-size: 1.25rem; | |
| 84 | font-weight: 600; | |
| 85 | margin: 0 0 0.25rem; | |
| 86 | } | |
| 87 | h2 { | |
| 88 | font-size: 0.75rem; | |
| 89 | font-weight: 600; | |
| 90 | letter-spacing: 0.06em; | |
| 91 | text-transform: uppercase; | |
| 92 | color: var(--muted); | |
| 93 | margin: 0 0 0.25rem; | |
| 94 | } | |
| 95 | p { | |
| 96 | margin: 0; | |
| 97 | } | |
| 98 | .lede { | |
| 99 | margin-bottom: 1.5rem; | |
| 100 | } | |
| 101 | .muted { | |
| 102 | color: var(--muted); | |
| 103 | } | |
| 104 | .small { | |
| 105 | font-size: 0.8125rem; | |
| 106 | } | |
| 107 | section { | |
| 108 | margin-bottom: 1.5rem; | |
| 109 | } | |
| 110 | .setting { | |
| 111 | display: flex; | |
| 112 | align-items: center; | |
| 113 | justify-content: space-between; | |
| 114 | gap: 1rem; | |
| 115 | padding: 0.75rem 0; | |
| 116 | border-bottom: 1px solid var(--line); | |
| 117 | } | |
| The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is. | 118 | /* A row hidden until it applies, such as Restart to change the title bar. */ |
| 119 | .setting[hidden] { | |
| 120 | display: none; | |
| 121 | } | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 122 | section .setting:last-child { |
| 123 | border-bottom: 0; | |
| 124 | } | |
| 125 | .setting > span { | |
| 126 | display: flex; | |
| 127 | flex-direction: column; | |
| 128 | gap: 0.125rem; | |
| 129 | min-width: 0; | |
| 130 | } | |
| 131 | .setting strong { | |
| 132 | font-weight: 500; | |
| 133 | overflow-wrap: anywhere; | |
| 134 | } | |
| 135 | .setting .muted { | |
| 136 | font-size: 0.8125rem; | |
| 137 | } | |
| 138 | .setting.off { | |
| 139 | opacity: 0.5; | |
| 140 | } | |
| 141 | input[type="checkbox"][role="switch"] { | |
| 142 | appearance: none; | |
| 143 | flex: none; | |
| 144 | width: 36px; | |
| 145 | height: 20px; | |
| 146 | margin: 0; | |
| 147 | border-radius: 999px; | |
| 148 | background: var(--line); | |
| 149 | position: relative; | |
| 150 | transition: background 0.15s; | |
| 151 | } | |
| 152 | input[type="checkbox"][role="switch"]::after { | |
| 153 | content: ""; | |
| 154 | position: absolute; | |
| 155 | top: 2px; | |
| 156 | left: 2px; | |
| 157 | width: 16px; | |
| 158 | height: 16px; | |
| 159 | border-radius: 50%; | |
| 160 | background: var(--fg); | |
| 161 | transition: transform 0.15s; | |
| 162 | } | |
| 163 | input[type="checkbox"][role="switch"]:checked { | |
| 164 | background: var(--accent); | |
| 165 | } | |
| 166 | input[type="checkbox"][role="switch"]:checked::after { | |
| 167 | transform: translateX(16px); | |
| 168 | background: var(--accent-ink); | |
| 169 | } | |
| 170 | input:disabled { | |
| 171 | opacity: 0.5; | |
| 172 | } | |
| 173 | @media (prefers-reduced-motion: reduce) { | |
| 174 | input[type="checkbox"][role="switch"], | |
| 175 | input[type="checkbox"][role="switch"]::after { | |
| 176 | transition: none; | |
| 177 | } | |
| 178 | } | |
| 179 | button, | |
| 180 | select { | |
| 181 | flex: none; | |
| 182 | font: inherit; | |
| 183 | font-size: 0.8125rem; | |
| 184 | font-weight: 500; | |
| 185 | padding: 0.375rem 0.75rem; | |
| 186 | border-radius: 8px; | |
| 187 | border: 1px solid var(--line); | |
| 188 | background: var(--surface); | |
| 189 | color: var(--fg); | |
| 190 | cursor: default; | |
| 191 | } | |
| 192 | button:hover, | |
| 193 | select:hover { | |
| 194 | border-color: #3a3a44; | |
| 195 | } | |
| 196 | .setting > .buttons { | |
| 197 | flex-direction: row; | |
| 198 | flex: none; | |
| 199 | gap: 0.5rem; | |
| 200 | } | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 201 | /* A row with buttons beside its words: in a narrow window, the buttons go |
| 202 | under them rather than squeezing them into a column. */ | |
| 203 | .setting:has(> .buttons) { | |
| 204 | flex-wrap: wrap; | |
| 205 | row-gap: 0.5rem; | |
| 206 | } | |
| 207 | .setting:has(> .buttons) > span:first-child { | |
| 208 | flex: 1 1 14rem; | |
| 209 | } | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 210 | button.primary { |
| 211 | background: var(--accent); | |
| 212 | border-color: var(--accent); | |
| 213 | color: var(--accent-ink); | |
| 214 | } | |
| 215 | button.primary:hover { | |
| 216 | filter: brightness(1.06); | |
| 217 | } | |
| 218 | button[hidden] { | |
| 219 | display: none; | |
| 220 | } | |
| 221 | button:disabled { | |
| 222 | opacity: 0.6; | |
| 223 | } | |
| 224 | button:focus-visible, | |
| 225 | select:focus-visible, | |
| 226 | input:focus-visible, | |
| 227 | a:focus-visible { | |
| 228 | outline: 2px solid var(--accent); | |
| 229 | outline-offset: 2px; | |
| 230 | } | |
| 231 | a { | |
| 232 | color: var(--muted); | |
| 233 | text-decoration: underline; | |
| 234 | text-underline-offset: 3px; | |
| 235 | } | |
| 236 | a:hover { | |
| 237 | color: var(--fg); | |
| 238 | } | |
| 239 | .error { | |
| 240 | color: var(--error); | |
| 241 | font-size: 0.875rem; | |
| 242 | margin-bottom: 1rem; | |
| 243 | } | |
| 244 | .foot { | |
| 245 | text-align: center; | |
| 246 | } | |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 247 | code { |
| 248 | font: 0.8125rem ui-monospace, SFMono-Regular, Consolas, monospace; | |
| 249 | } | |
| 250 | /* The runner's state: a dot in its tone, what it is doing, and when it | |
| 251 | isn't running, why and the fix, with its buttons. */ | |
| 252 | .diagnosis { | |
| 253 | display: flex; | |
| 254 | align-items: flex-start; | |
| 255 | gap: 0.75rem; | |
| 256 | margin: 0.25rem 0 0.5rem; | |
| 257 | padding: 0.75rem; | |
| 258 | border: 1px solid var(--line); | |
| 259 | border-radius: 10px; | |
| 260 | background: var(--surface); | |
| 261 | } | |
| 262 | .diagnosis-text { | |
| 263 | display: flex; | |
| 264 | flex-direction: column; | |
| 265 | gap: 0.25rem; | |
| 266 | min-width: 0; | |
| 267 | flex: 1; | |
| 268 | font-size: 0.8125rem; | |
| 269 | } | |
| 270 | .diagnosis-text strong { | |
| 271 | font-size: 0.875rem; | |
| 272 | font-weight: 500; | |
| 273 | } | |
| 274 | .diagnosis-text .buttons { | |
| 275 | display: flex; | |
| 276 | flex-wrap: wrap; | |
| 277 | gap: 0.5rem; | |
| 278 | margin-top: 0.25rem; | |
| 279 | } | |
| 280 | .diagnosis-text .buttons:empty { | |
| 281 | display: none; | |
| 282 | } | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 283 | .diagnosis[hidden] { |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 284 | display: none; |
| 285 | } | |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 286 | .dot { |
| 287 | flex: none; | |
| 288 | width: 8px; | |
| 289 | height: 8px; | |
| 290 | margin-top: 0.4rem; | |
| 291 | border-radius: 50%; | |
| 292 | background: var(--muted); | |
| 293 | } | |
| 294 | .dot.ok { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 295 | background: var(--ok); |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 296 | } |
| 297 | .dot.working { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 298 | background: var(--working); |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 299 | } |
| 300 | .dot.warning { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 301 | background: var(--warning); |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 302 | } |
| 303 | .dot.problem { | |
| 304 | background: var(--error); | |
| 305 | } | |
| 306 | .warning-note { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 307 | color: var(--warning); |
| 308 | } | |
| 309 | .visually-hidden { | |
| 310 | position: absolute; | |
| 311 | width: 1px; | |
| 312 | height: 1px; | |
| 313 | margin: -1px; | |
| 314 | padding: 0; | |
| 315 | overflow: hidden; | |
| 316 | clip: rect(0 0 0 0); | |
| 317 | white-space: nowrap; | |
| 318 | border: 0; | |
| 319 | } | |
| 320 | ||
| 321 | /* Sandboxed, each kind of job it runs, side by side: Linux jobs, Windows | |
| 322 | jobs. Two to a row where they fit, one under the other where they don't. */ | |
| 323 | .kinds { | |
| 324 | display: grid; | |
| 325 | grid-template-columns: repeat(auto-fit, minmax(15.5rem, 1fr)); | |
| 326 | gap: 0.75rem; | |
| 327 | margin: 0.5rem 0 0.75rem; | |
| 328 | } | |
| 329 | .kinds[hidden] { | |
| 330 | display: none; | |
| 331 | } | |
| 332 | .kinds.single { | |
| 333 | grid-template-columns: 1fr; | |
| 334 | } | |
| 335 | .kind { | |
| 336 | display: flex; | |
| 337 | flex-direction: column; | |
| 338 | gap: 0.625rem; | |
| 339 | min-width: 0; | |
| 340 | padding: 0.875rem; | |
| 341 | border: 1px solid var(--line); | |
| 342 | border-radius: 12px; | |
| 343 | background: var(--surface); | |
| 344 | transition: border-color 0.2s; | |
| 345 | } | |
| 346 | /* A kind that needs a look says so with its edge as well as its label. */ | |
| 347 | .kind[data-tone="problem"] { | |
| 348 | border-color: rgba(255, 138, 138, 0.35); | |
| 349 | } | |
| 350 | .kind[data-tone="warning"] { | |
| 351 | border-color: rgba(251, 191, 36, 0.3); | |
| 352 | } | |
| 353 | /* Its icon, and beside it its name over its state. */ | |
| 354 | .kind-head { | |
| 355 | display: flex; | |
| 356 | align-items: center; | |
| 357 | gap: 0.75rem; | |
| 358 | } | |
| 359 | .kind-icon { | |
| 360 | flex: none; | |
| 361 | display: grid; | |
| 362 | place-items: center; | |
| 363 | width: 40px; | |
| 364 | height: 40px; | |
| 365 | border-radius: 10px; | |
| 366 | background: var(--raised); | |
| 367 | color: var(--fg); | |
| 368 | } | |
| 369 | .kind.off .kind-icon { | |
| 370 | color: var(--muted); | |
| 371 | } | |
| 372 | .kind-name { | |
| 373 | display: flex; | |
| 374 | flex-direction: column; | |
| 375 | align-items: flex-start; | |
| 376 | gap: 0.25rem; | |
| 377 | min-width: 0; | |
| 378 | flex: 1 1 auto; | |
| 379 | } | |
| 380 | .kind-name h3 { | |
| 381 | margin: 0; | |
| 382 | font-size: 0.9375rem; | |
| 383 | font-weight: 600; | |
| 384 | letter-spacing: -0.005em; | |
| 385 | } | |
| 386 | .kind-about { | |
| 387 | margin: 0; | |
| 388 | color: var(--muted); | |
| 389 | font-size: 0.8125rem; | |
| 390 | line-height: 1.4; | |
| 391 | } | |
| 392 | /* Its state, as a label: a dot in its tone, and a few words. */ | |
| 393 | .pill { | |
| 394 | display: inline-flex; | |
| 395 | align-items: center; | |
| 396 | gap: 0.375rem; | |
| 397 | flex: none; | |
| 398 | padding: 0.0625rem 0.5rem 0.0625rem 0.4375rem; | |
| 399 | border: 1px solid var(--line-strong); | |
| 400 | border-radius: 999px; | |
| 401 | color: var(--muted); | |
| 402 | font-size: 0.75rem; | |
| 403 | font-weight: 500; | |
| 404 | line-height: 1.4; | |
| 405 | white-space: nowrap; | |
| 406 | } | |
| 407 | .pill-dot { | |
| 408 | width: 6px; | |
| 409 | height: 6px; | |
| 410 | border-radius: 50%; | |
| 411 | background: currentColor; | |
| 412 | } | |
| 413 | .pill[data-tone="ok"] { | |
| 414 | border-color: rgba(74, 222, 128, 0.35); | |
| 415 | background: rgba(74, 222, 128, 0.1); | |
| 416 | color: var(--ok); | |
| 417 | } | |
| 418 | .pill[data-tone="working"] { | |
| 419 | border-color: rgba(96, 165, 250, 0.35); | |
| 420 | background: rgba(96, 165, 250, 0.1); | |
| 421 | color: var(--working); | |
| 422 | } | |
| 423 | .pill[data-tone="warning"] { | |
| 424 | border-color: rgba(251, 191, 36, 0.35); | |
| 425 | background: rgba(251, 191, 36, 0.1); | |
| 426 | color: var(--warning); | |
| 427 | } | |
| 428 | .pill[data-tone="problem"] { | |
| 429 | border-color: rgba(255, 138, 138, 0.4); | |
| 430 | background: rgba(255, 138, 138, 0.1); | |
| 431 | color: var(--error); | |
| 432 | } | |
| 433 | /* On its way (running a job, starting, downloading): the dot breathes. */ | |
| 434 | .pill.live .pill-dot { | |
| 435 | animation: breathe 1.6s ease-in-out infinite; | |
| 436 | } | |
| 437 | @keyframes breathe { | |
| 438 | 50% { | |
| 439 | opacity: 0.35; | |
| 440 | } | |
| 441 | } | |
| 442 | .kind-body { | |
| 443 | display: flex; | |
| 444 | flex-direction: column; | |
| 445 | gap: 0.25rem; | |
| 446 | font-size: 0.8125rem; | |
| 447 | line-height: 1.45; | |
| 448 | } | |
| 449 | .kind-body[hidden] { | |
| 450 | display: none; | |
| 451 | } | |
| 452 | .kind-body p { | |
| 453 | margin: 0; | |
| 454 | } | |
| 455 | .kind-headline { | |
| 456 | color: var(--fg); | |
| 457 | font-size: 0.875rem; | |
| 458 | font-weight: 500; | |
| 459 | } | |
| 460 | .kind-cause { | |
| 461 | color: var(--muted); | |
| 462 | } | |
| 463 | .kind-fix { | |
| 464 | color: var(--fg); | |
| 465 | } | |
| 466 | .kind-body code, | |
| 467 | .diagnosis-text code { | |
| 468 | padding: 0.0625rem 0.3125rem; | |
| 469 | border-radius: 5px; | |
| 470 | background: var(--raised); | |
| 471 | color: var(--fg); | |
| 472 | font-size: 0.75rem; | |
| 473 | overflow-wrap: anywhere; | |
| 474 | user-select: text; | |
| 475 | } | |
| 476 | .kind-job { | |
| 477 | display: flex; | |
| 478 | flex-direction: column; | |
| 479 | gap: 0.125rem; | |
| 480 | padding: 0.5rem 0.625rem; | |
| 481 | border-radius: 8px; | |
| 482 | background: var(--raised); | |
| 483 | } | |
| 484 | .kind-job-label { | |
| 485 | color: var(--muted); | |
| 486 | font-size: 0.6875rem; | |
| 487 | font-weight: 600; | |
| 488 | letter-spacing: 0.05em; | |
| 489 | text-transform: uppercase; | |
| 490 | } | |
| 491 | .kind-job-what { | |
| 492 | min-width: 0; | |
| 493 | overflow-wrap: anywhere; | |
| 494 | } | |
| 495 | .kind-job-what a { | |
| 496 | color: var(--fg); | |
| 497 | font-weight: 500; | |
| 498 | } | |
| 499 | .kind-job-what a:hover { | |
| 500 | color: var(--accent); | |
| 501 | } | |
| 502 | .kind-progress { | |
| 503 | display: flex; | |
| 504 | align-items: center; | |
| 505 | gap: 0.625rem; | |
| 506 | } | |
| 507 | .kind-progress .bar { | |
| 508 | flex: 1; | |
| 509 | height: 6px; | |
| 510 | overflow: hidden; | |
| 511 | border-radius: 999px; | |
| 512 | background: var(--line); | |
| 513 | } | |
| 514 | .kind-progress .bar span { | |
| 515 | display: block; | |
| 516 | height: 100%; | |
| 517 | border-radius: 999px; | |
| 518 | background: var(--accent); | |
| 519 | transition: width 0.4s ease; | |
| 520 | } | |
| 521 | .kind-progress .small { | |
| 522 | min-width: 2.5rem; | |
| 523 | text-align: right; | |
| 524 | font-variant-numeric: tabular-nums; | |
| 525 | } | |
| 526 | /* What it is on g1t: there to read, never in the way. */ | |
| 527 | .kind-details { | |
| 528 | display: grid; | |
| 529 | gap: 0.125rem; | |
| 530 | margin: 0; | |
| 531 | padding-top: 0.5rem; | |
| 532 | border-top: 1px solid var(--line); | |
| 533 | font-size: 0.75rem; | |
| 534 | } | |
| 535 | .kind-details[hidden] { | |
| 536 | display: none; | |
| 537 | } | |
| 538 | .kind-details div { | |
| 539 | display: flex; | |
| 540 | align-items: baseline; | |
| 541 | gap: 0.5rem; | |
| 542 | min-width: 0; | |
| 543 | } | |
| 544 | .kind-details dt { | |
| 545 | flex: none; | |
| 546 | width: 4.25rem; | |
| 547 | color: var(--muted); | |
| 548 | } | |
| 549 | .kind-details dd { | |
| 550 | display: flex; | |
| 551 | align-items: baseline; | |
| 552 | flex-wrap: wrap; | |
| 553 | gap: 0 0.5rem; | |
| 554 | min-width: 0; | |
| 555 | margin: 0; | |
| 556 | color: var(--fg); | |
| 557 | overflow-wrap: anywhere; | |
| 558 | user-select: text; | |
| 559 | } | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 560 | /* A newer version waiting, over when g1t last looked. */ |
| 561 | .kind-update { | |
| 562 | display: grid; | |
| 563 | gap: 0.0625rem; | |
| 564 | } | |
| 565 | .kind-update-ready { | |
| 566 | color: var(--accent); | |
| 567 | } | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 568 | button.link { |
| 569 | display: inline-flex; | |
| 570 | align-items: center; | |
| 571 | gap: 0.25rem; | |
| 572 | padding: 0; | |
| 573 | border: 0; | |
| 574 | background: none; | |
| 575 | color: var(--fg); | |
| 576 | font-size: inherit; | |
| 577 | font-weight: 400; | |
| 578 | text-decoration: underline; | |
| 579 | text-decoration-color: var(--line-strong); | |
| 580 | text-underline-offset: 3px; | |
| 581 | } | |
| 582 | button.link:hover { | |
| 583 | color: var(--accent); | |
| 584 | text-decoration-color: currentColor; | |
| 585 | } | |
| 586 | button.link svg { | |
| 587 | flex: none; | |
| 588 | color: var(--muted); | |
| 589 | } | |
| 590 | .kind-actions { | |
| 591 | display: flex; | |
| 592 | flex-wrap: wrap; | |
| 593 | align-items: center; | |
| 594 | gap: 0.5rem; | |
| 595 | margin-top: auto; | |
| 596 | } | |
| 597 | .kind-actions[hidden] { | |
| 598 | display: none; | |
| 599 | } | |
| 600 | .kind-actions .end { | |
| 601 | margin-left: auto; | |
| 602 | } | |
| 603 | button.quiet { | |
| 604 | background: transparent; | |
| 605 | color: var(--muted); | |
| 606 | } | |
| 607 | button.quiet:hover { | |
| 608 | color: var(--fg); | |
| 609 | } | |
| 610 | @media (prefers-reduced-motion: reduce) { | |
| 611 | .pill.live .pill-dot { | |
| 612 | animation: none; | |
| 613 | } | |
| 614 | .kind, | |
| 615 | .kind-progress .bar span { | |
| 616 | transition: none; | |
| 617 | } | |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 618 | } |
| 619 | details.log { | |
| 620 | padding: 0.75rem 0; | |
| 621 | } | |
| 622 | details.log summary { | |
| 623 | color: var(--muted); | |
| 624 | font-size: 0.8125rem; | |
| 625 | } | |
| 626 | details.log pre { | |
| 627 | max-height: 12rem; | |
| 628 | overflow: auto; | |
| 629 | margin: 0.5rem 0; | |
| 630 | padding: 0.5rem; | |
| 631 | border-radius: 8px; | |
| 632 | background: #050507; | |
| 633 | color: var(--muted); | |
| 634 | font: 0.75rem/1.4 ui-monospace, SFMono-Regular, Consolas, monospace; | |
| 635 | white-space: pre-wrap; | |
| 636 | overflow-wrap: anywhere; | |
| 637 | user-select: text; | |
| 638 | } | |
| The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is. | 639 | /* The Runner window's head: what the whole runner is doing, as the title |
| 640 | bar's chip says it, with its Start or Stop, as Docker Desktop shows its | |
| 641 | engine. */ | |
| 642 | .engine { | |
| 643 | display: flex; | |
| 644 | align-items: center; | |
| 645 | gap: 0.75rem; | |
| 646 | margin: 0 0 1rem; | |
| 647 | padding: 0.875rem 1rem; | |
| 648 | border: 1px solid var(--line); | |
| 649 | border-radius: 10px; | |
| 650 | background: var(--surface); | |
| 651 | } | |
| 652 | .engine .dot { | |
| 653 | width: 10px; | |
| 654 | height: 10px; | |
| 655 | margin-top: 0; | |
| 656 | } | |
| 657 | .engine .dot.working { | |
| 658 | animation: breathe 1.6s ease-in-out infinite; | |
| 659 | } | |
| 660 | .engine-text { | |
| 661 | display: flex; | |
| 662 | flex-direction: column; | |
| 663 | gap: 0.125rem; | |
| 664 | flex: 1; | |
| 665 | min-width: 0; | |
| 666 | } | |
| 667 | .engine-text strong { | |
| 668 | font-size: 0.9375rem; | |
| 669 | font-weight: 500; | |
| 670 | } | |
| 671 | .engine-text .muted { | |
| 672 | font-size: 0.8125rem; | |
| 673 | overflow-wrap: anywhere; | |
| 674 | } | |
| 675 | @keyframes breathe { | |
| 676 | 50% { | |
| 677 | opacity: 0.35; | |
| 678 | } | |
| 679 | } | |
| 680 | @media (prefers-reduced-motion: reduce) { | |
| 681 | .engine .dot.working { | |
| 682 | animation: none; | |
| 683 | } | |
| 684 | } | |
| 685 | /* Its log, open and following along. */ | |
| 686 | pre.live-log { | |
| 687 | max-height: 16rem; | |
| 688 | overflow: auto; | |
| 689 | margin: 0.5rem 0; | |
| 690 | padding: 0.625rem 0.75rem; | |
| 691 | border: 1px solid var(--line); | |
| 692 | border-radius: 8px; | |
| 693 | background: #050507; | |
| 694 | color: var(--muted); | |
| 695 | font: 0.75rem/1.45 ui-monospace, SFMono-Regular, Consolas, monospace; | |
| 696 | white-space: pre-wrap; | |
| 697 | overflow-wrap: anywhere; | |
| 698 | user-select: text; | |
| 699 | } | |
| 700 | p.buttons { | |
| 701 | display: flex; | |
| 702 | gap: 0.5rem; | |
| 703 | margin: 0; | |
| 704 | } | |
| 705 | /* Settings' line for the runner: its dot beside what it is doing. */ | |
| 706 | .runner-line { | |
| 707 | display: inline-flex; | |
| 708 | align-items: center; | |
| 709 | gap: 0.5rem; | |
| 710 | } | |
| 711 | .runner-line .dot { | |
| 712 | margin-top: 0; | |
| 713 | } | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 714 | /* A button at work: a small spinner before its label until the status that follows. */ |
| 715 | button.busy { | |
| 716 | cursor: progress; | |
| 717 | } | |
| 718 | button.busy::before { | |
| 719 | content: ""; | |
| 720 | display: inline-block; | |
| 721 | width: 0.75em; | |
| 722 | height: 0.75em; | |
| 723 | margin-right: 0.5em; | |
| 724 | vertical-align: -0.1em; | |
| 725 | border: 2px solid currentColor; | |
| 726 | border-right-color: transparent; | |
| 727 | border-radius: 50%; | |
| 728 | animation: spin 0.8s linear infinite; | |
| 729 | } | |
| 730 | @keyframes spin { | |
| 731 | to { | |
| 732 | transform: rotate(360deg); | |
| 733 | } | |
| 734 | } | |
| 735 | @media (prefers-reduced-motion: reduce) { | |
| 736 | button.busy::before { | |
| 737 | animation: none; | |
| 738 | } | |
| 739 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.