theme: upgrade palette from Catppuccin Mocha to Frappé

Replaces all dark-Mocha colours (#1e1e2e base, #313244 surface0, etc.)
with the noticeably brighter Frappé variants (#303446 base, #414559
surface0, etc.) across the QSS and every hardcoded colour in Python
source files (syntax highlighter, completer popup, log viewer, explain
view, table viewer, schema browser, icons, etc.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 16:51:37 -04:00
co-authored by Claude Sonnet 4.6
parent 22f1dd5f45
commit b003976d39
14 changed files with 221 additions and 221 deletions
+2 -2
View File
@@ -14,7 +14,7 @@ class ConnectionProfile:
database: str = ""
username: str = ""
password: str = field(default="", repr=False)
color: str = "#89b4fa"
color: str = "#8caaee"
id: str = field(default_factory=lambda: str(uuid.uuid4()))
ssl: bool = False
ssl_cert: str = ""
@@ -71,7 +71,7 @@ class ConnectionProfile:
port=data.get("port", 3306),
database=data.get("database", ""),
username=data.get("username", ""),
color=data.get("color", "#89b4fa"),
color=data.get("color", "#8caaee"),
ssl=data.get("ssl", False),
ssl_cert=data.get("ssl_cert", ""),
ssl_key=data.get("ssl_key", ""),
+1 -1
View File
@@ -11,7 +11,7 @@ from PyQt6.QtGui import QColor, QFont
class ResultTableModel(QAbstractTableModel):
"""Immutable result-set model — replaces data via set_data()."""
NULL_COLOR = QColor("#6c7086") # muted grey for NULL
NULL_COLOR = QColor("#737994") # muted grey for NULL
NUM_ALIGN = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
TEXT_ALIGN = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
+2 -2
View File
@@ -17,7 +17,7 @@ from app.config.connections import save_profile
class ColorButton(QPushButton):
"""A button that shows a solid colour and opens a colour picker."""
def __init__(self, color: str = "#89b4fa", parent=None):
def __init__(self, color: str = "#8caaee", parent=None):
super().__init__(parent)
self._color = color
self.setFixedSize(32, 24)
@@ -26,7 +26,7 @@ class ColorButton(QPushButton):
def _refresh(self):
self.setStyleSheet(
f"background-color:{self._color}; border:1px solid #45475a; border-radius:4px;"
f"background-color:{self._color}; border:1px solid #51576d; border-radius:4px;"
)
def _pick(self):
+11 -11
View File
@@ -36,12 +36,12 @@ def _cost_color(val_str: str) -> QColor:
try:
v = float(str(val_str).replace(",", ""))
except (ValueError, TypeError):
return QColor("#cdd6f4") # neutral (Catppuccin text)
return QColor("#c6d0f5") # neutral (Catppuccin text)
if v <= 10:
return QColor("#a6e3a1") # green — cheap
return QColor("#a6d189") # green — cheap
if v <= 1_000:
return QColor("#f9e2af") # yellow — moderate
return QColor("#f38ba8") # red — expensive
return QColor("#e5c890") # yellow — moderate
return QColor("#e78284") # red — expensive
# ── Background worker ─────────────────────────────────────────────────────────
@@ -101,9 +101,9 @@ def _build_mysql_tree(tree: QTreeWidget, cols: list, rows: list):
good_types = {"const", "eq_ref", "ref", "system"}
jt_lower = join_type.lower()
if jt_lower in bad_types:
item.setForeground(1, QBrush(QColor("#f38ba8")))
item.setForeground(1, QBrush(QColor("#e78284")))
elif jt_lower in good_types:
item.setForeground(1, QBrush(QColor("#a6e3a1")))
item.setForeground(1, QBrush(QColor("#a6d189")))
# Colour rows estimate
item.setForeground(3, QBrush(_cost_color(est_rows)))
@@ -203,9 +203,9 @@ def _build_sqlite_tree(tree: QTreeWidget, cols: list, rows: list):
# Colour SCAN (bad) vs SEARCH/INDEX (good)
detail_str = str(detail).upper()
if "SCAN" in detail_str and "INDEX" not in detail_str:
item.setForeground(1, QBrush(QColor("#f38ba8")))
item.setForeground(1, QBrush(QColor("#e78284")))
elif "INDEX" in detail_str or "SEARCH" in detail_str:
item.setForeground(1, QBrush(QColor("#a6e3a1")))
item.setForeground(1, QBrush(QColor("#a6d189")))
items[rid] = item
parent_item = items.get(parent)
@@ -334,8 +334,8 @@ class ExplainPanel(QWidget):
legend_row = QHBoxLayout()
legend_row.setContentsMargins(8, 2, 0, 4)
for colour, label in [
("#a6e3a1", "Efficient"), ("#f9e2af", "Moderate"),
("#f38ba8", "Expensive / SCAN"),
("#a6d189", "Efficient"), ("#e5c890", "Moderate"),
("#e78284", "Expensive / SCAN"),
]:
dot = QLabel("")
dot.setStyleSheet(f"color: {colour};")
@@ -414,7 +414,7 @@ class ExplainPanel(QWidget):
self._status_lbl.setText(f"Error: {msg[:80]}")
self._tree.clear()
err_item = QTreeWidgetItem([f"{msg}"])
err_item.setForeground(0, QBrush(QColor("#f38ba8")))
err_item.setForeground(0, QBrush(QColor("#e78284")))
self._tree.setColumnCount(1)
self._tree.setHeaderLabels(["Error"])
self._tree.addTopLevelItem(err_item)
+9 -9
View File
@@ -30,14 +30,14 @@ from PyQt6.QtGui import (
from app.utils.logger import get_log_path
# ── Level colours (Catppuccin Mocha palette) ─────────────────────────────────
# ── Level colours (Catppuccin Frappé palette) ─────────────────────────────────
_LEVEL_COLOURS: dict[str, str] = {
"DEBUG": "#6c7086", # surface2 / dimmed
"INFO": "#cdd6f4", # text (default)
"WARNING": "#f9e2af", # yellow
"ERROR": "#f38ba8", # red
"CRITICAL": "#ff79c6", # pink / bright red
"DEBUG": "#737994", # overlay0 / dimmed
"INFO": "#c6d0f5", # text (default)
"WARNING": "#e5c890", # yellow
"ERROR": "#e78284", # red
"CRITICAL": "#f4b8e4", # pink / bright
}
_LEVEL_ORDER = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
@@ -85,8 +85,8 @@ class _LogHighlighter(QSyntaxHighlighter):
# Highlight search matches in bright yellow
if self._search:
search_fmt = QTextCharFormat()
search_fmt.setBackground(QColor("#f9e2af"))
search_fmt.setForeground(QColor("#1e1e2e"))
search_fmt.setBackground(QColor("#e5c890"))
search_fmt.setForeground(QColor("#303446"))
idx = text.lower().find(self._search)
while idx != -1:
self.setFormat(idx, len(self._search), search_fmt)
@@ -107,7 +107,7 @@ class _LevelBtn(QPushButton):
def _apply_style(self, checked: bool):
if checked:
self.setStyleSheet(
f"QPushButton {{ background: {self._colour}; color: #1e1e2e; "
f"QPushButton {{ background: {self._colour}; color: #303446; "
f"border: none; border-radius: 3px; font-weight: bold; }}"
)
else:
+1 -1
View File
@@ -153,7 +153,7 @@ class QueryHistoryPanel(QWidget):
for c, val in enumerate(items):
item = QTableWidgetItem(val)
if c == 3 and status == "ERROR":
item.setForeground(QColor("#f38ba8"))
item.setForeground(QColor("#e78284"))
self._table.setItem(r, c, item)
def _filter(self, text: str):
+4 -4
View File
@@ -96,7 +96,7 @@ class SchemaBrowser(QWidget):
# Dim colour — show the profile colour but muted
color = QColor(profile.color)
color.setAlpha(140)
item.setForeground(0, QBrush(QColor("#6c7086"))) # greyed out
item.setForeground(0, QBrush(QColor("#737994"))) # greyed out
item.setIcon(0, get_icon(profile.db_type, 16))
item.setToolTip(0, self._connection_tooltip(profile))
@@ -350,9 +350,9 @@ class SchemaBrowser(QWidget):
child = QTreeWidgetItem([label])
child.setData(0, Qt.ItemDataRole.UserRole, (NT["column"], "", "", c.name))
if c.is_primary_key:
child.setForeground(0, QBrush(QColor("#f9e2af")))
child.setForeground(0, QBrush(QColor("#e5c890")))
elif c.is_foreign_key:
child.setForeground(0, QBrush(QColor("#89dceb")))
child.setForeground(0, QBrush(QColor("#99d1db")))
item.addChild(child)
def _load_indexes(self, item, pid, db, table):
@@ -386,7 +386,7 @@ class SchemaBrowser(QWidget):
def _show_error(self, item, msg):
err = QTreeWidgetItem([f"{msg}"])
err.setForeground(0, QBrush(QColor("#f38ba8")))
err.setForeground(0, QBrush(QColor("#e78284")))
item.addChild(err)
# ── Context menu ──────────────────────────────────────────────────────────
+6 -6
View File
@@ -53,19 +53,19 @@ class _CompletionPopup(QListWidget):
self.setStyleSheet("""
QListWidget {
background: #1e1e2e;
color: #cdd6f4;
border: 1px solid #45475a;
background: #292c3c;
color: #c6d0f5;
border: 1px solid #51576d;
border-radius: 4px;
padding: 2px;
outline: none;
}
QListWidget::item { padding: 2px 8px; }
QListWidget::item:selected {
background: #313244;
color: #cdd6f4;
background: #414559;
color: #c6d0f5;
}
QListWidget::item:hover { background: #2a2a3c; }
QListWidget::item:hover { background: #363a4f; }
""")
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
+4 -4
View File
@@ -81,7 +81,7 @@ class CodeEditor(QPlainTextEdit):
extra = []
if not self.isReadOnly():
sel = QTextEdit.ExtraSelection()
sel.format.setBackground(QColor("#2a2a3c"))
sel.format.setBackground(QColor("#3d4155"))
sel.format.setProperty(QTextFormat.Property.FullWidthSelection, True)
sel.cursor = self.textCursor()
sel.cursor.clearSelection()
@@ -90,7 +90,7 @@ class CodeEditor(QPlainTextEdit):
def line_number_area_paint_event(self, event):
painter = QPainter(self._line_area)
painter.fillRect(event.rect(), QColor("#1a1a2e"))
painter.fillRect(event.rect(), QColor("#2a2d3e"))
block = self.firstVisibleBlock()
number = block.blockNumber()
@@ -100,7 +100,7 @@ class CodeEditor(QPlainTextEdit):
while block.isValid() and top <= event.rect().bottom():
if block.isVisible() and bottom >= event.rect().top():
painter.setPen(QColor("#45475a"))
painter.setPen(QColor("#51576d"))
painter.drawText(
0, top, self._line_area.width() - 6,
self.fontMetrics().height(),
@@ -339,7 +339,7 @@ class FindBar(QWidget):
self._do_find(backward=True)
def _set_no_match(self, no_match: bool):
color = "#f38ba8" if no_match else ""
color = "#e78284" if no_match else ""
self._find_input.setStyleSheet(
f"background: {color};" if no_match else ""
)
+11 -11
View File
@@ -17,15 +17,15 @@ def _fmt(color: str, bold: bool = False, italic: bool = False) -> QTextCharForma
return f
# ── Token categories (Catppuccin Mocha palette) ───────────────────────────────
_KEYWORD_FMT = _fmt("#89b4fa", bold=True) # blue — DDL/DML/control
_TYPE_FMT = _fmt("#fab387") # peach — data types
_FUNCTION_FMT = _fmt("#a6e3a1") # green — functions
_STRING_FMT = _fmt("#a6e3a1") # green — string literals
_NUMBER_FMT = _fmt("#fab387") # orange — numeric literals
_COMMENT_FMT = _fmt("#6c7086", italic=True) # grey — comments
_OPERATOR_FMT = _fmt("#cba6f7") # mauve — operators/special
_STAR_FMT = _fmt("#cba6f7", bold=True) # mauve — * wildcard
# ── Token categories (Catppuccin Frappé palette) ─────────────────────────────
_KEYWORD_FMT = _fmt("#8caaee", bold=True) # blue — DDL/DML/control
_TYPE_FMT = _fmt("#ef9f76") # peach — data types
_FUNCTION_FMT = _fmt("#a6d189") # green — functions
_STRING_FMT = _fmt("#a6d189") # green — string literals
_NUMBER_FMT = _fmt("#ef9f76") # orange — numeric literals
_COMMENT_FMT = _fmt("#737994", italic=True) # grey — comments
_OPERATOR_FMT = _fmt("#ca9ee6") # mauve — operators/special
_STAR_FMT = _fmt("#ca9ee6", bold=True) # mauve — * wildcard
# ── Keyword lists ─────────────────────────────────────────────────────────────
_KEYWORDS = [
@@ -101,9 +101,9 @@ class SQLHighlighter(QSyntaxHighlighter):
# Single-quoted strings
(QRegularExpression(r"'[^'\\]*(?:\\.[^'\\]*)*'"), _STRING_FMT),
# Double-quoted identifiers
(QRegularExpression(r'"[^"]*"'), _fmt("#89dceb")),
(QRegularExpression(r'"[^"]*"'), _fmt("#99d1db")),
# Backtick identifiers (MySQL)
(QRegularExpression(r"`[^`]*`"), _fmt("#89dceb")),
(QRegularExpression(r"`[^`]*`"), _fmt("#99d1db")),
# * wildcard
(QRegularExpression(r"\bSELECT\s+\*|\*(?=\s*FROM)",
QRegularExpression.PatternOption.CaseInsensitiveOption),
+2 -2
View File
@@ -171,9 +171,9 @@ class TableStructureView(QWidget):
item = QTableWidgetItem(str(val))
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
if col.is_primary_key and c == 0:
item.setForeground(QColor("#f9e2af"))
item.setForeground(QColor("#e5c890"))
elif col.is_foreign_key and c == 0:
item.setForeground(QColor("#89dceb"))
item.setForeground(QColor("#99d1db"))
t.setItem(r, c, item)
def _load_indexes(self):
+6 -6
View File
@@ -148,17 +148,17 @@ class EditableTableModel(QAbstractTableModel):
if role == Qt.ItemDataRole.ForegroundRole:
if self._is_deleted_row(r):
return QBrush(QColor("#f38ba8")) # red — pending delete
return QBrush(QColor("#e78284")) # red — pending delete
if (r, c) in self._dirty:
return QBrush(QColor("#fab387")) # orange — edited
return QBrush(QColor("#ef9f76")) # orange — edited
if val is None:
return QBrush(QColor("#6c7086")) # grey — NULL
return QBrush(QColor("#737994")) # grey — NULL
if role == Qt.ItemDataRole.BackgroundRole:
if self._is_deleted_row(r):
return QBrush(QColor("#2a1a1e"))
return QBrush(QColor("#3a2530"))
if (r, c) in self._dirty:
return QBrush(QColor("#2a1f1a"))
return QBrush(QColor("#3a2f25"))
return None
@@ -351,7 +351,7 @@ class TableViewer(QWidget):
# Separator
sep = QLabel("|")
sep.setStyleSheet("color:#45475a; padding:0 4px;")
sep.setStyleSheet("color:#51576d; padding:0 4px;")
# CRUD buttons
self._add_btn = QPushButton(" Add")
+1 -1
View File
@@ -64,7 +64,7 @@ ICONS = {
def make_icon(glyph: str, size: int = 20,
fg: str = "#cdd6f4", bg: str = "transparent") -> QIcon:
fg: str = "#c6d0f5", bg: str = "transparent") -> QIcon:
"""Create a QIcon from a Unicode glyph."""
px = QPixmap(QSize(size, size))
px.fill(Qt.GlobalColor.transparent)