Dashboard: UI polish (typography, palette, modal cleanup)

- Switch UI font to Inter, keep monospace only for code/logs/paths/
  identifiers (loaded via Google Fonts; system fallbacks)
- Refine the light/dark palette with softer borders, clearer text
  hierarchy, and a more nuanced shadow/elevation system; introduce
  --bg-elevated, --border-strong, --shadow-elevated tokens; retain the
  orange accent
- Introduce a consistent spacing scale (--space-1..6) and apply it across
  sections, modals, and controls
- Extract shared modal CSS classes (.modal-info, .modal-hint,
  .modal-prompt, .modal-field, .modal-input, .modal-select,
  .modal-textarea, .modal-actions, .btn-secondary) and strip duplicated
  inline styles from all seven modals; add an accent-colored focus ring
  for inputs/textareas and a subtle backdrop blur
- Replace the native <select> in Add Language with a filterable combobox:
  type to filter, keyboard navigation (Up/Down/Enter/Esc), substring
  highlight, click-outside to close; the typed value is validated against
  the available languages list before submission
- Update CHANGELOG accordingly

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Dominik Jain <dominik.jain@oraios-ai.de>
This commit is contained in:
authored and Dr. Dominik Jain committed 2026-05-12 16:52:13 +02:00
1 parent 2e05602895
commit d3454bc39e
4 files changed
+576 -165

No files matched your search

