diff --git a/packages/omnibioai-ui/dist/index.css b/packages/omnibioai-ui/dist/index.css index 7e85dc47..4b9328d4 100644 --- a/packages/omnibioai-ui/dist/index.css +++ b/packages/omnibioai-ui/dist/index.css @@ -1,2 +1,2 @@ -.omni-btn{border-radius:var(--radius-sm);font-family:var(--font-sans);cursor:pointer;white-space:nowrap;border:none;align-items:center;gap:6px;font-weight:600;text-decoration:none;transition:opacity .15s,background .15s,box-shadow .15s;display:inline-flex}.omni-btn:disabled{opacity:.4;cursor:not-allowed}.omni-btn--sm{font-size:var(--font-size-xs);padding:4px 10px}.omni-btn--md{font-size:var(--font-size-sm);padding:7px 14px}.omni-btn--lg{font-size:var(--font-size-base);padding:10px 20px}.omni-btn--primary{background:var(--color-accent);color:var(--color-on-accent);border:.5px solid var(--color-accent)}.omni-btn--primary:hover:not(:disabled){opacity:.88}.omni-btn--secondary{background:var(--color-bg-surface);color:var(--color-text);border:.5px solid var(--color-border)}.omni-btn--secondary:hover:not(:disabled){background:var(--color-bg-surface2)}.omni-btn--danger{background:var(--color-danger-dim);color:var(--color-danger);border:.5px solid var(--color-danger-border)}.omni-btn--danger:hover:not(:disabled){opacity:.88}.omni-btn--ghost{color:var(--color-text-muted);border:.5px solid var(--color-border);background:0 0}.omni-btn--ghost:hover:not(:disabled){color:var(--color-text);background:var(--color-bg-surface2)}.omni-spinner{border-style:solid;border-color:var(--color-border);border-top-color:var(--color-accent);border-radius:50%;animation:.7s linear infinite omni-spin;display:inline-block}.omni-spinner--sm{border-width:1.5px;width:12px;height:12px}.omni-spinner--md{border-width:2px;width:18px;height:18px}.omni-spinner--lg{border-width:3px;width:28px;height:28px}@keyframes omni-spin{to{transform:rotate(360deg)}}.omni-badge{border-radius:var(--radius-pill);font-size:var(--font-size-xs);border:.5px solid #0000;align-items:center;gap:3px;padding:2px 8px;font-weight:600;display:inline-flex}.omni-badge--success{background:var(--color-success-dim);color:var(--color-success);border-color:var(--color-success-border)}.omni-badge--warning{background:var(--color-warning-dim);color:var(--color-warning);border-color:var(--color-warning-border)}.omni-badge--danger{background:var(--color-danger-dim);color:var(--color-danger);border-color:var(--color-danger-border)}.omni-badge--info{background:var(--color-info-dim);color:var(--color-info);border-color:var(--color-info-border)}.omni-badge--default,.omni-badge--neutral{background:var(--color-bg-surface2);color:var(--color-text-secondary);border-color:var(--color-border)}.omni-card{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden}.omni-card__header{border-bottom:.5px solid var(--color-border);justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.omni-card__title{font-size:var(--font-size-sm);color:var(--color-text);font-weight:600}.omni-card__actions{align-items:center;gap:6px;display:flex}.omni-card__body{padding:14px 16px}.omni-card--elevated{box-shadow:0 4px 16px #0006}.omni-card--clickable{cursor:pointer;transition:background .15s}.omni-card--clickable:hover{background:var(--color-bg-surface2)}.omni-input-group{flex-direction:column;gap:4px;display:flex}.omni-input-label{font-size:var(--font-size-sm);color:var(--color-text-secondary);font-weight:600}.omni-input{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:var(--radius-sm);width:100%;color:var(--color-text);font-size:var(--font-size-base);font-family:var(--font-sans);outline:none;padding:7px 10px;transition:border-color .15s,box-shadow .15s}.omni-input:focus{border-color:var(--color-accent);box-shadow:0 0 0 3px var(--color-accent-dim)}.omni-input::placeholder{color:var(--color-text-muted)}.omni-input:disabled{opacity:.5;cursor:not-allowed}.omni-input--error{border-color:var(--color-danger)}.omni-input-error{font-size:var(--font-size-xs);color:var(--color-danger)}.omni-status-dot-wrap{align-items:center;gap:5px;display:inline-flex}.omni-status-dot{border-radius:50%;flex-shrink:0;width:6px;height:6px}.omni-status-dot--up{background:var(--color-success)}.omni-status-dot--down{background:var(--color-danger)}.omni-status-dot--init,.omni-status-dot--warn{background:var(--color-warning)}.omni-status-dot--pulse{animation:2s ease-in-out infinite omni-status-pulse}@keyframes omni-status-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.8)}}.omni-status-dot-label{font-size:var(--font-size-xs);color:var(--color-text-secondary)}.omni-table-wrap{border:.5px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden}table.omni-table{border-collapse:collapse;width:100%;font-size:var(--font-size-sm);font-family:var(--font-sans)}.omni-table thead th{font-size:var(--font-size-xs);color:var(--color-text-muted);background:var(--color-bg-surface2);border-bottom:.5px solid var(--color-border);text-align:left;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;-webkit-user-select:none;user-select:none;padding:8px 12px;font-weight:600}.omni-table thead th.sortable{cursor:pointer}.omni-table thead th.sortable:hover{color:var(--color-text-secondary)}.omni-table thead th.sorted-asc:after{content:" ↑"}.omni-table thead th.sorted-desc:after{content:" ↓"}.omni-table thead th.r,.omni-table tbody td.r{text-align:right}.omni-table tbody td{border-bottom:.5px solid var(--color-border);color:var(--color-text);vertical-align:middle;padding:8px 12px}.omni-table tbody tr:last-child td{border-bottom:none}.omni-table tbody tr:hover td{background:var(--color-bg-surface2)}.omni-table-footer{border-top:.5px solid var(--color-border);background:var(--color-bg-surface);justify-content:center;align-items:center;gap:6px;padding:8px 12px;display:flex}.omni-pg-btn{font-size:var(--font-size-xs);border:.5px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-surface2);color:var(--color-text-muted);cursor:pointer;font-family:var(--font-sans);padding:4px 10px;transition:color .15s}.omni-pg-btn:hover:not(:disabled){color:var(--color-text)}.omni-pg-btn:disabled{opacity:.4;cursor:not-allowed}.omni-pg-btn.active{background:var(--color-accent);color:var(--color-on-accent);border-color:var(--color-accent)}.omni-pg-info{font-size:var(--font-size-xs);color:var(--color-text-muted);margin:0 4px}.omni-tab-nav{background:var(--color-bg-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;border-bottom:.5px solid var(--color-border);gap:2px;padding:4px 4px 0;display:flex}.omni-tab-btn{font-size:var(--font-size-base);color:var(--color-text-muted);cursor:pointer;white-space:nowrap;font-weight:500;font-family:var(--font-sans);border-radius:var(--radius-sm) var(--radius-sm) 0 0;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:9px 18px;transition:color .15s}.omni-tab-btn:hover{color:var(--color-text-secondary)}.omni-tab-btn.active{color:var(--color-text);border-bottom-color:var(--color-accent)}.omni-tab-panel{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:0 0 var(--radius-lg) var(--radius-lg);border-top:none;padding:18px 20px}.omni-progress-wrap{align-items:center;gap:8px;display:flex}.omni-progress-track{background:var(--color-bg-surface2);border-radius:3px;flex:1;height:6px;overflow:hidden}.omni-progress-track--sm{height:4px}.omni-progress-track--md{height:6px}.omni-progress-track--lg{height:10px}.omni-progress-fill{border-radius:3px;min-width:2px;height:100%;transition:width .3s}.omni-progress-fill--success{background:var(--color-success)}.omni-progress-fill--danger{background:var(--color-danger)}.omni-progress-fill--warning{background:var(--color-warning)}.omni-progress-fill--accent{background:var(--color-accent)}.omni-progress-fill--info{background:var(--color-info)}.omni-progress-label{font-size:var(--font-size-xs);color:var(--color-text-muted);white-space:nowrap;text-align:right;min-width:36px;font-weight:600}.omni-tooltip-wrap{display:inline-flex;position:relative}.omni-tooltip-box{background:var(--color-bg-surface2);border:.5px solid var(--color-border);border-radius:var(--radius-sm);font-size:var(--font-size-xs);color:var(--color-text);white-space:nowrap;pointer-events:none;opacity:0;z-index:100;font-family:var(--font-sans);padding:5px 9px;transition:opacity .15s;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%)}.omni-tooltip-wrap:hover .omni-tooltip-box{opacity:1}.omni-select-group{flex-direction:column;gap:4px;display:flex}.omni-select-label{font-size:var(--font-size-sm);color:var(--color-text-secondary);font-weight:600}.omni-select{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:var(--radius-sm);color:var(--color-text);font-size:var(--font-size-base);font-family:var(--font-sans);cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-position:right 10px center;background-repeat:no-repeat;outline:none;padding:7px 30px 7px 10px;transition:border-color .15s}.omni-select:focus{border-color:var(--color-accent)}.omni-select:disabled{opacity:.5;cursor:not-allowed} +.omni-btn{border-radius:var(--radius-sm);font-family:var(--font-sans);cursor:pointer;white-space:nowrap;border:none;align-items:center;gap:6px;font-weight:600;text-decoration:none;transition:opacity .15s,background .15s,box-shadow .15s;display:inline-flex}.omni-btn:disabled{opacity:.4;cursor:not-allowed}.omni-btn--sm{font-size:var(--font-size-xs);padding:4px 10px}.omni-btn--md{font-size:var(--font-size-sm);padding:7px 14px}.omni-btn--lg{font-size:var(--font-size-base);padding:10px 20px}.omni-btn--primary{background:var(--color-accent);color:var(--color-on-accent);border:.5px solid var(--color-accent)}.omni-btn--primary:hover:not(:disabled){opacity:.88}.omni-btn--secondary{background:var(--color-bg-surface);color:var(--color-text);border:.5px solid var(--color-border)}.omni-btn--secondary:hover:not(:disabled){background:var(--color-bg-surface2)}.omni-btn--danger{background:var(--color-danger-dim);color:var(--color-danger);border:.5px solid var(--color-danger-border)}.omni-btn--danger:hover:not(:disabled){opacity:.88}.omni-btn--ghost{color:var(--color-text-muted);border:.5px solid var(--color-border);background:0 0}.omni-btn--ghost:hover:not(:disabled){color:var(--color-text);background:var(--color-bg-surface2)}.omni-spinner{border-style:solid;border-color:var(--color-border);border-top-color:var(--color-accent);border-radius:50%;animation:.7s linear infinite omni-spin;display:inline-block}.omni-spinner--sm{border-width:1.5px;width:12px;height:12px}.omni-spinner--md{border-width:2px;width:18px;height:18px}.omni-spinner--lg{border-width:3px;width:28px;height:28px}@keyframes omni-spin{to{transform:rotate(360deg)}}.omni-badge{border-radius:var(--radius-pill);font-size:var(--font-size-xs);border:.5px solid #0000;align-items:center;gap:3px;padding:2px 8px;font-weight:600;display:inline-flex}.omni-badge--success{background:var(--color-success-dim);color:var(--color-success);border-color:var(--color-success-border)}.omni-badge--warning{background:var(--color-warning-dim);color:var(--color-warning);border-color:var(--color-warning-border)}.omni-badge--danger{background:var(--color-danger-dim);color:var(--color-danger);border-color:var(--color-danger-border)}.omni-badge--info{background:var(--color-info-dim);color:var(--color-info);border-color:var(--color-info-border)}.omni-badge--default,.omni-badge--neutral{background:var(--color-bg-surface2);color:var(--color-text-secondary);border-color:var(--color-border)}.omni-card{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden}.omni-card__header{border-bottom:.5px solid var(--color-border);justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.omni-card__title{font-size:var(--font-size-sm);color:var(--color-text);font-weight:600}.omni-card__actions{align-items:center;gap:6px;display:flex}.omni-card__body{padding:14px 16px}.omni-card--elevated{box-shadow:0 4px 16px #0006}.omni-card--clickable{cursor:pointer;transition:background .15s}.omni-card--clickable:hover{background:var(--color-bg-surface2)}.omni-input-group{flex-direction:column;gap:4px;display:flex}.omni-input-label{font-size:var(--font-size-sm);color:var(--color-text-secondary);font-weight:600}.omni-input{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:var(--radius-sm);width:100%;color:var(--color-text);font-size:var(--font-size-base);font-family:var(--font-sans);outline:none;padding:7px 10px;transition:border-color .15s,box-shadow .15s}.omni-input:focus{border-color:var(--color-accent);box-shadow:0 0 0 3px var(--color-accent-dim)}.omni-input::placeholder{color:var(--color-text-muted)}.omni-input:disabled{opacity:.5;cursor:not-allowed}.omni-input--error{border-color:var(--color-danger)}.omni-input-error{font-size:var(--font-size-xs);color:var(--color-danger)}.omni-status-dot-wrap{align-items:center;gap:5px;display:inline-flex}.omni-status-dot{border-radius:50%;flex-shrink:0;width:6px;height:6px}.omni-status-dot--up{background:var(--color-success)}.omni-status-dot--down{background:var(--color-danger)}.omni-status-dot--init,.omni-status-dot--warn{background:var(--color-warning)}.omni-status-dot--pulse{animation:2s ease-in-out infinite omni-status-pulse}@keyframes omni-status-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.8)}}.omni-status-dot-label{font-size:var(--font-size-xs);color:var(--color-text-secondary)}.omni-table-wrap{border:.5px solid var(--color-border);border-radius:var(--radius-lg);-webkit-overflow-scrolling:touch;overflow:auto hidden}table.omni-table{border-collapse:collapse;width:100%;font-size:var(--font-size-sm);font-family:var(--font-sans)}.omni-table thead th{font-size:var(--font-size-xs);color:var(--color-text-muted);background:var(--color-bg-surface2);border-bottom:.5px solid var(--color-border);text-align:left;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;-webkit-user-select:none;user-select:none;padding:8px 12px;font-weight:600}.omni-table thead th.sortable{cursor:pointer}.omni-table thead th.sortable:hover{color:var(--color-text-secondary)}.omni-table thead th.sorted-asc:after{content:" ↑"}.omni-table thead th.sorted-desc:after{content:" ↓"}.omni-table thead th.r,.omni-table tbody td.r{text-align:right}.omni-table tbody td{border-bottom:.5px solid var(--color-border);color:var(--color-text);vertical-align:middle;padding:8px 12px}.omni-table tbody tr:last-child td{border-bottom:none}.omni-table tbody tr:hover td{background:var(--color-bg-surface2)}.omni-table-footer{border-top:.5px solid var(--color-border);background:var(--color-bg-surface);justify-content:center;align-items:center;gap:6px;padding:8px 12px;display:flex}.omni-pg-btn{font-size:var(--font-size-xs);border:.5px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-surface2);color:var(--color-text-muted);cursor:pointer;font-family:var(--font-sans);padding:4px 10px;transition:color .15s}.omni-pg-btn:hover:not(:disabled){color:var(--color-text)}.omni-pg-btn:disabled{opacity:.4;cursor:not-allowed}.omni-pg-btn.active{background:var(--color-accent);color:var(--color-on-accent);border-color:var(--color-accent)}.omni-pg-info{font-size:var(--font-size-xs);color:var(--color-text-muted);margin:0 4px}.omni-tab-nav{background:var(--color-bg-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;border-bottom:.5px solid var(--color-border);gap:2px;padding:4px 4px 0;display:flex}.omni-tab-btn{font-size:var(--font-size-base);color:var(--color-text-muted);cursor:pointer;white-space:nowrap;font-weight:500;font-family:var(--font-sans);border-radius:var(--radius-sm) var(--radius-sm) 0 0;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:9px 18px;transition:color .15s}.omni-tab-btn:hover{color:var(--color-text-secondary)}.omni-tab-btn.active{color:var(--color-text);border-bottom-color:var(--color-accent)}.omni-tab-panel{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:0 0 var(--radius-lg) var(--radius-lg);border-top:none;padding:18px 20px}.omni-progress-wrap{align-items:center;gap:8px;display:flex}.omni-progress-track{background:var(--color-bg-surface2);border-radius:3px;flex:1;height:6px;overflow:hidden}.omni-progress-track--sm{height:4px}.omni-progress-track--md{height:6px}.omni-progress-track--lg{height:10px}.omni-progress-fill{border-radius:3px;min-width:2px;height:100%;transition:width .3s}.omni-progress-fill--success{background:var(--color-success)}.omni-progress-fill--danger{background:var(--color-danger)}.omni-progress-fill--warning{background:var(--color-warning)}.omni-progress-fill--accent{background:var(--color-accent)}.omni-progress-fill--info{background:var(--color-info)}.omni-progress-label{font-size:var(--font-size-xs);color:var(--color-text-muted);white-space:nowrap;text-align:right;min-width:36px;font-weight:600}.omni-tooltip-wrap{display:inline-flex;position:relative}.omni-tooltip-box{background:var(--color-bg-surface2);border:.5px solid var(--color-border);border-radius:var(--radius-sm);font-size:var(--font-size-xs);color:var(--color-text);white-space:nowrap;pointer-events:none;opacity:0;z-index:100;font-family:var(--font-sans);padding:5px 9px;transition:opacity .15s;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%)}.omni-tooltip-wrap:hover .omni-tooltip-box{opacity:1}.omni-select-group{flex-direction:column;gap:4px;display:flex}.omni-select-label{font-size:var(--font-size-sm);color:var(--color-text-secondary);font-weight:600}.omni-select{background:var(--color-bg-surface);border:.5px solid var(--color-border);border-radius:var(--radius-sm);color:var(--color-text);font-size:var(--font-size-base);font-family:var(--font-sans);cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-position:right 10px center;background-repeat:no-repeat;outline:none;padding:7px 30px 7px 10px;transition:border-color .15s}.omni-select:focus{border-color:var(--color-accent)}.omni-select:disabled{opacity:.5;cursor:not-allowed} /*$vite$:1*/ \ No newline at end of file diff --git a/packages/omnibioai-ui/src/components/Table/Table.css b/packages/omnibioai-ui/src/components/Table/Table.css index cc63f150..dd1f3f2c 100644 --- a/packages/omnibioai-ui/src/components/Table/Table.css +++ b/packages/omnibioai-ui/src/components/Table/Table.css @@ -2,6 +2,13 @@ border: 0.5px solid var(--color-border); border-radius: var(--radius-lg); overflow: hidden; + /* Narrow viewports: scroll the table horizontally inside its own box + instead of overflowing the page (the wrap's own rounded corners / + border still clip vertically via `overflow: hidden` above — this + just adds a scroll axis, it doesn't change desktop behavior since + content that already fits never triggers a scrollbar). */ + overflow-x: auto; + -webkit-overflow-scrolling: touch; } table.omni-table { diff --git a/src/index.css b/src/index.css index 7fa0ac36..2b5265fb 100644 --- a/src/index.css +++ b/src/index.css @@ -136,6 +136,91 @@ a:focus:not(:focus-visible) { } } +/* Verified at 390/428px: 2 tiles/row leaves ~175px per tile, which wraps + the longer plugin names (e.g. "Multi-Agent Orchestrator") to 3 lines — + still readable, but 1 column reads noticeably better on the narrowest + phones. Scoped tighter than the 600px rule above so nothing changes + between 421-600px. */ +@media (max-width: 420px) { + .app-grid { grid-template-columns: 1fr; } +} + +@media (max-width: 767px) { + .workbench-header-actions button { min-height: 44px; } +} + +/* ── Generic: horizontal table scroll instead of page overflow ── */ +/* Used by raw elements that don't go through the shared + @omnibioai/ui
component (which got its own overflow-x:auto on + .omni-table-wrap in a separate commit). */ +.studio-table-scroll { overflow-x: auto; } + +/* ── Jobs page: fixed N-col grids collapse below 768px ── */ +/* !important is needed here (not elsewhere in this stylesheet) because + these specific grid-template-columns values are set inline in Jobs.jsx + and some are computed from JS state (e.g. selected ? "1fr 1fr" : "1fr"), + so a plain class rule of lower precedence can't win against them. */ +@media (max-width: 767px) { + .jobs-submit-grid { grid-template-columns: 1fr !important; } + .jobs-stats-grid { grid-template-columns: repeat(2, 1fr) !important; } + .jobs-main-grid { grid-template-columns: 1fr !important; } + .jobs-info-grid { grid-template-columns: 1fr !important; } +} +@media (max-width: 400px) { + .jobs-stats-grid { grid-template-columns: 1fr !important; } +} + +/* ── Config/wizard pages: fixed N-col panel grids collapse to 1 column ── */ +/* Settings, LLM (x2), Cloud, HPC (x2), Launch's Summary+Logs, IdeServices — + same "several Nfr panels side by side" shape, all readable as a single + stacked column below 768px. !important for the same inline-style-override + reason as the Jobs rules above. */ +@media (max-width: 767px) { + .studio-grid-1 { grid-template-columns: 1fr !important; } +} + +/* Mode's runtime-mode selector cards (title + 2-line description) — needs + more room per card than a status tile, so drop to 2 columns first, then + 1 on the narrowest phones. */ +@media (max-width: 767px) { + .studio-mode-grid { grid-template-columns: repeat(2, 1fr) !important; } +} +@media (max-width: 480px) { + .studio-mode-grid { grid-template-columns: 1fr !important; } +} + +/* HealthCard status-tile rows (Mode + Launch) — compact enough to stay + multi-column on mobile, just fewer of them. */ +@media (max-width: 767px) { + .studio-health-grid { grid-template-columns: repeat(3, 1fr) !important; } +} +@media (max-width: 480px) { + .studio-health-grid { grid-template-columns: repeat(2, 1fr) !important; } +} + +/* Shared UI.jsx Input/Select/Btn — 44px touch target on mobile only. + min-height (not height) so multi-line/larger variants aren't clamped + down; a plain min-height addition never shrinks anything, so this is + safe to apply uniformly across every page that imports these. */ +@media (max-width: 767px) { + .studio-field { min-height: 44px; } +} + +/* ── Logs page: fixed-px row grid (60px 80px 80px 1fr) degrades badly on + narrow screens — three fixed columns eat ~220px regardless of viewport, + leaving the message column crushed. Below 640px, keep time/level/source + on one compact auto-sized row and drop the message to its own full-width + row underneath (grid auto-placement via the message span's 1/-1). ── */ +@media (max-width: 640px) { + .studio-log-row { + grid-template-columns: auto auto 1fr !important; + row-gap: 3px; + } + .studio-log-row > span:nth-child(4) { + grid-column: 1 / -1; + } +} + .toggle { width: 32px; height: 18px; @@ -160,3 +245,50 @@ a:focus:not(:focus-visible) { } .toggle.on::after { left: 16px; } .toggle.off::after { left: 2px; } + +/* ── App shell: mobile nav (additive — desktop unaffected above 767px) ── */ +/* Desktop's wrapper already sets width inline (200 or 0 when a + service is open); below the breakpoint MobileNav (drawer) takes over as + the only nav surface, so the inline sidebar rail is force-collapsed. */ +@media (max-width: 767px) { + .studio-sidebar-wrap { + width: 0 !important; + } +} + +/* Hamburger trigger — hidden by default (desktop), shown only under the + breakpoint. Mirrors Tailwind's `md:hidden` without pulling Tailwind into + a file that otherwise uses none of it. */ +.studio-hamburger { + display: none; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + min-width: 44px; + min-height: 44px; + border-radius: var(--radius-sm); + background: var(--bg3); + border: 1px solid var(--border2); + color: var(--text); + cursor: pointer; + flex-shrink: 0; + font-size: 16px; + padding: 0; +} +@media (max-width: 767px) { + .studio-hamburger { display: inline-flex; } +} + +/* ── Login: mobile touch-target + card padding polish ── */ +/* Base (desktop) value matches Login.jsx's previous inline 2.5rem exactly; + pulled into a class only so the mobile media query below has something + to override without an !important fight against the inline style. */ +.studio-login-card { padding: 2.5rem; } +@media (max-width: 480px) { + .studio-login-card { padding: 1.75rem 1.25rem; } +} +@media (max-width: 767px) { + .studio-login-btn, + .studio-login-tab { min-height: 44px; } +} diff --git a/src/ui/App.jsx b/src/ui/App.jsx index 7bd5a0f3..7552af70 100644 --- a/src/ui/App.jsx +++ b/src/ui/App.jsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react"; import LicenseGate from "./components/LicenseGate"; import BugReport from "./components/BugReport"; import Sidebar from "./components/Sidebar"; +import MobileNav from "./components/MobileNav"; import UpdateBanner from "./components/UpdateBanner"; import Mode from "./pages/Mode"; import LLM from "./pages/LLM"; @@ -93,6 +94,7 @@ export default function App() { const [authChecked, setAuthChecked] = useState(false); // has the initial session check resolved? (web only) const [oauthNotice, setOauthNotice] = useState(null); // result of an OAuth redirect: link_required | error const [returnTo, setReturnTo] = useState(() => getSafeReturnTo()); // ?return_to= target, captured once + const [mobileNavOpen, setMobileNavOpen] = useState(false); // MobileNav drawer — mobile only, no desktop effect // ─── Load saved config + first-run detection ─────────── useEffect(() => { @@ -255,8 +257,10 @@ export default function App() { background:"var(--bg)", color:"var(--text)", fontFamily:"var(--font)", overflow:"hidden", }}> - {/* Sidebar — auto-hides when inside a service/app view */} -
+ {/* Mobile-only nav trigger — hidden on desktop via .studio-hamburger (index.css) */} + +
)}
+ + {/* MobileNav drawer — position:fixed, so it's out of the flex flow + entirely; renders identically on desktop (just permanently closed + since the hamburger that opens it is display:none there). */} + setMobileNavOpen(false)} + />
)} diff --git a/src/ui/components/GrafanaViewer/GrafanaViewer.jsx b/src/ui/components/GrafanaViewer/GrafanaViewer.jsx index 1d7826cf..4521d114 100644 --- a/src/ui/components/GrafanaViewer/GrafanaViewer.jsx +++ b/src/ui/components/GrafanaViewer/GrafanaViewer.jsx @@ -154,8 +154,9 @@ export function GrafanaViewer({ onBack, label }) {
-
+
📈
diff --git a/src/ui/components/Login.jsx b/src/ui/components/Login.jsx index 47977a3f..b2546105 100644 --- a/src/ui/components/Login.jsx +++ b/src/ui/components/Login.jsx @@ -147,11 +147,11 @@ export default function Login({ title, description }) {
@@ -209,6 +209,7 @@ export default function Login({ title, description }) { +
+ + {/* Nav items — same sections/items/active-state as desktop Sidebar */} + + + {/* Signed-in user + sign out — mirrors desktop Sidebar */} + {currentUser && ( +
+
+ {currentUser.email} +
+
{ logout(); onClose(); }} + style={{ + minHeight: 44, display: "flex", alignItems: "center", + fontSize: "var(--font-size-xs)", fontFamily: "var(--mono)", fontWeight: 600, + color: "var(--color-text-muted)", cursor: "pointer", flexShrink: 0, + }} + > + Sign out +
+
+ )} +
+ + ); +} diff --git a/src/ui/components/OAuthLinkConfirm.jsx b/src/ui/components/OAuthLinkConfirm.jsx index 900af489..c0438526 100644 --- a/src/ui/components/OAuthLinkConfirm.jsx +++ b/src/ui/components/OAuthLinkConfirm.jsx @@ -27,8 +27,9 @@ export default function OAuthLinkConfirm({ linkToken, provider, email, onDone, o
-
+
An account already exists for {email}. diff --git a/src/ui/components/RequirePermission.jsx b/src/ui/components/RequirePermission.jsx index cd2f8a8f..c116e985 100644 --- a/src/ui/components/RequirePermission.jsx +++ b/src/ui/components/RequirePermission.jsx @@ -34,8 +34,8 @@ export default function RequirePermission({ currentUser, permission, description function AccessDenied({ email, permission }) { return ( -
-
+
+
🔒
Access denied
diff --git a/src/ui/components/UI.jsx b/src/ui/components/UI.jsx index da13e2cf..02eaeaea 100644 --- a/src/ui/components/UI.jsx +++ b/src/ui/components/UI.jsx @@ -76,6 +76,7 @@ export function Input({ type = "text", placeholder, value, onChange }) { placeholder={placeholder} value={value} onChange={onChange} + className="studio-field" onFocus={(e) => (e.target.style.borderColor = "rgba(0,229,160,0.4)")} onBlur={(e) => (e.target.style.borderColor = "var(--border2)")} style={{ @@ -124,6 +125,7 @@ export function Select({ value, onChange, options }) {
-
+
{IDE_SERVICES.map(svc => ( -
+
@@ -378,6 +379,7 @@ export default function Jobs() { ))}
+ )} @@ -467,7 +469,7 @@ export default function Jobs() { {/* Servers & Tools info */} -
+
{/* Servers */}
No servers configured
) : ( +
@@ -508,6 +511,7 @@ export default function Jobs() { ))}
+
)}
diff --git a/src/ui/pages/LLM.jsx b/src/ui/pages/LLM.jsx index 5d7a3026..6ac4f09d 100644 --- a/src/ui/pages/LLM.jsx +++ b/src/ui/pages/LLM.jsx @@ -36,7 +36,7 @@ function LLMConsole({ config, setConfig }) {
-
+
{/* Ollama */} @@ -117,7 +117,7 @@ function LLMConsole({ config, setConfig }) {
-
+
{/* OpenAI */} diff --git a/src/ui/pages/Launch.jsx b/src/ui/pages/Launch.jsx index 8832efa9..800e4daf 100644 --- a/src/ui/pages/Launch.jsx +++ b/src/ui/pages/Launch.jsx @@ -227,12 +227,12 @@ export default function Launch({ config, onStatusChange }) {
{/* Health Cards */} -
+
{SERVICES.map(s => )}
{/* Summary + Logs */} -
+
diff --git a/src/ui/pages/Logs.jsx b/src/ui/pages/Logs.jsx index cb6d6d84..1c840f7c 100644 --- a/src/ui/pages/Logs.jsx +++ b/src/ui/pages/Logs.jsx @@ -140,7 +140,7 @@ export default function Logs() {
)} {filtered.map((l, i) => ( -
-
+
{HEALTH.map((h) => ( ))} diff --git a/src/ui/pages/RoleManagement.jsx b/src/ui/pages/RoleManagement.jsx index 1c33e956..8af5a436 100644 --- a/src/ui/pages/RoleManagement.jsx +++ b/src/ui/pages/RoleManagement.jsx @@ -18,8 +18,8 @@ export default function RoleManagement({ currentUser }) { function AccessDenied({ email }) { return ( -
-
+
+
🔒
Access denied
diff --git a/src/ui/pages/Services.jsx b/src/ui/pages/Services.jsx index 33471ffe..5ada4d5c 100644 --- a/src/ui/pages/Services.jsx +++ b/src/ui/pages/Services.jsx @@ -323,6 +323,7 @@ function ServicesConsole({ config }) { iconColor={GROUP_COLORS[group] || "teal"} /> +
@@ -463,6 +464,7 @@ function ServicesConsole({ config }) { })}
+
))} diff --git a/src/ui/pages/Settings.jsx b/src/ui/pages/Settings.jsx index fc8ca0fd..c11628e5 100644 --- a/src/ui/pages/Settings.jsx +++ b/src/ui/pages/Settings.jsx @@ -207,7 +207,7 @@ function SettingsConsole({ config, setConfig }) { -
+
{/* General */}