UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

2,149 lines (1,874 loc) 129 kB
<!doctype html> <html lang="en" data-trellis-band="L3"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Trellis — admin</title> <link rel="stylesheet" href="/theme/runtime-theme.css" /> <link rel="stylesheet" href="/admin-datatable.css" /> <script type="module" src="@trellis.computer/ui/dist/index.mjs"></script> <style> :root { --toolbar-control-h: 34px; --sidebar-expanded-w: 200px; --sidebar-rail-w: 56px; --chrome-h: var(--sidebar-rail-w); --sidebar-w: var(--sidebar-expanded-w); --secondary-w: 224px; --secondary-rail-w: 48px; --oplog-w: 280px; --sidebar-bg: #0c0c0c; --sidebar-min-w: 140px; --sidebar-max-w: 360px; --secondary-min-w: 160px; --secondary-max-w: 360px; --oplog-min-w: 200px; --oplog-max-w: 560px; --sidebar-item-hover: rgba(255, 255, 255, 0.06); --sidebar-item-active: rgba(157, 190, 254, 0.12); --toolbar-track: #1e1e1e; --toolbar-active: #1c1c1c; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } html.sidebar-collapsed { --sidebar-w: var(--sidebar-rail-w); } html.oplog-collapsed { --oplog-w: 0px; } html.inspector-pinned { --oplog-w: 280px; } html.admin-embed .shell { grid-template-columns: var(--secondary-w) minmax(0, 1fr); } html.admin-embed.inspector-pinned .shell { grid-template-columns: var(--secondary-w) minmax(0, 1fr) var(--oplog-w); } html:not(.inspector-pinned) .oplog { display: none; } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; font-family: var(--font); background: var(--bg); color: var(--text); } .shell { display: grid; grid-template-columns: var(--sidebar-w) var(--secondary-w) minmax( 0, 1fr ); grid-template-rows: var(--chrome-h) minmax(0, 1fr); height: 100%; transition: grid-template-columns 0.18s ease; } html.inspector-pinned .shell { grid-template-columns: var(--sidebar-w) var(--secondary-w) minmax(0, 1fr) var(--oplog-w); } @media (prefers-reduced-motion: reduce) { .shell { transition: none; } } /* TRL-191/196: playground island — hide kernel sidebar (single outer nav) */ html.admin-embed .sidebar { display: none; } html.admin-embed .header { grid-column: 1 / -1; grid-row: 1; } html.admin-embed .secondary { grid-column: 1; grid-row: 2; } html.admin-embed .main { grid-column: 2; grid-row: 2; } html.admin-embed .oplog { grid-column: 3; grid-row: 2; } html.admin-embed .sidebar-toggle { display: none; } html.admin-embed .resize-handle[data-resize='sidebar'] { display: none; } .sidebar { grid-column: 1; grid-row: 1 / -1; background: var(--sidebar-bg); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding: 0 10px 10px; gap: 4px; overflow-x: hidden; overflow-y: auto; min-width: 0; z-index: 3; position: relative; } .sidebar-brand { display: flex; align-items: center; justify-content: flex-start; gap: 8px; height: var(--chrome-h); margin: 0 -10px 4px; padding: 0 10px; flex: none; min-width: 0; box-sizing: border-box; border-bottom: 1px solid var(--border); } /* Same mark as fractal-playground primary rail (/logo.png + foreground mask) */ .brand-mark { width: 22px; height: 22px; flex: none; background: var(--text); -webkit-mask-image: url(/logo.png); mask-image: url(/logo.png); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; } .brand-text { white-space: nowrap; overflow: hidden; font: 700 13px var(--font); letter-spacing: -0.02em; color: var(--text); } .zone { font: 600 10px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text3); padding: 8px 10px 6px; white-space: nowrap; overflow: hidden; } .nav-item { display: flex; align-items: center; gap: 8px; height: 36px; padding: 0 10px; border: none; border-radius: 8px; background: transparent; color: var(--text2); font: 500 13px var(--font); text-align: left; cursor: pointer; width: 100%; min-width: 0; } .nav-label { white-space: nowrap; overflow: hidden; text-overflow: clip; } .sidebar-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex: none; padding: 0; border: none; border-radius: 6px; background: transparent; color: var(--text3); cursor: pointer; } .sidebar-toggle:hover { background: var(--sidebar-item-hover); color: var(--text); } .sidebar-toggle:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; } .sidebar-toggle svg { width: 16px; height: 16px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } .sidebar-toggle .icon-expand { display: none; } .sidebar-toggle .nav-label { display: none; } html.sidebar-collapsed .sidebar-toggle .icon-collapse { display: none; } html.sidebar-collapsed .sidebar-toggle .icon-expand { display: block; } /* Collapsed → icon rail (fractal-playground w-14 density) */ html.sidebar-collapsed .sidebar { /* No top/side padding — brand box shares the header chrome row at full rail width */ padding: 0 0 10px; align-items: center; } html.sidebar-collapsed .sidebar .nav-label, html.sidebar-collapsed .sidebar-brand .brand-text { display: none; } html.sidebar-collapsed .sidebar .zone { display: none; } html.sidebar-collapsed .sidebar-brand { justify-content: center; align-self: stretch; padding: 0; margin: 0 0 4px; width: 100%; height: var(--chrome-h); box-sizing: border-box; } html.sidebar-collapsed .sidebar .nav-item { justify-content: center; padding: 0; width: 36px; height: 36px; } html.sidebar-collapsed .resize-handle[data-resize='sidebar'] { display: none; } html.sidebar-collapsed .sidebar nav[aria-label='Operate'] { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; } .nav-item svg { width: 16px; height: 16px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } .nav-item:hover:not([aria-disabled='true']):not([aria-current='page']) { background: var(--sidebar-item-hover); color: var(--text); } .nav-item[aria-current='page'] { background: var(--sidebar-item-active); color: var(--accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent); } .nav-item[aria-disabled='true'] { opacity: 0.38; cursor: not-allowed; color: var(--text3); } .nav-item:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; } .header { grid-column: 2 / -1; grid-row: 1; display: flex; align-items: center; gap: 8px; height: var(--chrome-h); padding: 0 12px; background: var(--tml-glass-surface); backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); min-width: 0; z-index: 2; } .header-start { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; } .header-end { margin-left: auto; display: flex; align-items: center; gap: 2px; flex: none; min-width: 0; } .header-stats { display: flex; align-items: center; gap: 2px; min-width: 0; font: 500 11px var(--mono); color: var(--text2); } .header-stats .item { display: inline-flex; align-items: center; gap: 5px; padding: 0 8px; height: 28px; color: var(--text2); white-space: nowrap; } .header-stats .item svg { width: 12px; height: 12px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; opacity: 0.75; } .header-stats .item b { font-weight: 600; color: var(--text); } .header-stats .item .lbl { color: var(--text3); } .header-stats .live { display: inline-flex; align-items: center; gap: 6px; padding: 0 8px; height: 28px; white-space: nowrap; } .header-stats .live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 0 0 rgba(18, 201, 5, 0.45); animation: pulse 1.6s ease-out infinite; } @keyframes pulse { 70% { box-shadow: 0 0 0 8px rgba(18, 201, 5, 0); } 100% { box-shadow: 0 0 0 0 rgba(18, 201, 5, 0); } } @media (prefers-reduced-motion: reduce) { .header-stats .live-dot { animation: none; } } @media (max-width: 1100px) { .header-stats .item[data-priority='low'] { display: none; } } @media (max-width: 820px) { .header-stats .item .lbl { display: none; } } .chrome-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex: none; padding: 0; border: none; border-radius: 6px; background: transparent; color: var(--text3); cursor: pointer; } .chrome-toggle:hover { background: var(--sidebar-item-hover); color: var(--text); } .chrome-toggle:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; } .chrome-toggle svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } .notify { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: none; border-radius: 6px; background: transparent; color: var(--text3); cursor: pointer; } .notify:hover { color: var(--text); background: var(--sidebar-item-hover); } .notify:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; } .notify svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } .notify-badge { position: absolute; top: 2px; right: 0; min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--accent); color: #0a0a0a; font: 700 9px var(--mono); line-height: 14px; text-align: center; } .notify-badge[hidden] { display: none; } .crumb { display: flex; align-items: center; gap: 8px; font: 500 11px var(--mono); color: var(--text3); white-space: nowrap; overflow: hidden; min-width: 0; flex: 1; text-transform: lowercase; } .crumb-sep { color: var(--text3); opacity: 0.55; flex: none; } .crumb-segment { display: inline-flex; align-items: center; gap: 5px; flex: none; } .crumb-segment svg { width: 14px; height: 14px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; opacity: 0.85; } .crumb-repo { color: var(--text2); } .crumb-tab { color: var(--text); font-weight: 600; } .crumb-repo, .crumb-zone, .crumb-route, .crumb-tab { flex: none; } .view-header { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 16px; background: var(--bg); border-bottom: 1px solid var(--border); flex: none; min-width: 0; } #board-toolbar { flex: 1; min-width: 0; } .view-meta[hidden] { display: none !important; } .view-header-start { display: flex; align-items: center; gap: 10px; min-width: 0; flex: none; } .view-header-start[hidden] { display: none !important; } .view-meta { font: 500 11px var(--mono); color: var(--text3); margin-top: 2px; } .view-header-actions { display: flex; align-items: center; gap: 4px; flex: none; min-width: 0; } .view-header-actions[hidden] { display: none !important; } .view-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; } .view-panel { display: none; flex: 1; min-height: 0; overflow: hidden; flex-direction: column; } .view-panel[data-active='true'] { display: flex; } .ops-timeline { flex: 1; min-height: 0; overflow: auto; } .ops-timeline .ops { min-height: 100%; } .route-panel-stub { display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 6px; padding: 24px 16px; color: var(--text3); font: 500 12px var(--font); } .route-panel-stub strong { color: var(--text2); font: 600 13px var(--font); } .causal-graph-panel { flex: 1; min-height: 0; overflow: auto; padding: 16px 20px 24px; } .graph-intro h2 { font-size: 15px; font-weight: 600; margin-bottom: 4px; } .graph-intro p { color: var(--text3); margin-bottom: 16px; max-width: 52ch; } .graph-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 16px 8px 8px; } .graph-row { display: flex; align-items: flex-start; } .graph-times { flex: none; width: 52px; list-style: none; margin: 0; padding: 0 6px 0 0; border-right: 1px solid var(--border); } .graph-times li { height: 72px; display: flex; align-items: center; justify-content: flex-end; font: 500 10px var(--mono); color: var(--text3); white-space: nowrap; } .graph-times li.trunk { color: var(--text2); font-weight: 600; } .graph-svg { flex: none; overflow: visible; display: block; } .graph-svg [data-node-id] { cursor: pointer; } .graph-list { flex: 1; list-style: none; min-width: 0; margin: 0; padding: 0 0 0 4px; } .graph-list li { height: 72px; display: flex; align-items: center; } .graph-row-btn { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; border: none; background: transparent; color: inherit; padding: 8px 12px; border-radius: 8px; cursor: pointer; font: inherit; transition: background 0.15s ease; } .graph-row-btn:hover { background: rgba(255, 255, 255, 0.04); } .graph-row-btn:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; } .graph-row-btn.selected { background: color-mix(in srgb, var(--surface2) 72%, var(--bg)); } .graph-active-badge { display: inline-flex; align-items: center; gap: 4px; flex: none; padding: 2px 6px 2px 4px; border-radius: 999px; background: color-mix(in srgb, var(--green, #12c905) 14%, transparent); color: var(--green, #12c905); font: 600 9px var(--mono); letter-spacing: 0.04em; text-transform: uppercase; } .graph-active-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green, #12c905); flex: none; box-shadow: 0 0 6px color-mix(in srgb, var(--green, #12c905) 55%, transparent); } .graph-active-spin { width: 12px; height: 12px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; animation: graph-lane-spin 1.1s linear infinite; } @keyframes graph-lane-spin { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .graph-active-spin { animation: none; } } .graph-active-label { display: none; } @media (min-width: 900px) { .graph-active-label { display: inline; } } .causal-graph-skeleton .skeleton-row { height: 72px; margin: 0 12px; border-radius: 6px; background: linear-gradient( 90deg, var(--surface2), var(--bg), var(--surface2) ); background-size: 200% 100%; animation: causal-graph-pulse 1.2s ease-in-out infinite; } @keyframes causal-graph-pulse { 0%, 100% { background-position: 100% 0; } 50% { background-position: 0 0; } } @media (prefers-reduced-motion: reduce) { .causal-graph-skeleton .skeleton-row { animation: none; } .graph-list .graph-row-btn { transition: none; } } .causal-graph-empty { padding: 48px 24px; text-align: center; color: var(--text3); } .causal-graph-empty strong { display: block; color: var(--text2); margin-bottom: 8px; } .graph-pill { flex: none; border: 2px solid var(--accent); color: var(--accent); background: var(--surface); border-radius: 999px; padding: 4px 10px; font-size: 12px; font-weight: 500; line-height: 1; } .graph-pill.trunk { border-color: var(--text3); color: var(--text3); } .graph-flag { flex: none; background: var(--text3); color: var(--bg); font-size: 12px; font-weight: 600; padding: 4px 10px 4px 14px; line-height: 1; clip-path: polygon(0% 50%, 22% 0%, 100% 0%, 100% 100%, 22% 100%); } .graph-msg-wrap { min-width: 0; flex: 1; } .graph-msg { display: block; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .graph-msg.trunk { color: var(--text3); } .graph-meta { display: block; margin-top: 2px; font: 500 11px var(--mono); color: var(--text3); } .graph-legend { display: flex; flex-wrap: wrap; gap: 16px 24px; margin-top: 14px; padding: 0 4px; color: var(--text3); font-size: 12px; } .graph-legend span { display: inline-flex; align-items: center; gap: 8px; } .graph-swatch { width: 12px; height: 12px; border-radius: 50%; flex: none; } .graph-swatch.merge { background: var(--surface); border: 3px solid var(--text3); box-sizing: border-box; } .ops-badge { margin-left: auto; font: 600 10px var(--mono); color: var(--green); } .ops-badge[hidden] { display: none; } .route-item[data-zone='logs'][aria-current='page'] { background: rgba(245, 158, 11, 0.12); color: #f59e0b; } #pin-toggle[aria-pressed='true'] { background: rgba(245, 158, 11, 0.12); color: #f59e0b; } .secondary { grid-column: 2; grid-row: 2; background: var(--surface2); border-right: 1px solid var(--border); display: flex; flex-direction: column; min-width: 0; overflow-y: auto; overflow-x: hidden; position: relative; z-index: 2; } .secondary-zone { flex: none; } .secondary-zone-toggle { width: 100%; display: flex; align-items: center; gap: 6px; padding: 10px 12px 6px; border: none; background: transparent; font: 600 10px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text3); cursor: pointer; text-align: left; } .secondary-zone-toggle:hover { color: var(--text2); background: rgba(255, 255, 255, 0.03); } .secondary-zone-toggle svg.chev { width: 12px; height: 12px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; transition: transform 0.18s ease; } .secondary-zone.collapsed .secondary-zone-toggle svg.chev { transform: rotate(-90deg); } @media (prefers-reduced-motion: reduce) { .secondary-zone-toggle svg.chev { transition: none; } } .secondary-zone[data-zone='logs'] .secondary-zone-toggle { border-top: 1px solid rgba(255, 255, 255, 0.06); margin-top: 2px; padding-top: 12px; } .secondary-zone.collapsed .secondary-nav { display: none; } .secondary-nav { display: flex; flex-direction: column; gap: 2px; padding: 0 8px 10px; flex: none; } .secondary-nav .nav-item { font-size: 12px; height: 32px; border-radius: 6px; } .secondary-empty { display: none; flex: 1; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 6px; padding: 24px 16px; color: var(--text3); font: 500 12px var(--font); } .secondary-empty[data-active='true'] { display: flex; } .secondary-empty strong { color: var(--text2); font: 600 13px var(--font); } .milestones-panel { display: flex; flex: 1; flex-direction: column; min-height: 0; overflow: auto; padding: 16px; gap: 10px; } .milestones-panel[data-active='true'] { display: flex; } .milestones-empty { color: var(--text3); font: 500 12px var(--font); } .milestones-list { display: flex; flex-direction: column; gap: 8px; } .milestone-card { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: color-mix( in srgb, var(--tml-glass-surface) 72%, transparent ); } .milestone-message { font: 600 13px var(--font); color: var(--text); line-height: 1.35; } .milestone-meta { display: flex; flex-wrap: wrap; gap: 8px; font: 500 11px var(--mono); color: var(--text3); } .milestone-id { color: var(--entity-milestone, #2090f5); } .resize-handle { position: absolute; top: 0; bottom: 0; width: 4px; z-index: 5; cursor: col-resize; background: transparent; } .resize-handle:hover, .resize-handle.is-active { background: color-mix(in srgb, var(--accent) 35%, transparent); } .resize-handle[data-resize='sidebar'], .resize-handle[data-resize='secondary'] { right: 0; } .resize-handle[data-resize='oplog'] { left: 0; } html.is-resizing { cursor: col-resize; user-select: none; } html.is-resizing iframe, html.is-resizing .kanban, html.is-resizing .main { pointer-events: none; } .view-toggle { display: flex; align-items: center; gap: 0; height: var(--toolbar-control-h); padding: 2px; border: 1px solid var(--border); border-radius: 8px; background: var(--toolbar-track); flex: none; } .view-toggle button { display: inline-flex; align-items: center; gap: 6px; height: calc(var(--toolbar-control-h) - 4px); border: none; border-radius: 6px; background: transparent; color: var(--text3); font: 500 12.8px var(--font); padding: 0 10px; cursor: pointer; } .view-toggle button svg { width: 14px; height: 14px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } .view-toggle button .lbl { display: none; } .view-toggle button[aria-checked='true'] { background: var(--toolbar-active); color: var(--text); } .view-toggle button[aria-checked='true'] .lbl { display: inline; } .search { position: relative; flex: 1; min-width: 140px; display: flex; align-items: center; gap: 6px; height: var(--toolbar-control-h); box-sizing: border-box; padding: 0 8px; /* inset stroke — keeps content box 34px so #search-input measures 34px (e2e) */ border: none; border-radius: 8px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--border); } .search > svg { width: 14px; height: 14px; flex: none; stroke: var(--text3); fill: none; stroke-width: 1.75; } .search input, #search-input { flex: 1; min-width: 0; height: var(--toolbar-control-h); box-sizing: border-box; border: none; background: transparent; color: var(--text); font: 400 12.8px var(--font); outline: none; } .search:focus-within { box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 1px var(--tml-accent-glow); } .search-clear { border: none; background: transparent; color: var(--text3); font-size: 16px; cursor: pointer; display: none; line-height: 1; flex: none; } .search.has-value .search-clear { display: block; } .toolbar-icon-btn { width: var(--toolbar-control-h); height: var(--toolbar-control-h); display: inline-flex; align-items: center; justify-content: center; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--text2); cursor: pointer; flex: none; } .toolbar-icon-btn:hover:not(:disabled) { background: var(--toolbar-track); } .toolbar-icon-btn:disabled { opacity: 0.4; cursor: not-allowed; } .toolbar-icon-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; } .filters-wrap { position: relative; flex: none; } .filters-menu { position: absolute; z-index: 30; right: 0; top: calc(100% + 4px); min-width: 180px; padding: 8px 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; font: 400 12px var(--font); color: var(--text2); } .filters-menu[hidden] { display: none; } .btn-new-issue { height: var(--toolbar-control-h); margin-left: 4px; padding: 0 12px; border: none; border-radius: 8px; background: var(--accent); color: #0a0a0a; font: 600 12.8px var(--font); cursor: not-allowed; opacity: 0.45; flex: none; white-space: nowrap; } .main { grid-column: 3; grid-row: 2; display: flex; flex-direction: column; min-height: 0; overflow: hidden; } html.inspector-pinned .main { grid-column: 3; } #tml-root { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; } .banner { display: none; margin: 12px 16px 0; padding: 8px 12px; border-radius: 8px; font: 500 12px var(--mono); border: 1px solid color-mix(in srgb, var(--yellow) 40%, transparent); background: color-mix(in srgb, var(--yellow) 12%, transparent); color: var(--yellow); } .banner.show { display: block; } .banner.stale { border-color: color-mix(in srgb, var(--red) 40%, transparent); background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); } .proj { display: none; flex: 1; min-height: 0; overflow: auto; padding: 16px; background: var(--bg); } .proj.active { display: flex; flex-direction: column; } /* Kanban: board scrolls horizontally; each column body scrolls vertically */ .proj.kanban-view.active { overflow: hidden; padding: 0; } .grid-host { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; width: 100%; } /* Full-bleed Operate table — edge to chrome, no inset card */ .proj.table-view { padding: 0; overflow: hidden; } .proj.table-view.active { display: flex; flex-direction: column; } .proj.table-view .table-wrap { flex: 1; min-height: 0; } .kanban { display: flex; gap: 16px; flex: 1; min-height: 0; max-height: 100%; width: 100%; min-width: 0; padding: 16px; box-sizing: border-box; overflow-x: auto; overflow-y: hidden; align-items: stretch; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; } .kanban-col { flex: 0 0 300px; width: 300px; min-width: 300px; max-width: 300px; display: flex; flex-direction: column; min-height: 0; max-height: 100%; } .kanban-col-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px 10px 0 0; border-bottom: none; flex: none; } .kanban-col-head .col-title { font: 600 12px var(--font); text-transform: uppercase; letter-spacing: 0.4px; color: var(--text2); } .kanban-col-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 8px; padding: 10px; min-height: 0; max-height: 100%; overflow-y: auto; overscroll-behavior: contain; background: var(--tml-kanban-body-inset); border: 1px solid var(--border); border-top: none; border-radius: 0 0 10px 10px; } /* TML clones the col-body per row; nested clones must not flex-grow (tall cards). */ .kanban-col-body > .kanban-col-body { flex: none; border: none; padding: 0; background: transparent; min-height: 0; max-height: none; overflow: visible; border-radius: 0; } .kanban-empty { color: var(--text3); font-size: 12px; text-align: center; padding: 24px 8px; } .issue-card, .lane-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; text-align: left; color: inherit; font: inherit; cursor: pointer; width: 100%; flex: none; } .lane-promote { margin-top: 8px; font-size: 11px; padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2, var(--surface)); color: var(--text-muted, var(--text)); cursor: pointer; } .lane-promote:hover { border-color: var(--accent); } .issue-card:hover, .lane-card:hover, .issue-card:focus-visible, .lane-card:focus-visible { border-color: var(--accent); outline: none; box-shadow: 0 0 0 1px var(--tml-accent-glow); } .issue-card.flash, .lane-card.flash, table.lanes-table tbody tr.flash { border-color: var(--green); } /* Green create pulse — new issues / lanes / rows */ @keyframes flash-create { 0% { background-color: color-mix( in srgb, var(--green) 42%, var(--surface) ); box-shadow: 0 0 0 1px color-mix(in srgb, var(--green) 55%, transparent); } 100% { background-color: var(--surface); box-shadow: none; } } .issue-card.flash-create, .lane-card.flash-create, table.lanes-table tbody tr.flash-create td { animation: flash-create 1.25s ease-out; } @media (prefers-reduced-motion: reduce) { .issue-card.flash-create, .lane-card.flash-create, table.lanes-table tbody tr.flash-create td { animation: none; background-color: color-mix( in srgb, var(--green) 22%, var(--surface) ); } .progress-spin svg { animation: none; } } .issue-card-head, .lane-card-head { display: flex; align-items: center; gap: 8px; min-width: 0; } .issue-card-head .issue-id, .lane-card-head .lane-id { flex: 1; min-width: 0; } .progress-spin { display: none; flex: none; width: 14px; height: 14px; color: var(--green); } .issue-card[data-status='in_progress'] .progress-spin, .lane-card[data-status='active'] .progress-spin, .lane-card[data-status='promoting'] .progress-spin, table.lanes-table tbody tr[data-status='active'] .progress-spin, table.lanes-table tbody tr[data-status='promoting'] .progress-spin { display: inline-flex; align-items: center; justify-content: center; } .progress-spin svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; animation: progress-spin 0.85s linear infinite; } @keyframes progress-spin { to { transform: rotate(360deg); } } table.lanes-table tbody tr td .progress-spin { margin-right: 6px; vertical-align: -2px; } .issue-id, .lane-id { font: 600 11px var(--mono); color: var(--accent); } .lane-id { color: var(--green); } .issue-title { margin-top: 4px; font: 600 13px var(--font); } .issue-meta, .lane-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } .lane-meta { flex-direction: column; font: 400 11px var(--mono); color: var(--text3); gap: 2px; } .lane-badge { font: 500 10px var(--mono); color: var(--green); background: var(--tml-badge-success-bg); border: 1px solid var(--tml-badge-success-border); padding: 2px 8px; border-radius: 999px; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .lane-badge.none { background: var(--surface2); border-color: var(--border); color: var(--text3); } .priority-badge { font: 600 10px var(--mono); text-transform: uppercase; padding: 3px 8px; border-radius: 999px; } .priority-badge.critical, .priority-badge.high { background: var(--tml-badge-critical-bg); color: var(--red); } .priority-badge.medium { background: var(--tml-badge-warning-bg); color: var(--yellow); } .priority-badge.low { background: var(--tml-badge-neutral-bg); color: var(--text3); } .oplog { grid-row: 2; grid-column: 4; background: var(--surface2); border-left: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; min-width: 0; overflow: hidden; position: relative; } .oplog-head { display: flex; align-items: center; gap: 4px; height: var(--chrome-h); padding: 0 8px 0 12px; border-bottom: 1px solid var(--border); flex: none; box-sizing: border-box; background: var(--tml-glass-surface); backdrop-filter: blur(12px); } .oplog-head h2 { margin: 0; flex: 1; min-width: 0; font: 600 11px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } html.oplog-collapsed .oplog-head h2, html.oplog-collapsed .ops { display: none; } .ops { flex: 1; overflow: auto; } .oplog-day-toggle { position: sticky; top: 0; z-index: 1; width: 100%; display: flex; align-items: center; gap: 6px; padding: 6px 12px; border: none; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); background: var(--surface-inset); font: 600 10px var(--mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--text3); cursor: pointer; text-align: left; } .oplog-day-toggle::before { content: '▾'; font-size: 12px; line-height: 1; opacity: 0.7; } .oplog-day-toggle[aria-expanded='false']::before { content: '▸'; } @media (prefers-reduced-motion: reduce) { .oplog-day-toggle::before { transition: none; } } .oplog-day-list[hidden] { display: none; } .op { display: grid; grid-template-columns: 64px 1fr 72px; gap: 8px; padding: 8px 14px; font: 400 11px var(--mono); border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); cursor: pointer; } .op:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); } .op:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: -2px; } .op time { color: var(--text3); } .op .kind { color: var(--text2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .op .hash { color: var(--accent); } .ops-empty { padding: 24px 14px; color: var(--text3); font: 400 12px var(--mono); } .toast { position: fixed; right: calc(var(--oplog-w) + 20px); bottom: 16px; padding: 10px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; font: 500 12px var(--mono); opacity: 0; transform: translateY(8px); transition: opacity 0.2s, transform 0.2s; pointer-events: none; z-index: 20; cursor: pointer; max-width: min(360px, calc(100vw - 32px)); } .toast.show { opacity: 1; transform: translateY(0); pointer-events: auto; } html.oplog-collapsed .toast { right: 16px; } dialog { margin: auto; /* overrides * { margin: 0 } — centers showModal() */ border: 1px solid var(--border); border-radius: 14px; background: var(--surface); color: var(--text); padding: 0; max-width: 560px; width: calc(100% - 32px); max-height: 80vh; overflow-y: auto; } dialog::backdrop { background: rgba(0, 0, 0, 0.6); } @media (prefers-reduced-motion: no-preference) { dialog[open] { animation: admin-dlg-in 0.2s ease-out; } } @keyframes admin-dlg-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } .dlg-h { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; border-bottom: 1px solid var(--border); font: 700 14px var(--font); } .dlg-b { padding: 14px 16px 18px; color: var(--text2); font: 400 13px var(--font); } .dlg-h button { border: none; background: transparent; color: var(--text3); font-size: 18px; cursor: pointer; } .dlg-actions { margin-top: 12px; } .dlg-actions button { font: 500 12px var(--mono); padding: 6px 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--accent); color: #0a0a0a; cursor: pointer; } .hidden-by-search { display: none !important; } @media (max-width: 1099px) { .shell { grid-template-columns: var(--sidebar-w) var(--secondary-w) minmax( 0, 1fr ); grid-template-rows: var(--chrome-h) minmax(0, 1fr); } html.inspector-pinned .shell { grid-template-rows: var(--chrome-h) minmax(0, 1fr) 200px; } html.admin-embed .shell { grid-template-columns: var(--secondary-w) minmax(0, 1fr); } html.admin-embed.inspector-pinned .shell { grid-template-rows: var(--chrome-h) minmax(0, 1fr) 200px; } .header { grid-column: 2 / -1; grid-row: 1; } html.admin-embed .header { grid-column: 1 / -1; grid-row: 1; } html.inspector-pinned .oplog { grid-row: 3; grid-column: 1 / -1; border-left: none; border-top: 1px solid var(--border); } html.admin-embed.inspector-pinned .oplog { grid-column: 1 / -1; grid-row: 3; } .toast { right: 16px; } } </style> </head> <body> <div class="shell"> <aside class="sidebar" id="sidebar"> <div class="sidebar-brand" aria-label="Trellis"> <span class="brand-mark" aria-hidden="true"></span> <span class="brand-text">Trellis</span> </div> <div class="zone">Operate</div> <nav aria-label="Operate"> <button type="button" class="nav-item" data-nav-id="collections" aria-disabled="true" aria-label="Collections (coming soon)" title="Collections (coming soon)" > <svg viewBox="0 0 24 24" aria-hidden="true"> <ellipse cx="12" cy="5" rx="9" ry="3" /> <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" /> <path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3" /> </svg> <span class="nav-label">Collections</span> </button> <button type="button" class="nav-item" data-nav-id="storage" aria-disabled="true" aria-label="Storage (coming soon)" title="Storage (coming soon)" > <svg viewBox="0 0 24 24" aria-hidden="true"> <path d="M22 12H2" /> <path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M6 16h.01" /> <path d="M10 16h.01" /> </svg> <span class="nav-label">Storage</span> </button> <button type="button" class="nav-item" data-nav-id="pages" aria-disabled="true" aria-label="Pages (coming soon)" title="Pages (coming soon)" > <svg viewBox="0 0 24 24" aria-hidden="true"> <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" /> <path d="M14 2v6h6" /> <path d="M16 13H8" /> <path d="M16 17H8" /> <path d="M10 9H8" /> </svg> <span class="nav-label">Pages</span> </button> <button type="button" class="nav-item" data-nav-id="history" aria-disabled="true" aria-label="History (coming soon)" title="History (coming soon)" > <svg viewBox="0 0 24 24" aria-hidden="true"> <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /> <path d="M3 3v5h5" /> <path d="M12 7v5l4 2" /> </svg> <span class="nav-label">History</span> </button> <button type="button" class="n