Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/codex/DiffViewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <QPushButton>
#include <QRegularExpression>
#include <QScrollBar>
#include <QSet>
#include <QSettings>
#include <QSignalBlocker>
#include <QSplitter>
Expand Down Expand Up @@ -855,28 +856,35 @@ void DiffViewer::applySnapshot(const GitDiffSnapshot &value) {

void DiffViewer::updateFileWatches() {
QStringList desired;
QSet<QString> desiredSet;
const auto retain = [&desired, &desiredSet](const QString &path) {
if (!path.isEmpty() && !desiredSet.contains(path)) {
desired.push_back(path);
desiredSet.insert(path);
}
};
if (snapshot) {
for (const GitDiffFile &file : snapshot->files) {
const QFileInfo info(file.absolutePath);
if (info.exists())
desired.push_back(info.absoluteFilePath());
retain(info.absoluteFilePath());
const QString parent = info.absolutePath();
if (!parent.isEmpty() && QFileInfo(parent).isDir())
desired.push_back(parent);
if (QFileInfo(parent).isDir())
retain(parent);
}
}
desired.removeDuplicates();
const QStringList existing = fileWatcher->files() + fileWatcher->directories();
const QSet<QString> existingSet(existing.begin(), existing.end());
QStringList removed;
for (const QString &path : existing) {
if (!desired.contains(path))
if (!desiredSet.contains(path))
removed.push_back(path);
}
if (!removed.isEmpty())
fileWatcher->removePaths(removed);
QStringList added;
for (const QString &path : desired) {
if (!existing.contains(path))
if (!existingSet.contains(path))
added.push_back(path);
}
if (!added.isEmpty())
Expand Down
31 changes: 10 additions & 21 deletions src/codex/TurnSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,6 @@ std::string stringValue(const nlohmann::json &object, const char *key) {
: std::string{};
}

QString friendly(QString value) {
value.replace(QLatin1Char('-'), QLatin1Char(' '));
value.replace(QLatin1Char('_'), QLatin1Char(' '));
if (!value.isEmpty())
value[0] = value[0].toUpper();
return value;
}

void addChoice(QComboBox *combo, const QString &label, const QString &value) {
if (combo->findData(value) < 0)
combo->addItem(label, value);
Expand All @@ -99,7 +91,8 @@ void selectValue(QComboBox *combo, const QString &value,
const QString &fallback = {}) {
int index = combo->findData(value);
if (index < 0) {
combo->addItem(fallback.isEmpty() ? friendly(value) : fallback, value);
combo->addItem(
fallback.isEmpty() ? UiStyle::humanizeLabel(value) : fallback, value);
index = combo->count() - 1;
}
combo->setCurrentIndex(index);
Expand All @@ -122,7 +115,7 @@ QWidget *labelled(const QString &caption, QWidget *control,
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(SettingLabelSpacing);
auto *label = new QLabel(caption);
label->setStyleSheet(QStringLiteral("color:#667085;font-weight:600;"));
label->setProperty("kind", "settingLabel");
const int labelHeight = label->fontMetrics().height();
label->setFixedHeight(labelHeight);
label->setBuddy(buddy ? buddy : control);
Expand Down Expand Up @@ -187,9 +180,6 @@ QString optionalString(const nlohmann::json &object, const char *key) {

TurnSettingsWidget::TurnSettingsWidget(QWidget *parent) : QWidget(parent) {
setObjectName(QStringLiteral("codexTurnSettings"));
setStyleSheet(QStringLiteral(
"QWidget#codexTurnSettings{background:#ffffff;border-top:1px solid "
"#d7dee8;}"));
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
auto *root = new QGridLayout(this);
root->setContentsMargins(10, 8, 10, 8);
Expand All @@ -204,8 +194,6 @@ TurnSettingsWidget::TurnSettingsWidget(QWidget *parent) : QWidget(parent) {
cwd = new QLineEdit;
cwd->setObjectName(QStringLiteral("codexWorkspace"));
cwd->setFixedHeight(SettingControlHeight);
cwd->setStyleSheet(
QStringLiteral("QLineEdit#codexWorkspace{min-height:30px;}"));
cwd->setPlaceholderText(QStringLiteral("Thread default workspace"));
auto *workspacePicker = new QWidget;
workspacePicker->setFixedHeight(SettingControlHeight);
Expand Down Expand Up @@ -269,7 +257,7 @@ TurnSettingsWidget::TurnSettingsWidget(QWidget *parent) : QWidget(parent) {
addChoice(effort, QStringLiteral("Thread default"), DefaultValue);
for (const char *value :
{"minimal", "low", "medium", "high", "xhigh", "ultra"})
addChoice(effort, friendly(QString::fromLatin1(value)),
addChoice(effort, UiStyle::humanizeLabel(QString::fromLatin1(value)),
QString::fromLatin1(value));
addChoice(sandbox, QStringLiteral("Thread default"), DefaultValue);
addChoice(sandbox, QStringLiteral("Workspace"),
Expand Down Expand Up @@ -640,8 +628,8 @@ void TurnSettingsWidget::refreshModelOptions() {
const std::string defaultEffort =
stringValue(*definition, "defaultReasoningEffort");
if (!defaultEffort.empty())
defaultLabel =
friendly(text(defaultEffort)) + QStringLiteral(" - default");
defaultLabel = UiStyle::humanizeLabel(text(defaultEffort)) +
QStringLiteral(" - default");
}
addChoice(effort, defaultLabel, DefaultValue);
const nlohmann::json supported =
Expand All @@ -652,12 +640,12 @@ void TurnSettingsWidget::refreshModelOptions() {
for (const auto &option : supported) {
const std::string key = stringValue(option, "reasoningEffort");
if (!key.empty())
addChoice(effort, friendly(text(key)), text(key));
addChoice(effort, UiStyle::humanizeLabel(text(key)), text(key));
}
} else {
for (const char *key :
{"minimal", "low", "medium", "high", "xhigh", "ultra"})
addChoice(effort, friendly(QString::fromLatin1(key)),
addChoice(effort, UiStyle::humanizeLabel(QString::fromLatin1(key)),
QString::fromLatin1(key));
}
const QString requestedEffort =
Expand Down Expand Up @@ -699,7 +687,8 @@ void TurnSettingsWidget::refreshModelOptions() {
if (legacyTiers.is_array()) {
for (const auto &tier : legacyTiers) {
if (tier.is_string())
addChoice(serviceTier, friendly(text(tier.get<std::string>())),
addChoice(serviceTier,
UiStyle::humanizeLabel(text(tier.get<std::string>())),
text(tier.get<std::string>()));
}
}
Expand Down
22 changes: 11 additions & 11 deletions src/codex/middle/ConversationCards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,13 @@ bool setVisibleMarkdown(QLabel *label, const QString &markdown) {

QString displayStatus(const QString &status) {
const QByteArray encoded = status.toUtf8();
const std::string_view display =
classifyStatus(std::string_view(encoded.constData(),
static_cast<std::size_t>(encoded.size())))
.text;
return QString::fromUtf8(display.data(),
static_cast<qsizetype>(display.size()));
const PresentationStatus classified = classifyStatus(std::string_view(
encoded.constData(), static_cast<std::size_t>(encoded.size())));
const QString display = QString::fromUtf8(
classified.text.data(), static_cast<qsizetype>(classified.text.size()));
return classified.kind == StatusKind::Unknown
? UiStyle::humanizeLabel(display)
: display;
}

QString statusTone(const QString &status) {
Expand Down Expand Up @@ -338,9 +339,7 @@ QString agentMetadata(const AgentActivityData &activity) {
QString displayChangeKind(const QString &kind) {
if (kind.isEmpty())
return QStringLiteral("Changed");
QString result = kind;
result[0] = result[0].toUpper();
return result;
return UiStyle::humanizeLabel(kind);
}

struct DiffCounts {
Expand Down Expand Up @@ -960,8 +959,9 @@ class ConversationCard::Impl final {
}
case CardKind::GenericActivity: {
const auto &activity = std::get<GenericActivityData>(data.payload);
title->setText(activity.type.isEmpty() ? QStringLiteral("Activity")
: activity.type);
title->setText(activity.type.isEmpty()
? QStringLiteral("Activity")
: UiStyle::humanizeLabel(activity.type));
metadata->setText(boundedGenericActivity(activity.raw));
metadata->setObjectName(QStringLiteral("genericActivityMetadata"));
metadata->show();
Expand Down
4 changes: 2 additions & 2 deletions src/codex/middle/InspectorPane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ QFrame *InspectorPane::agentFrame(const AgentSnapshot &agent) {

InspectorPane::InspectorPane(QWidget *parent) : QFrame(parent) {
setObjectName(QStringLiteral("inspector"));
setStyleSheet(QStringLiteral("QFrame#inspector{background:#fbfcfe;}"));
setMinimumWidth(300);
setMaximumWidth(520);

Expand Down Expand Up @@ -610,7 +609,8 @@ void InspectorPane::refreshRequests() {
auto *layout = new QVBoxLayout(frame);
layout->setContentsMargins(12, 10, 12, 10);
layout->setSpacing(6);
layout->addWidget(makeLabel(text(request.kind), "title"));
layout->addWidget(
makeLabel(UiStyle::humanizeLabel(text(request.kind)), "title"));
layout->addWidget(
makeLabel(QStringLiteral("thread %1 | generation %2 | request %3")
.arg(text(request.threadContext))
Expand Down
34 changes: 34 additions & 0 deletions src/codex/ui/UiStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ QString applicationStyleSheet() {
font-size: %1pt;
}
QMainWindow, QWidget#applicationShell { background: #f6f8fb; }
QWidget#codexTurnSettings { background: #ffffff; border-top: 1px solid #d7dee8; }
QFrame#inspector { background: #fbfcfe; }
QLabel { background: transparent; font-weight: 400; }
QLabel[kind="muted"] { color: #667085; font-size: %1pt; }
QLabel[kind="section"] {
Expand All @@ -106,6 +108,7 @@ QString applicationStyleSheet() {
QLabel[kind="code"] { font-family: monospace; font-size: %2pt; font-weight: 400; }
QLabel[kind="meta"] { color: #667085; font-size: %1pt; }
QLabel[kind="small"] { color: #667085; font-size: %1pt; }
QLabel[kind="settingLabel"] { color: #667085; font-weight: 600; }
QLabel[tone="active"] { color: #285fca; }
QLabel[tone="success"] { color: #176b45; }
QLabel[tone="warning"] { color: #8a5208; }
Expand Down Expand Up @@ -223,6 +226,7 @@ QString applicationStyleSheet() {
selection-color: #1d2633;
}
QPlainTextEdit[empty="true"] { color: #98a2b3; }
QLineEdit#codexWorkspace { min-height: 30px; }
QPlainTextEdit[kind="code"], QPlainTextEdit[kind="command"],
QTextEdit[kind="code"], QTextEdit[kind="command"],
QPlainTextEdit[kind="infoViewer"] {
Expand Down Expand Up @@ -490,4 +494,34 @@ QString applicationStyleSheet() {
.arg(compact, standard, section, heading, panelHeader);
}

QString humanizeLabel(QString value) {
value = value.trimmed();
QString result;
result.reserve(value.size() + 4);
bool space = false;
for (qsizetype index = 0; index < value.size(); ++index) {
QChar character = value[index];
if (character.isSpace() || character == QLatin1Char('-') ||
character == QLatin1Char('_') || character == QLatin1Char('.') ||
character == QLatin1Char('/')) {
space = !result.isEmpty();
continue;
}
const QChar previous = index > 0 ? value[index - 1] : QChar{};
const QChar next = index + 1 < value.size() ? value[index + 1] : QChar{};
const bool wordBoundary =
character.isUpper() && (previous.isLower() || previous.isDigit() ||
(previous.isUpper() && next.isLower()));
if ((space || wordBoundary) && !result.endsWith(QLatin1Char(' ')))
result.append(QLatin1Char(' '));
if (wordBoundary || (space && character.isUpper() && next.isLower()))
character = character.toLower();
result.append(character);
space = false;
}
if (!result.isEmpty())
result[0] = result[0].toUpper();
return result;
}

} // namespace codexui::UiStyle
1 change: 1 addition & 0 deletions src/codex/ui/UiStyle.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ inline constexpr auto redText = "#982f3d";
inline constexpr auto purple = "#6941c6";

QString applicationStyleSheet();
QString humanizeLabel(QString value);
enum class ChevronDirection { Down, Left, Right };
void drawChevron(QWidget *widget, const QRect &indicator, bool enabled,
bool highlighted,
Expand Down
21 changes: 16 additions & 5 deletions tests/codex/ApplicationLayoutTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,21 @@ bool testIncrementalThreadSettings() {
if (!model || !approval || !access || !network)
return expect(false, "thread settings controls are discoverable");

bool canonicalSettingsStyle = settings.styleSheet().isEmpty();
for (const QLabel *label : settings.findChildren<QLabel *>()) {
if (label->text() == QStringLiteral("Model"))
canonicalSettingsStyle &= label->property("kind") == "settingLabel" &&
label->styleSheet().isEmpty();
}

model->setCurrentIndex(model->findData(QStringLiteral("gpt-b")));
settings.setContext(
"thread-a",
{{"model", "gpt-a"}, {"approvalPolicy", "on-request"}}, models,
nlohmann::json::array(), 1, {{"approvalPolicy", "on-request"}});
bool result = expect(
bool result = expect(canonicalSettingsStyle,
"thread settings use canonical application styling");
result &= expect(
model->currentData().toString() == QStringLiteral("gpt-b") &&
approval->currentData().toString() == QStringLiteral("on-request"),
"a partial authoritative update preserves unrelated pending settings");
Expand Down Expand Up @@ -1312,10 +1321,12 @@ bool testInspectorDetailParity() {
"long agent Markdown follows visible metadata without surplus height");
inspector.tabs()->setCurrentIndex(3);
spin(20);
result &= expect(
hasLabelContaining(inspector, QStringLiteral("thread Original title")) &&
hasLabelContaining(inspector, QStringLiteral("3 questions")),
"Requests show their thread title and retained question count");
result &=
expect(hasLabelContaining(inspector, QStringLiteral("User input")) &&
hasLabelContaining(inspector,
QStringLiteral("thread Original title")) &&
hasLabelContaining(inspector, QStringLiteral("3 questions")),
"Requests show their thread title and retained question count");
model.applyEvent(presentation::event(
5, 1, "thread.name.changed", {{"name", "Renamed title"}},
presentation::Authority::Replace, {{"threadId", "owner-thread"}}));
Expand Down
24 changes: 18 additions & 6 deletions tests/codex/ConversationCardsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1675,17 +1675,29 @@ bool testGeneratedImagePresentationAndGenericBound() {
"generated",
"turn",
"unknown",
GenericActivityData{QStringLiteral("Unknown activity"),
{{"large", std::string(100000, 'x')}}}};
GenericActivityData{QStringLiteral("contextCompaction"),
{{"type", "contextCompaction"},
{"large", std::string(100000, 'x')}}}};
ConversationCard genericCard(generic);
genericCard.show();
spin();
auto *details = genericCard.findChild<QLabel *>(
QStringLiteral("genericActivityMetadata"));
result &= expect(details && details->text().size() < 4200 &&
details->text().endsWith(
QStringLiteral("[Activity details truncated]")),
"unknown activity text is bounded before Qt lays it out");
const auto genericLabels = genericCard.findChildren<QLabel *>();
result &= expect(
std::ranges::any_of(genericLabels,
[](QLabel *label) {
return label->property("kind").toString() ==
QStringLiteral("title") &&
label->text() ==
QStringLiteral("Context compaction");
}) &&
std::get<GenericActivityData>(generic.payload).type ==
QStringLiteral("contextCompaction") &&
details && details->text().size() < 4200 &&
details->text().endsWith(
QStringLiteral("[Activity details truncated]")),
"protocol labels are humanized without changing bounded raw details");
return result;
}

Expand Down
31 changes: 31 additions & 0 deletions tests/codex/GitChangesLiveTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,37 @@ bool testLiveWorkingTreeChanges() {
},
3500),
"discovers a modified tracked file");
const QString renamed =
directory.filePath(QStringLiteral("renamed-tracked.txt"));
result &= expect(writeFile(tracked, QByteArray("original\n")) &&
QFile::rename(tracked, renamed) &&
waitFor(
[&] {
return hasFile(viewer.currentSnapshot(),
QStringLiteral(
"renamed-tracked.txt"),
QStringLiteral("Renamed"));
},
1500),
"moves watches with a renamed tracked file");
result &= expect(QFile::rename(renamed, tracked) &&
waitFor(
[&] {
return viewer.currentSnapshot().files.empty();
},
1500),
"restores watches when a renamed file moves back");
result &= expect(writeFile(tracked, QByteArray("modified\n")),
"modifies the restored tracked file");
viewer.refreshRepository();
result &= expect(waitFor(
[&] {
return hasFile(viewer.currentSnapshot(),
QStringLiteral("tracked.txt"),
QStringLiteral("Modified"));
},
1500),
"rediscovers a modified file after a rename cycle");
result &= expect(writeFile(tracked, QByteArray("original\n")) &&
waitFor(
[&] {
Expand Down