Claude Code engine, image viewer, colour tags, checks per turn and more
- Claude Code as an engine: chats on a "claude-code/<alias>" model run through the claude CLI (stream-json); tool approvals go through our permission system. Sign in from the web UI (URL + pasted code), CLI version and update from Settings; the Docker image contains the CLI. - Small models (8k context) for side tasks: input is cut to the model's context window; one-line chat status, "What does this do?" on approvals, "Plan as automation" (job draft from a chat). - Mobile: top bar no longer widens the page, image viewer (zoom, swipe, mark up, use in message), jump to previous own message, message menu, time under every message. - Approvals: push actions (allow/reject), "always allow in this project" (never for dangerous commands) with revoke. - Per turn: changed files with diffs and rollback; project check command with a green/red badge and "let the agent fix it". - Colour tags for chats in the colours of the active theme. - Project defaults for new chats, project page (timeline), offline queue. Tested in a throwaway container with a mock model (8k small model): 26 API checks passed (permissions, diffs, checks, defaults, job draft, side-task budget, Claude Code status/login URL, unsigned-in chat). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
084452abf6
commit
62fe5bf467
42 files changed
+3734
-55
No files matched your search
+438
-7
@@ -439,6 +439,9 @@ button.session-group:hover {
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
/* Nothing in the bars may widen the page (and push it sideways on phones). */
|
||||
overflow-x: clip;
|
||||
max-width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
@@ -518,6 +521,31 @@ button.session-group:hover {
|
||||
|
||||
.msg {
|
||||
margin-bottom: 14px;
|
||||
position: relative;
|
||||
}
|
||||
/* Message actions: hover on desktop, faintly visible on touch. */
|
||||
.msg-menu {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
.msg:hover > .msg-menu,
|
||||
.msg-menu:focus-within,
|
||||
.msg-menu[open] {
|
||||
opacity: 1;
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.msg-menu {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.msg-menu > summary {
|
||||
background: var(--bg1);
|
||||
border: 1px solid var(--bg2);
|
||||
line-height: 0;
|
||||
}
|
||||
.msg-user {
|
||||
background: var(--bg1);
|
||||
@@ -542,14 +570,16 @@ button.session-group:hover {
|
||||
background: var(--bg1);
|
||||
cursor: text;
|
||||
}
|
||||
.msg-user .msg-actions {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
display: none;
|
||||
}
|
||||
.msg-user:hover .msg-actions {
|
||||
.msg-time {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.msg-user .msg-time {
|
||||
text-align: right;
|
||||
}
|
||||
.msg-user img {
|
||||
max-height: 160px;
|
||||
@@ -1357,6 +1387,23 @@ table.grid input[type="number"] {
|
||||
.topbar .path {
|
||||
display: none;
|
||||
}
|
||||
.topbar .grow {
|
||||
min-width: 0;
|
||||
}
|
||||
.topbar .chip {
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
max-width: 30vw;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.menu-pop-body {
|
||||
max-width: calc(100vw - 16px);
|
||||
}
|
||||
.menu-pop-body .mobile-only {
|
||||
display: block;
|
||||
}
|
||||
.topbar button.small {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
@@ -2526,6 +2573,7 @@ button.danger-solid {
|
||||
height: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
pointer-events: none;
|
||||
z-index: 4;
|
||||
}
|
||||
@@ -4439,3 +4487,386 @@ span.chat-tab > button {
|
||||
height: 64px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* --- image viewer ------------------------------------------------------------ */
|
||||
.viewer-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
background: rgba(0, 0, 0, 0.92);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
.viewer-img {
|
||||
max-width: 100vw;
|
||||
max-height: 100dvh;
|
||||
object-fit: contain;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
touch-action: none;
|
||||
transition: transform 0.08s ease-out;
|
||||
background: repeating-conic-gradient(#222 0% 25%, #111 0% 50%) 50% / 16px 16px;
|
||||
}
|
||||
.viewer-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
padding-top: max(8px, env(safe-area-inset-top));
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.6), transparent);
|
||||
color: #eee;
|
||||
font-size: 13px;
|
||||
}
|
||||
.viewer-bar .button,
|
||||
.viewer-bar button {
|
||||
color: #eee;
|
||||
}
|
||||
.viewer-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 3.5em;
|
||||
}
|
||||
.viewer-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.viewer-nav {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
width: 40px;
|
||||
height: 56px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
color: #eee;
|
||||
}
|
||||
.viewer-nav.prev {
|
||||
left: 6px;
|
||||
}
|
||||
.viewer-nav.next {
|
||||
right: 6px;
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.viewer-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* Thumbnails invite a tap. */
|
||||
.chat img.inline-image,
|
||||
.image-refs img,
|
||||
.user-files img,
|
||||
.image-result img,
|
||||
.svg-block img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
/* --- image markup ------------------------------------------------------------ */
|
||||
.annotator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 52px 8px 64px;
|
||||
}
|
||||
.annotator canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
touch-action: none;
|
||||
cursor: crosshair;
|
||||
background: #111;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.annotator-tools {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 10px;
|
||||
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
.annotator-colors {
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
margin: 0 6px;
|
||||
}
|
||||
.annotator-colors .swatch {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
.annotator-colors .swatch.on {
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
|
||||
/* --- approvals: explanation, session summary ------------------------------------ */
|
||||
.explain {
|
||||
margin: 8px 0 0;
|
||||
padding: 6px 10px;
|
||||
border-left: 3px solid var(--purple);
|
||||
border-radius: 4px;
|
||||
background: var(--bg1);
|
||||
font-size: 12.5px;
|
||||
color: var(--fg2);
|
||||
}
|
||||
.explain .icon {
|
||||
color: var(--purple);
|
||||
}
|
||||
.explain-btn {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.session-item .summary {
|
||||
font-size: 12px;
|
||||
color: var(--fg3);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* --- changes of one turn ------------------------------------------------------- */
|
||||
.turn-changes {
|
||||
margin: -6px 0 12px;
|
||||
}
|
||||
.turn-changes-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--bg2);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg1);
|
||||
}
|
||||
.turn-changes-body > button {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.turn-file > summary {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.turn-file .turn-stat {
|
||||
margin-left: auto;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.turn-file .turn-stat .add {
|
||||
color: var(--green);
|
||||
}
|
||||
.turn-file .turn-stat .del {
|
||||
color: var(--red);
|
||||
}
|
||||
.turn-file .diff {
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* --- colour tags of chats ------------------------------------------------------
|
||||
* The tag is a name; the colour comes from the active theme (--red, --accent, …),
|
||||
* the background is mixed into --bg0, so every theme (light or dark) stays readable. */
|
||||
[data-color="red"] { --tag: var(--red); }
|
||||
[data-color="orange"] { --tag: var(--accent2, var(--red)); }
|
||||
[data-color="yellow"] { --tag: var(--accent, var(--green)); }
|
||||
[data-color="green"] { --tag: var(--green); }
|
||||
[data-color="aqua"] { --tag: var(--aqua); }
|
||||
[data-color="blue"] { --tag: var(--blue); }
|
||||
[data-color="purple"] { --tag: var(--purple); }
|
||||
|
||||
.chat-pane[data-color] {
|
||||
background: color-mix(in srgb, var(--tag) 9%, var(--bg0));
|
||||
}
|
||||
.topbar[data-color] {
|
||||
border-bottom: 2px solid var(--tag);
|
||||
background: color-mix(in srgb, var(--tag) 14%, var(--bg0));
|
||||
}
|
||||
.session-item[data-color] {
|
||||
box-shadow: inset 3px 0 0 var(--tag);
|
||||
}
|
||||
.color-badge {
|
||||
padding: 0 7px;
|
||||
border-radius: 999px;
|
||||
color: var(--fg0);
|
||||
background: color-mix(in srgb, var(--tag) 30%, var(--bg1));
|
||||
border: 1px solid color-mix(in srgb, var(--tag) 60%, transparent);
|
||||
}
|
||||
.color-swatches {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.color-swatches .swatch {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--tag);
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
.color-swatches .swatch.on {
|
||||
border-color: var(--fg0);
|
||||
box-shadow: 0 0 0 2px var(--bg1), 0 0 0 4px var(--tag);
|
||||
}
|
||||
.chat-pane[data-color] .composer-wrap {
|
||||
background: color-mix(in srgb, var(--tag) 9%, var(--bg0));
|
||||
}
|
||||
|
||||
/* --- check badge of a turn ------------------------------------------------------ */
|
||||
.turn-check {
|
||||
margin: -6px 0 12px;
|
||||
}
|
||||
.turn-check .chip {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.turn-check .check-passed {
|
||||
color: var(--green);
|
||||
}
|
||||
.turn-check .check-failed,
|
||||
.turn-check .check-timeout {
|
||||
color: var(--red);
|
||||
}
|
||||
.turn-check-body {
|
||||
margin-top: 4px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--bg2);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg1);
|
||||
}
|
||||
.turn-check-body pre {
|
||||
margin: 0 0 6px;
|
||||
max-height: 40vh;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* --- messages waiting for a connection ------------------------------------------ */
|
||||
.outbox {
|
||||
margin: 0 12px 6px;
|
||||
padding: 6px 10px;
|
||||
border: 1px dashed var(--accent2, var(--accent));
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg1);
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.outbox-head,
|
||||
.outbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.outbox-item span {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--fg3);
|
||||
}
|
||||
|
||||
/* --- project page: timeline -------------------------------------------------------- */
|
||||
.project-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.timeline-day h3 {
|
||||
margin: 14px 0 6px;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.timeline {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 0 0 14px;
|
||||
border-left: 2px solid var(--bg3);
|
||||
}
|
||||
.timeline li {
|
||||
position: relative;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.timeline li::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
top: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--tag, var(--bg3));
|
||||
border: 2px solid var(--bg0);
|
||||
}
|
||||
.timeline li > a {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--bg2);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg1);
|
||||
color: var(--fg1);
|
||||
text-decoration: none;
|
||||
}
|
||||
.timeline li > a:hover {
|
||||
border-color: var(--bg3);
|
||||
}
|
||||
.tl-title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
color: var(--fg0);
|
||||
font-weight: 600;
|
||||
}
|
||||
.tl-when {
|
||||
margin-left: auto;
|
||||
font-weight: 400;
|
||||
font-size: 11.5px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.tl-summary {
|
||||
margin-top: 2px;
|
||||
color: var(--fg2);
|
||||
font-size: 13px;
|
||||
}
|
||||
.tl-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 12px;
|
||||
margin-top: 4px;
|
||||
font-size: 11.5px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.tl-meta .check-passed {
|
||||
color: var(--green);
|
||||
}
|
||||
.tl-meta .check-failed,
|
||||
.tl-meta .check-timeout {
|
||||
color: var(--red);
|
||||
}
|
||||
Reference in new issue
Block a user