+5
View File
@@ -2,6 +2,11 @@
Status of the `main` branch. Changes prior to the next official version change will appear here. Status of the `main` branch. Changes prior to the next official version change will appear here.
* Dashboard:
- UI polish: switch UI font to Inter (with system fallbacks) and use JetBrains Mono only for code/logs/paths/identifiers; refine the light/dark palette with softer borders, clearer text hierarchy, and a more nuanced shadow/elevation system; introduce a consistent spacing scale; keep the orange accent.
- Modal markup cleanup: extract shared CSS classes (`.modal-info`, `.modal-hint`, `.modal-prompt`, `.modal-field`, `.modal-input`, `.modal-select`, `.modal-textarea`, `.modal-actions`, `.btn-secondary`) and remove duplicated inline styles from all seven modals. Inputs and textareas get an accent-colored focus ring; the modal backdrop has a subtle blur.
- Add Language: replace the native `<select>` with a filterable combobox — type to filter, keyboard navigation (Up/Down/Enter/Esc), substring highlight, click-outside to close. The typed value is validated against the available languages list before submission.
# v1.3.0 (2026-05-11) # v1.3.0 (2026-05-11)
* General: * General:
+366 -85
View File
@@ -3,64 +3,91 @@ html {
} }
:root { :root {
/* Typography */
--font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
/* Spacing scale */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
/* Light theme variables */ /* Light theme variables */
--bg-primary: #f5f5f5; --bg-primary: #f5f5f5;
--bg-secondary: #ffffff; --bg-secondary: #ffffff;
--text-primary: #000000; --bg-elevated: #ffffff;
--text-secondary: #333333; --text-primary: #1f2328;
--text-muted: #666666; --text-secondary: #3f4754;
--border-color: #ddd; --text-muted: #6a737d;
--border-color: #e3e6ea;
--border-strong: #d0d7de;
--btn-primary: #eaa45d; --btn-primary: #eaa45d;
--btn-hover: #dca662; --btn-hover: #dca662;
--btn-disabled: #6c757d; --btn-disabled: #adb5bd;
--shadow: 0 2px 4px rgba(0, 0, 0, 0.1); --btn-secondary-bg: #f0f2f5;
--tool-highlight: #ffff00; --btn-secondary-hover: #e3e6ea;
--tool-highlight-text: #000000; --shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.06);
--log-debug: #808080; --shadow-elevated: 0 4px 12px rgba(15, 23, 42, 0.08);
--log-info: #000000; --tool-highlight: #fff3bf;
--log-warning: #FF8C00; --tool-highlight-text: #1f2328;
--log-error: #FF0000; --log-debug: #8b95a1;
--stats-header: #f8f9fa; --log-info: #1f2328;
--log-warning: #d97706;
--log-error: #dc2626;
--stats-header: #f0f2f5;
--header-height: 150px; --header-height: 150px;
--header-padding: 20px; --header-padding: var(--space-5);
--header-gap-main: 25px; --header-gap-main: var(--space-5);
--frame-padding: 25px; --frame-padding: var(--space-5);
--border-radius: 5px; --border-radius: 6px;
--border-radius-sm: 4px;
} }
[data-theme="dark"] { [data-theme="dark"] {
/* Dark theme variables */ /* Dark theme variables */
--bg-primary: #1a1a1a; --bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d; --bg-secondary: #2d2d2d;
--text-primary: #ffffff; --bg-elevated: #262b32;
--text-secondary: #e0e0e0; --text-primary: #e6edf3;
--text-muted: #b0b0b0; --text-secondary: #c9d1d9;
--border-color: #444; --text-muted: #8b95a1;
--border-color: #2d333b;
--border-strong: #3d444d;
--btn-primary: #eaa45d; --btn-primary: #eaa45d;
--btn-hover: #dca662; --btn-hover: #dca662;
--btn-disabled: #6c757d; --btn-disabled: #4a5159;
--shadow: 0 2px 4px rgba(0, 0, 0, 0.3); --btn-secondary-bg: #2d333b;
--tool-highlight: #ffd700; --btn-secondary-hover: #3d444d;
--tool-highlight-text: #000000; --shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.35);
--log-debug: #808080; --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.45);
--log-info: #ffffff; --tool-highlight: #f6c948;
--log-warning: #FF8C00; --tool-highlight-text: #15181c;
--log-error: #FF0000; --log-debug: #8b95a1;
--stats-header: #3a3a3a; --log-info: #e6edf3;
--log-warning: #f59e0b;
--log-error: #f87171;
--stats-header: #262b32;
} }
.news-section { .news-section {
background: var(--bg-secondary); background: var(--bg-secondary);
padding: 20px; border: 1px solid var(--border-color);
padding: var(--space-5);
border-radius: var(--border-radius); border-radius: var(--border-radius);
box-shadow: var(--shadow); box-shadow: var(--shadow);
margin-bottom: 25px; margin-bottom: var(--space-4);
} }
.news-section h2 { .news-section h2 {
margin: 0 0 20px 0; margin: 0 0 var(--space-4) 0;
font-size: 18px; font-size: 15px;
color: var(--text-primary); font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
} }
.news-item { .news-item {
@@ -72,6 +99,11 @@ html {
position: relative; position: relative;
} }
.news-item a {
color: var(--text-muted);
text-decoration: underline;
}
.news-item:last-child { .news-item:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@@ -144,13 +176,35 @@ html {
} }
body { body {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-family: var(--font-sans);
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0; margin: 0;
background-color: var(--bg-primary); background-color: var(--bg-primary);
color: var(--text-primary); color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease; transition: background-color 0.3s ease, color 0.3s ease;
} }
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
letter-spacing: -0.005em;
}
code, pre, kbd, samp,
.log-container,
.memory-editor,
.project-path,
.tool-item,
.stat-tool-name,
.execution-name,
.execution-meta,
.last-execution-name,
.config-value-mono {
font-family: var(--font-mono);
}
#frame { #frame {
max-width: 1600px; max-width: 1600px;
margin: 0 auto; margin: 0 auto;
@@ -260,18 +314,20 @@ body {
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
color: var(--text-primary); color: var(--text-primary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 8px 16px; padding: 8px 14px;
border-radius: 4px; border-radius: var(--border-radius-sm);
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 14px;
transition: background-color 0.3s ease, border-color 0.3s ease; font-family: inherit;
transition: background-color 0.2s ease, border-color 0.2s ease;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: var(--space-2);
} }
.menu-button:hover { .menu-button:hover {
background-color: var(--border-color); background-color: var(--btn-secondary-hover);
border-color: var(--border-strong);
} }
.menu-dropdown { .menu-dropdown {
@@ -412,7 +468,7 @@ body {
.overview-container { .overview-container {
display: grid; display: grid;
grid-template-columns: 1fr 400px; grid-template-columns: 1fr 400px;
gap: 20px; gap: var(--space-5);
} }
.overview-left { .overview-left {
@@ -430,23 +486,30 @@ body {
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: var(--border-radius); border-radius: var(--border-radius);
padding: 20px; padding: var(--space-5);
margin-bottom: 20px; margin-bottom: var(--space-4);
transition: background-color 0.3s ease, border-color 0.3s ease; transition: background-color 0.3s ease, border-color 0.3s ease;
} }
.config-section h2, .config-section h2,
.basic-stats-section h2 { .basic-stats-section h2 {
margin-top: 0; margin: 0 0 var(--space-4) 0;
color: var(--text-secondary); font-size: 15px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
} }
/* Collapsible Headers */ /* Collapsible Headers */
.collapsible-header { .collapsible-header {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
font-size: 18px; font-size: 15px;
color: var(--text-secondary); font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
display: flex; display: flex;
@@ -476,13 +539,17 @@ body {
.config-grid { .config-grid {
display: grid; display: grid;
grid-template-columns: 180px 1fr; grid-template-columns: 180px 1fr;
gap: 12px; gap: var(--space-3);
margin-bottom: 20px; margin-bottom: var(--space-4);
} }
.config-label { .config-label {
font-weight: bold; font-weight: 500;
color: var(--text-secondary); color: var(--text-muted);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.03em;
padding-top: 2px;
} }
.config-value { .config-value {
@@ -587,30 +654,29 @@ body {
.basic-stats-section { .basic-stats-section {
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 5px; border-radius: var(--border-radius);
padding: 20px; padding: var(--space-5);
margin-bottom: 20px; margin-bottom: var(--space-4);
transition: background-color 0.3s ease, border-color 0.3s ease; transition: background-color 0.3s ease, border-color 0.3s ease;
} }
.basic-stats-section h2 {
margin-top: 0;
color: var(--text-secondary);
}
/* Executions Styles */ /* Executions Styles */
.executions-section { .executions-section {
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 5px; border-radius: var(--border-radius);
padding: 20px; padding: var(--space-5);
margin-bottom: 20px; margin-bottom: var(--space-4);
transition: background-color 0.3s ease, border-color 0.3s ease; transition: background-color 0.3s ease, border-color 0.3s ease;
} }
.executions-section h2 { .executions-section h2 {
margin-top: 0; margin: 0 0 var(--space-4) 0;
color: var(--text-secondary); font-size: 15px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
} }
.execution-list { .execution-list {
@@ -864,40 +930,49 @@ body {
.btn { .btn {
background-color: var(--btn-primary); background-color: var(--btn-primary);
color: white; color: #fff;
border: none; border: none;
padding: 8px 16px; padding: 8px 16px;
border-radius: 4px; border-radius: var(--border-radius-sm);
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
transition: background-color 0.3s ease; font-weight: 500;
font-family: inherit;
transition: background-color 0.2s ease, transform 0.05s ease;
} }
.btn:hover { .btn:hover {
background-color: var(--btn-hover); background-color: var(--btn-hover);
} }
.btn:active {
transform: translateY(1px);
}
.btn:disabled { .btn:disabled {
background-color: var(--btn-disabled); background-color: var(--btn-disabled);
cursor: not-allowed; cursor: not-allowed;
transform: none;
} }
.theme-toggle { .theme-toggle {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: var(--space-2);
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
color: var(--text-primary); color: var(--text-primary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 4px; border-radius: var(--border-radius-sm);
padding: 8px 16px; padding: 8px 14px;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 14px;
transition: background-color 0.3s ease, border-color 0.3s ease; font-family: inherit;
transition: background-color 0.2s ease, border-color 0.2s ease;
} }
.theme-toggle:hover { .theme-toggle:hover {
background-color: var(--border-color); background-color: var(--btn-secondary-hover);
border-color: var(--border-strong);
} }
.theme-toggle span { .theme-toggle span {
@@ -1084,27 +1159,28 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(15, 23, 42, 0.55);
backdrop-filter: blur(2px);
} }
.modal-content { .modal-content {
background-color: var(--bg-primary); background-color: var(--bg-elevated);
margin: 10% auto; margin: 8vh auto;
padding: 25px; padding: var(--space-5);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 8px; border-radius: var(--border-radius);
width: 90%; width: 90%;
max-width: 500px; max-width: 500px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); box-shadow: var(--shadow-elevated);
position: relative; position: relative;
} }
.modal-close { .modal-close {
color: var(--text-muted); color: var(--text-muted);
float: right; float: right;
font-size: 28px; font-size: 24px;
font-weight: bold; font-weight: 400;
line-height: 20px; line-height: 1;
cursor: pointer; cursor: pointer;
transition: color 0.2s; transition: color 0.2s;
} }
@@ -1116,8 +1192,213 @@ body {
.modal h3 { .modal h3 {
margin-top: 0; margin-top: 0;
margin-bottom: 15px; margin-bottom: var(--space-3);
color: var(--text-primary); color: var(--text-primary);
font-size: 17px;
font-weight: 600;
}
/* Shared modal pieces (replace inline styles) */
.modal-info {
margin: var(--space-3) 0;
color: var(--text-primary);
line-height: 1.5;
}
.modal-hint {
margin: var(--space-3) 0;
color: var(--text-muted);
font-size: 13px;
line-height: 1.5;
}
.modal-field {
margin: var(--space-4) 0;
}
.modal-field label {
display: block;
margin-bottom: var(--space-2);
font-weight: 500;
color: var(--text-primary);
}
.modal-input,
.modal-select {
box-sizing: border-box;
width: 100%;
padding: 8px 10px;
border-radius: var(--border-radius-sm);
border: 1px solid var(--border-strong);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
font-family: var(--font-sans);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.modal-input:focus,
.modal-select:focus {
outline: none;
border-color: var(--btn-primary);
box-shadow: 0 0 0 3px rgba(234, 164, 93, 0.18);
}
.modal-textarea {
box-sizing: border-box;
width: 100%;
padding: var(--space-3);
border: 1px solid var(--border-strong);
border-radius: var(--border-radius-sm);
background: var(--bg-primary);
color: var(--text-primary);
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.5;
resize: vertical;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.modal-textarea:focus {
outline: none;
border-color: var(--btn-primary);
box-shadow: 0 0 0 3px rgba(234, 164, 93, 0.18);
}
.modal-actions {
display: flex;
gap: var(--space-2);
justify-content: flex-end;
margin-top: var(--space-4);
}
.modal-actions .btn {
padding: 8px 16px;
}
.modal-prompt {
margin: var(--space-4) 0;
color: var(--text-primary);
line-height: 1.5;
font-size: 15px;
}
.btn-secondary {
background-color: var(--btn-secondary-bg);
color: var(--text-primary);
}
.btn-secondary:hover {
background-color: var(--btn-secondary-hover);
}
.modal-title-with-meta {
margin-bottom: var(--space-4);
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
}
/* Filterable combobox (used in Add Language modal) */
.combobox {
position: relative;
}
.combobox-input {
padding-right: 28px;
}
.combobox-caret {
position: absolute;
top: 50%;
right: var(--space-3);
transform: translateY(-50%);
color: var(--text-muted);
font-size: 12px;
pointer-events: none;
transition: transform 0.15s ease, color 0.15s ease;
}
.combobox.open .combobox-caret {
color: var(--text-primary);
transform: translateY(-50%) rotate(180deg);
}
.combobox-options {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
margin: 0;
padding: var(--space-1) 0;
list-style: none;
background: var(--bg-elevated);
border: 1px solid var(--border-strong);
border-radius: var(--border-radius-sm);
box-shadow: var(--shadow-elevated);
max-height: 240px;
overflow-y: auto;
z-index: 20;
}
.combobox-options li {
padding: 6px var(--space-3);
color: var(--text-primary);
cursor: pointer;
font-size: 14px;
line-height: 1.4;
transition: background-color 0.1s ease;
}
.combobox-options li.no-match {
color: var(--text-muted);
font-style: italic;
cursor: default;
}
.combobox-options li:hover:not(.no-match),
.combobox-options li.highlighted:not(.no-match) {
background-color: var(--btn-secondary-hover);
}
.combobox-options li.highlighted:not(.no-match) {
color: var(--text-primary);
}
.combobox-options li mark {
background: transparent;
color: var(--btn-primary);
font-weight: 600;
}
.combobox-empty {
margin-top: var(--space-2);
color: var(--text-muted);
font-style: italic;
font-size: 13px;
}
.section-header-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.section-header-row h2 {
margin: 0 0 var(--space-4) 0;
}
.stats-summary-block {
margin-bottom: var(--space-5);
text-align: center;
}
.estimator-name {
text-align: center;
margin-bottom: var(--space-3);
color: var(--text-muted);
font-size: 13px;
} }
/* Language Badge Styles */ /* Language Badge Styles */
+151 -14
View File
@@ -296,6 +296,10 @@ class Dashboard {
this.$availableContextsDisplay = $('#available-contexts-display'); this.$availableContextsDisplay = $('#available-contexts-display');
this.$addLanguageModal = $('#add-language-modal'); this.$addLanguageModal = $('#add-language-modal');
this.$modalLanguageSelect = $('#modal-language-select'); this.$modalLanguageSelect = $('#modal-language-select');
this.$modalLanguageCombobox = $('#modal-language-combobox');
this.$modalLanguageOptions = $('#modal-language-options');
this.$modalLanguageEmpty = $('#modal-language-empty');
this.modalLanguageAvailable = [];
this.$modalProjectName = $('#modal-project-name'); this.$modalProjectName = $('#modal-project-name');
this.$modalAddBtn = $('#modal-add-btn'); this.$modalAddBtn = $('#modal-add-btn');
this.$modalCancelBtn = $('#modal-cancel-btn'); this.$modalCancelBtn = $('#modal-cancel-btn');
@@ -360,6 +364,7 @@ class Dashboard {
this.$modalAddBtn.click(this.addLanguageFromModal.bind(this)); this.$modalAddBtn.click(this.addLanguageFromModal.bind(this));
this.$modalCancelBtn.click(this.closeLanguageModal.bind(this)); this.$modalCancelBtn.click(this.closeLanguageModal.bind(this));
this.$modalClose.click(this.closeLanguageModal.bind(this)); this.$modalClose.click(this.closeLanguageModal.bind(this));
this.bindLanguageCombobox();
this.$removeModalOkBtn.click(this.confirmRemoveLanguageOk.bind(this)); this.$removeModalOkBtn.click(this.confirmRemoveLanguageOk.bind(this));
this.$removeModalCancelBtn.click(this.closeRemoveLanguageModal.bind(this)); this.$removeModalCancelBtn.click(this.closeRemoveLanguageModal.bind(this));
this.$modalCloseRemove.click(this.closeRemoveLanguageModal.bind(this)); this.$modalCloseRemove.click(this.closeRemoveLanguageModal.bind(this));
@@ -843,7 +848,7 @@ class Dashboard {
let html = ''; let html = '';
tools.forEach(function (tool) { tools.forEach(function (tool) {
html += '<div class="info-item" title="' + tool.name + '">' + tool.name + '</div>'; html += '<div class="info-item" title="' + tool.name + '"><code>' + tool.name + '</code></div>';
}); });
this.$availableToolsDisplay.html(html); this.$availableToolsDisplay.html(html);
@@ -1730,7 +1735,10 @@ class Dashboard {
closeLanguageModal() { closeLanguageModal() {
this.$addLanguageModal.fadeOut(200); this.$addLanguageModal.fadeOut(200);
this.$modalLanguageSelect.empty(); this.closeLanguageDropdown();
this.$modalLanguageSelect.val('');
this.$modalLanguageOptions.empty();
this.modalLanguageAvailable = [];
this.$modalAddBtn.prop('disabled', false).text('Add Language'); this.$modalAddBtn.prop('disabled', false).text('Add Language');
} }
@@ -1738,19 +1746,19 @@ class Dashboard {
let self = this; let self = this;
$.ajax({ $.ajax({
url: '/get_available_languages', type: 'GET', success: function (response) { url: '/get_available_languages', type: 'GET', success: function (response) {
const languages = response.languages || []; const languages = (response.languages || []).slice().sort();
// Clear all existing options self.modalLanguageAvailable = languages;
self.$modalLanguageSelect.empty(); self.$modalLanguageSelect.val('');
if (languages.length === 0) { if (languages.length === 0) {
// Show message if no languages available self.$modalLanguageOptions.empty().hide();
self.$modalLanguageSelect.append($('<option>').val('').text('No languages available to add')); self.$modalLanguageEmpty.show();
self.$modalLanguageSelect.prop('disabled', true).attr('placeholder', 'No languages available');
self.$modalAddBtn.prop('disabled', true); self.$modalAddBtn.prop('disabled', true);
} else { } else {
// Add language options self.$modalLanguageEmpty.hide();
languages.forEach(function (language) { self.$modalLanguageSelect.prop('disabled', false).attr('placeholder', 'Type to filter…');
self.$modalLanguageSelect.append($('<option>').val(language).text(language)); self.renderLanguageOptions('');
});
self.$modalAddBtn.prop('disabled', false); self.$modalAddBtn.prop('disabled', false);
} }
}, error: function (xhr, status, error) { }, error: function (xhr, status, error) {
@@ -1759,12 +1767,141 @@ class Dashboard {
}); });
} }
renderLanguageOptions(filter) {
const self = this;
const needle = (filter || '').toLowerCase();
const matches = this.modalLanguageAvailable.filter(function (lang) {
return lang.toLowerCase().includes(needle);
});
this.$modalLanguageOptions.empty();
if (matches.length === 0) {
this.$modalLanguageOptions.append(
$('<li class="no-match"></li>').text('No matches')
);
} else {
matches.forEach(function (lang, idx) {
let label = lang;
if (needle) {
const i = lang.toLowerCase().indexOf(needle);
if (i !== -1) {
const before = lang.slice(0, i);
const hit = lang.slice(i, i + needle.length);
const after = lang.slice(i + needle.length);
label = $('<span></span>')
.append(document.createTextNode(before))
.append($('<mark></mark>').text(hit))
.append(document.createTextNode(after))
.html();
}
}
const $li = $('<li role="option"></li>')
.attr('data-value', lang)
.html(label);
if (idx === 0) {
$li.addClass('highlighted');
}
$li.on('mousedown', function (e) {
e.preventDefault(); // keep input focus
self.selectLanguage(lang);
});
self.$modalLanguageOptions.append($li);
});
}
}
openLanguageDropdown() {
if (this.modalLanguageAvailable.length === 0) return;
this.renderLanguageOptions(this.$modalLanguageSelect.val());
this.$modalLanguageOptions.show();
this.$modalLanguageCombobox.addClass('open');
}
closeLanguageDropdown() {
this.$modalLanguageOptions.hide();
this.$modalLanguageCombobox.removeClass('open');
}
selectLanguage(lang) {
this.$modalLanguageSelect.val(lang);
this.closeLanguageDropdown();
}
moveLanguageHighlight(delta) {
const $items = this.$modalLanguageOptions.find('li:not(.no-match)');
if ($items.length === 0) return;
let idx = $items.index($items.filter('.highlighted'));
idx = (idx + delta + $items.length) % $items.length;
$items.removeClass('highlighted');
const $target = $items.eq(idx).addClass('highlighted');
const target = $target[0];
if (target && target.scrollIntoView) {
target.scrollIntoView({ block: 'nearest' });
}
}
bindLanguageCombobox() {
const self = this;
this.$modalLanguageSelect
.off('focus.combobox click.combobox input.combobox keydown.combobox')
.on('focus.combobox click.combobox', function () { self.openLanguageDropdown(); })
.on('input.combobox', function () {
self.renderLanguageOptions(self.$modalLanguageSelect.val());
self.openLanguageDropdown();
})
.on('keydown.combobox', function (e) {
const key = e.key;
if (key === 'ArrowDown') {
e.preventDefault();
if (self.$modalLanguageOptions.is(':visible')) {
self.moveLanguageHighlight(1);
} else {
self.openLanguageDropdown();
}
} else if (key === 'ArrowUp') {
e.preventDefault();
self.moveLanguageHighlight(-1);
} else if (key === 'Enter') {
const $hl = self.$modalLanguageOptions.find('li.highlighted:not(.no-match)');
if ($hl.length) {
e.preventDefault();
self.selectLanguage($hl.attr('data-value'));
}
} else if (key === 'Escape') {
if (self.$modalLanguageOptions.is(':visible')) {
e.stopPropagation(); // don't close the modal
self.closeLanguageDropdown();
}
}
});
this.$modalLanguageCombobox
.off('click.caret')
.on('click.caret', '.combobox-caret', function () {
self.$modalLanguageSelect.trigger('focus');
self.openLanguageDropdown();
});
$(document).off('mousedown.langCombobox').on('mousedown.langCombobox', function (e) {
if (!self.$modalLanguageCombobox.is(':visible')) return;
if (!$.contains(self.$modalLanguageCombobox[0], e.target)) {
self.closeLanguageDropdown();
}
});
}
addLanguageFromModal() { addLanguageFromModal() {
const selectedLanguage = this.$modalLanguageSelect.val(); const typed = (this.$modalLanguageSelect.val() || '').trim();
if (!selectedLanguage) { const matched = this.modalLanguageAvailable.find(function (l) {
alert('No language selected or no languages available to add'); return l.toLowerCase() === typed.toLowerCase();
});
if (!matched) {
alert(typed
? 'Unknown language: ' + typed + '. Pick one from the dropdown.'
: 'No language selected.');
return; return;
} }
const selectedLanguage = matched;
const self = this; const self = this;
+54 -66
View File
@@ -8,6 +8,9 @@
<link rel="icon" type="image/png" sizes="16x16" href="serena-icon-16.png"> <link rel="icon" type="image/png" sizes="16x16" href="serena-icon-16.png">
<link rel="icon" type="image/png" sizes="32x32" href="serena-icon-32.png"> <link rel="icon" type="image/png" sizes="32x32" href="serena-icon-32.png">
<link rel="icon" type="image/png" sizes="48x48" href="serena-icon-48.png"> <link rel="icon" type="image/png" sizes="48x48" href="serena-icon-48.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="dashboard.css"> <link rel="stylesheet" href="dashboard.css">
<script src="jquery.min.js"></script> <script src="jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@@ -69,8 +72,8 @@
</section> </section>
<section class="config-section"> <section class="config-section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <div class="section-header-row">
<h2 style="margin: 0;">Current Configuration</h2> <h2>Current Configuration</h2>
</div> </div>
<div id="config-display"> <div id="config-display">
<div class="loading">Loading configuration...</div> <div class="loading">Loading configuration...</div>
@@ -201,9 +204,9 @@
<button id="clear-stats" class="btn">Clear Stats</button> <button id="clear-stats" class="btn">Clear Stats</button>
</div> </div>
<div id="stats-summary" style="margin-bottom:20px; text-align:center;"></div> <div id="stats-summary" class="stats-summary-block"></div>
<div id="estimator-name" style="text-align:center; margin-bottom:10px;"></div> <div id="estimator-name" class="estimator-name"></div>
<div id="no-stats-message" style="text-align:center; color:var(--text-muted); font-style:italic; display:none;"> <div id="no-stats-message" class="no-stats-message" style="display:none;">
No tool stats collected yet. No tool stats collected yet.
</div> </div>
@@ -232,25 +235,26 @@
<div class="modal-content"> <div class="modal-content">
<span class="modal-close">&times;</span> <span class="modal-close">&times;</span>
<h3>Add Language</h3> <h3>Add Language</h3>
<p id="modal-project-info" style="margin: 15px 0; color: var(--text-primary); line-height: 1.5;"> <p id="modal-project-info" class="modal-info">
Adding a language to serena config of project <strong id="modal-project-name"></strong>. Adding a language to serena config of project <strong id="modal-project-name"></strong>.
</p> </p>
<p style="margin: 15px 0; color: var(--text-muted); font-size: 13px; line-height: 1.5;"> <p class="modal-hint">
Note that this may download some dependencies needed for the language server and then start it, Note that this may download some dependencies needed for the language server and then start it,
it may take a few seconds before the LS is responsive. it may take a few seconds before the LS is responsive.
</p> </p>
<div style="margin: 20px 0;"> <div class="modal-field">
<label for="modal-language-select" style="display: block; margin-bottom: 8px; font-weight: 500;">Select <label for="modal-language-select">Select Language:</label>
Language:</label> <div class="combobox" id="modal-language-combobox">
<select id="modal-language-select" class="language-select" <input type="text" id="modal-language-select" class="modal-input combobox-input"
style="width: 100%; padding: 8px 10px; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-primary); color: var(--text-primary); font-size: 14px;"> placeholder="Type to filter…" autocomplete="off" spellcheck="false">
</select> <span class="combobox-caret" aria-hidden="true">▾</span>
<ul id="modal-language-options" class="combobox-options" role="listbox" style="display:none"></ul>
<div id="modal-language-empty" class="combobox-empty" style="display:none">No languages available to add</div>
</div>
</div> </div>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <div class="modal-actions">
<button id="modal-cancel-btn" class="btn" <button id="modal-cancel-btn" class="btn btn-secondary">Cancel</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel <button id="modal-add-btn" class="btn">Add Language</button>
</button>
<button id="modal-add-btn" class="btn" style="padding: 8px 16px;">Add Language</button>
</div> </div>
</div> </div>
</div> </div>
@@ -259,14 +263,12 @@
<div id="remove-language-modal" class="modal" style="display:none"> <div id="remove-language-modal" class="modal" style="display:none">
<div class="modal-content"> <div class="modal-content">
<span class="modal-close modal-close-remove">&times;</span> <span class="modal-close modal-close-remove">&times;</span>
<p style="margin: 20px 0; color: var(--text-primary); line-height: 1.5; font-size: 15px;"> <p class="modal-prompt">
Remove language <strong id="remove-language-name"></strong> from configuration? Remove language <strong id="remove-language-name"></strong> from configuration?
</p> </p>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <div class="modal-actions">
<button id="remove-modal-cancel-btn" class="btn" <button id="remove-modal-cancel-btn" class="btn btn-secondary">Cancel</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel <button id="remove-modal-ok-btn" class="btn">OK</button>
</button>
<button id="remove-modal-ok-btn" class="btn" style="padding: 8px 16px;">OK</button>
</div> </div>
</div> </div>
</div> </div>
@@ -275,7 +277,7 @@
<div id="edit-memory-modal" class="modal" style="display:none"> <div id="edit-memory-modal" class="modal" style="display:none">
<div class="modal-content modal-content-large"> <div class="modal-content modal-content-large">
<span class="modal-close modal-close-edit-memory">&times;</span> <span class="modal-close modal-close-edit-memory">&times;</span>
<h3 style="margin-bottom: 20px; display: flex; align-items: center; gap: 8px;"> <h3 class="modal-title-with-meta">
Memory: Memory:
<span id="edit-memory-name" class="memory-name-display"></span> <span id="edit-memory-name" class="memory-name-display"></span>
<span id="edit-memory-rename-btn" class="memory-rename-btn" title="Rename memory"> <span id="edit-memory-rename-btn" class="memory-rename-btn" title="Rename memory">
@@ -286,13 +288,10 @@
</span> </span>
<input type="text" id="edit-memory-rename-input" class="memory-rename-input" style="display: none;"> <input type="text" id="edit-memory-rename-input" class="memory-rename-input" style="display: none;">
</h3> </h3>
<textarea id="edit-memory-content" class="memory-editor" rows="20" <textarea id="edit-memory-content" class="memory-editor modal-textarea" rows="20"></textarea>
style="width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); font-family: 'Courier New', monospace; font-size: 13px; resize: vertical;"></textarea> <div class="modal-actions">
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <button id="edit-memory-cancel-btn" class="btn btn-secondary">Cancel</button>
<button id="edit-memory-cancel-btn" class="btn" <button id="edit-memory-save-btn" class="btn">Save</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel
</button>
<button id="edit-memory-save-btn" class="btn" style="padding: 8px 16px;">Save</button>
</div> </div>
</div> </div>
</div> </div>
@@ -301,14 +300,12 @@
<div id="delete-memory-modal" class="modal" style="display:none"> <div id="delete-memory-modal" class="modal" style="display:none">
<div class="modal-content"> <div class="modal-content">
<span class="modal-close modal-close-delete-memory">&times;</span> <span class="modal-close modal-close-delete-memory">&times;</span>
<p style="margin: 20px 0; color: var(--text-primary); line-height: 1.5; font-size: 15px;"> <p class="modal-prompt">
Delete memory <strong id="delete-memory-name"></strong>? Delete memory <strong id="delete-memory-name"></strong>?
</p> </p>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <div class="modal-actions">
<button id="delete-memory-cancel-btn" class="btn" <button id="delete-memory-cancel-btn" class="btn btn-secondary">Cancel</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel <button id="delete-memory-ok-btn" class="btn">OK</button>
</button>
<button id="delete-memory-ok-btn" class="btn" style="padding: 8px 16px;">OK</button>
</div> </div>
</div> </div>
</div> </div>
@@ -317,26 +314,22 @@
<div id="create-memory-modal" class="modal" style="display:none"> <div id="create-memory-modal" class="modal" style="display:none">
<div class="modal-content"> <div class="modal-content">
<span class="modal-close modal-close-create-memory">&times;</span> <span class="modal-close modal-close-create-memory">&times;</span>
<p id="create-memory-project-info" style="margin: 15px 0; color: var(--text-primary); line-height: 1.5;"> <p id="create-memory-project-info" class="modal-info">
Create a new memory for project <strong id="create-memory-project-name"></strong>. Create a new memory for project <strong id="create-memory-project-name"></strong>.
</p> </p>
<p style="margin: 15px 0; color: var(--text-muted); font-size: 13px; line-height: 1.5;"> <p class="modal-hint">
Memory names should be descriptive and use underscores instead of spaces (e.g., "api_architecture", Memory names should be descriptive and use underscores instead of spaces (e.g., "api_architecture",
"testing_guidelines"). Use "/" to organize memories into subdirectories (e.g., "architecture/api_design"), "testing_guidelines"). Use "/" to organize memories into subdirectories (e.g., "architecture/api_design"),
use the "global/" prefix to write memories that are shared across projects (e.g., "global/java/style_guide"). use the "global/" prefix to write memories that are shared across projects (e.g., "global/java/style_guide").
</p> </p>
<div style="margin: 20px 0;"> <div class="modal-field">
<label for="create-memory-name-input" style="display: block; margin-bottom: 8px; font-weight: 500;">Memory <label for="create-memory-name-input">Memory Name:</label>
Name:</label> <input type="text" id="create-memory-name-input" class="memory-name-input modal-input"
<input type="text" id="create-memory-name-input" class="memory-name-input" placeholder="e.g., project_overview or topic/memory_name">
placeholder="e.g., project_overview or topic/memory_name"
style="width: 100%; padding: 8px 10px; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-primary); color: var(--text-primary); font-size: 14px;">
</div> </div>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <div class="modal-actions">
<button id="create-memory-cancel-btn" class="btn" <button id="create-memory-cancel-btn" class="btn btn-secondary">Cancel</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel <button id="create-memory-create-btn" class="btn">Create</button>
</button>
<button id="create-memory-create-btn" class="btn" style="padding: 8px 16px;">Create</button>
</div> </div>
</div> </div>
</div> </div>
@@ -345,15 +338,13 @@
<div id="cancel-execution-modal" class="modal" style="display:none"> <div id="cancel-execution-modal" class="modal" style="display:none">
<div class="modal-content"> <div class="modal-content">
<span class="modal-close modal-close-cancel-execution">&times;</span> <span class="modal-close modal-close-cancel-execution">&times;</span>
<p style="margin: 20px 0; color: var(--text-primary); line-height: 1.5; font-size: 15px;"> <p class="modal-prompt">
Are you sure? The execution will continue running until timeout, it will simply no longer be in the queue. Are you sure? The execution will continue running until timeout, it will simply no longer be in the queue.
Abandoning a running execution is only advised as a measure for unblocking Serena. Abandoning a running execution is only advised as a measure for unblocking Serena.
</p> </p>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <div class="modal-actions">
<button id="cancel-execution-cancel-btn" class="btn" <button id="cancel-execution-cancel-btn" class="btn btn-secondary">Cancel</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel <button id="cancel-execution-ok-btn" class="btn">OK</button>
</button>
<button id="cancel-execution-ok-btn" class="btn" style="padding: 8px 16px;">OK</button>
</div> </div>
</div> </div>
</div> </div>
@@ -362,17 +353,14 @@
<div id="edit-serena-config-modal" class="modal" style="display:none"> <div id="edit-serena-config-modal" class="modal" style="display:none">
<div class="modal-content modal-content-large"> <div class="modal-content modal-content-large">
<span class="modal-close modal-close-edit-serena-config">&times;</span> <span class="modal-close modal-close-edit-serena-config">&times;</span>
<h3 style="margin-bottom: 10px;">Global Serena Configuration</h3> <h3>Global Serena Configuration</h3>
<p style="margin: 10px 0 20px 0; color: var(--text-muted); font-size: 13px; line-height: 1.5;"> <p class="modal-hint">
Note: Changes to the configuration will only take effect after Serena is restarted. Note: Changes to the configuration will only take effect after Serena is restarted.
</p> </p>
<textarea id="edit-serena-config-content" class="memory-editor" rows="20" <textarea id="edit-serena-config-content" class="memory-editor modal-textarea" rows="20"></textarea>
style="width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); font-family: 'Courier New', monospace; font-size: 13px; resize: vertical;"></textarea> <div class="modal-actions">
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> <button id="edit-serena-config-cancel-btn" class="btn btn-secondary">Cancel</button>
<button id="edit-serena-config-cancel-btn" class="btn" <button id="edit-serena-config-save-btn" class="btn">Save</button>
style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel
</button>
<button id="edit-serena-config-save-btn" class="btn" style="padding: 8px 16px;">Save</button>
</div> </div>
</div> </div>
</div> </div>