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
8 changes: 2 additions & 6 deletions src/codex/ShellWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,18 +557,14 @@ void ShellWidget::Impl::render() {
text(view.conversation.emptyMessage));
middleRegion->conversation().reconcile(view.conversation.snapshot);
if (view.conversation.mode == UiConversationMode::Thread) {
QStringList metadata;
if (!view.conversation.workspace.empty())
metadata << text(view.conversation.workspace);
if (!view.conversation.status.empty())
metadata << text(view.conversation.status);
const QString activity = view.conversation.lastActivityAt
? lastActivityText(
*view.conversation.lastActivityAt)
: QString{};
middleRegion->setThreadHeading(
text(view.conversation.title),
metadata.join(QStringLiteral(" | ")), activity);
text(view.conversation.workspace), activity,
text(view.conversation.status), text(view.conversation.statusTone));
} else if (view.conversation.mode == UiConversationMode::NewThread) {
middleRegion->setThreadHeading(text(view.conversation.title),
text(view.conversation.workspace));
Expand Down
4 changes: 3 additions & 1 deletion src/codex/UiSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,9 @@ class UiSession::Impl final {
conversation.mode = UiConversationMode::Thread;
conversation.title = thread->title;
conversation.workspace = thread->cwd;
conversation.status = std::string(classifyStatus(thread->status).text);
const PresentationStatus status = classifyStatus(thread->status);
conversation.status = std::string(status.text);
conversation.statusTone = std::string(status.tone);
conversation.lastActivityAt = thread->lastActivityAt;
conversation.emptyMessage = "No materialized activity.";
} else if (newThreadIntent) {
Expand Down
1 change: 1 addition & 0 deletions src/codex/UiSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ struct UiConversationView {
std::string title;
std::string workspace;
std::string status;
std::string statusTone;
std::optional<std::int64_t> lastActivityAt;
std::string emptyMessage;
middle::ConversationSnapshot snapshot;
Expand Down
32 changes: 30 additions & 2 deletions src/codex/middle/MiddleRegionWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,16 +242,31 @@ MiddleRegionWidget::MiddleRegionWidget(QWidget *parent) : QWidget(parent) {
conversationTrailingMetadata = makeLabel({}, "meta");
conversationTrailingMetadata->setObjectName(
QStringLiteral("conversationTrailingMetadata"));
conversationTrailingMetadata->setProperty("tone", "strong");
conversationTrailingMetadata->setWordWrap(false);
conversationTrailingMetadata->setSizePolicy(QSizePolicy::Minimum,
QSizePolicy::Preferred);
conversationStateSeparator = makeLabel(QStringLiteral("|"), "meta");
conversationStateSeparator->setProperty("tone", "strong");
conversationStateSeparator->setSizePolicy(QSizePolicy::Minimum,
QSizePolicy::Preferred);
conversationState = makeLabel({}, "meta");
conversationState->setObjectName(QStringLiteral("conversationState"));
conversationState->setSizePolicy(QSizePolicy::Minimum,
QSizePolicy::Preferred);
conversationTitle->setAlignment(Qt::AlignLeft | Qt::AlignTop);
conversationMetadata->setAlignment(Qt::AlignLeft | Qt::AlignTop);
conversationTrailingMetadata->setAlignment(Qt::AlignRight | Qt::AlignTop);
conversationState->setAlignment(Qt::AlignLeft | Qt::AlignTop);
alignThreadHeadingBaselines();
auto *trailingHeading = new QHBoxLayout;
trailingHeading->setSpacing(4);
trailingHeading->addWidget(conversationTrailingMetadata, 0, Qt::AlignTop);
trailingHeading->addWidget(conversationStateSeparator, 0, Qt::AlignTop);
trailingHeading->addWidget(conversationState, 0, Qt::AlignTop);
threadHeading->addWidget(conversationTitle, 0, Qt::AlignTop);
threadHeading->addWidget(conversationMetadata, 1, Qt::AlignTop);
threadHeading->addWidget(conversationTrailingMetadata, 0, Qt::AlignTop);
threadHeading->addLayout(trailingHeading);
contentLayout->addLayout(threadHeading);
contentLayout->addSpacing(7);
contentLayout->addWidget(divider());
Expand Down Expand Up @@ -347,20 +362,31 @@ QSplitter *MiddleRegionWidget::splitterWidget() const noexcept {
}

void MiddleRegionWidget::setThreadHeading(QString title, QString metadata,
QString trailingMetadata) {
QString trailingMetadata,
QString state, QString stateTone) {
if (conversationTitle->text() != title)
conversationTitle->setText(std::move(title));
if (conversationMetadata->text() != metadata)
conversationMetadata->setText(std::move(metadata));
if (conversationTrailingMetadata->text() != trailingMetadata)
conversationTrailingMetadata->setText(std::move(trailingMetadata));
if (conversationState->text() != state)
conversationState->setText(std::move(state));
conversationStateSeparator->setVisible(!conversationState->text().isEmpty());
if (conversationState->property("tone").toString() != stateTone) {
conversationState->setProperty("tone", std::move(stateTone));
conversationState->style()->unpolish(conversationState);
conversationState->style()->polish(conversationState);
}
alignThreadHeadingBaselines();
}

void MiddleRegionWidget::alignThreadHeadingBaselines() {
conversationTitle->ensurePolished();
conversationMetadata->ensurePolished();
conversationTrailingMetadata->ensurePolished();
conversationStateSeparator->ensurePolished();
conversationState->ensurePolished();
const int offset = std::max(
0, conversationTitle->fontMetrics().ascent() -
conversationMetadata->fontMetrics().ascent());
Expand All @@ -369,6 +395,8 @@ void MiddleRegionWidget::alignThreadHeadingBaselines() {
0, conversationTitle->fontMetrics().ascent() -
conversationTrailingMetadata->fontMetrics().ascent());
conversationTrailingMetadata->setContentsMargins(0, trailingOffset, 0, 0);
conversationStateSeparator->setContentsMargins(0, trailingOffset, 0, 0);
conversationState->setContentsMargins(0, trailingOffset, 0, 0);
}

void MiddleRegionWidget::showNotice(QString message, bool error) {
Expand Down
5 changes: 4 additions & 1 deletion src/codex/middle/MiddleRegionWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class MiddleRegionWidget final : public QWidget {
[[nodiscard]] QSplitter *splitterWidget() const noexcept;

void setThreadHeading(QString title, QString metadata,
QString trailingMetadata = {});
QString trailingMetadata = {},
QString state = {}, QString stateTone = {});
void showNotice(QString message, bool error = true);
void showSidebar(bool visible);
void showInspector(bool visible);
Expand All @@ -58,6 +59,8 @@ class MiddleRegionWidget final : public QWidget {
QLabel *conversationTitle = nullptr;
QLabel *conversationMetadata = nullptr;
QLabel *conversationTrailingMetadata = nullptr;
QLabel *conversationStateSeparator = nullptr;
QLabel *conversationState = nullptr;
QToolButton *reasoningVisibility = nullptr;
QToolButton *updateVisibility = nullptr;
QToolButton *commandInitialFolding = nullptr;
Expand Down
1 change: 1 addition & 0 deletions src/codex/ui/UiStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ QString applicationStyleSheet() {
QLabel[kind="body"] { font-size: %2pt; }
QLabel[kind="code"] { font-family: monospace; font-size: %2pt; font-weight: 400; }
QLabel[kind="meta"] { color: #667085; font-size: %1pt; }
QLabel[kind="meta"][tone="strong"] { color: #1d2633; }
QLabel[kind="small"] { color: #667085; font-size: %1pt; }
QLabel[kind="settingLabel"] { color: #667085; font-weight: 600; }
QLabel[tone="active"] { color: #285fca; }
Expand Down
25 changes: 19 additions & 6 deletions tests/codex/ApplicationLayoutTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,10 @@ bool testOverlayGeometryAndRegionRouting() {
region.resize(1500, 820);
region.show();
region.setThreadHeading(QStringLiteral("Thread title"),
QStringLiteral("/workspace | Completed"),
QStringLiteral("Last activity: 14:15:51"));
QStringLiteral("/workspace"),
QStringLiteral("Last activity: 14:15:51"),
QStringLiteral("Completed"),
QStringLiteral("success"));
spin(20);

QSplitter *splitter = region.splitterWidget();
Expand All @@ -323,6 +325,8 @@ bool testOverlayGeometryAndRegionRouting() {
auto *conversationTrailingMetadata =
splitter->widget(1)->findChild<QLabel *>(
QStringLiteral("conversationTrailingMetadata"));
auto *conversationState = splitter->widget(1)->findChild<QLabel *>(
QStringLiteral("conversationState"));
auto *reasoningToggle = splitter->widget(1)->findChild<QToolButton *>(
QStringLiteral("conversationReasoningToggle"));
auto *updatesToggle = splitter->widget(1)->findChild<QToolButton *>(
Expand Down Expand Up @@ -350,14 +354,23 @@ bool testOverlayGeometryAndRegionRouting() {
"Threads and Conversation header dividers share the 10 px inset");
result &=
expect(conversationTitle && conversationMetadata &&
conversationTrailingMetadata &&
conversationTrailingMetadata && conversationState &&
conversationMetadata->geometry().left() >
conversationTitle->geometry().right() &&
conversationTrailingMetadata->geometry().right() >=
conversationTrailingMetadata->parentWidget()->width() -
16 &&
conversationTrailingMetadata->geometry().right() <
conversationState->geometry().left() &&
conversationState->geometry().right() >=
conversationState->parentWidget()->width() - 16 &&
conversationTrailingMetadata->text() ==
QStringLiteral("Last activity: 14:15:51") &&
conversationTrailingMetadata->property("tone").toString() ==
QStringLiteral("strong") &&
conversationState->text() == QStringLiteral("Completed") &&
conversationState->property("tone").toString() ==
QStringLiteral("success") &&
conversationState->width() >=
conversationState->fontMetrics().horizontalAdvance(
conversationState->text()) &&
conversationTrailingMetadata->width() >=
conversationTrailingMetadata->fontMetrics()
.horizontalAdvance(
Expand Down
4 changes: 2 additions & 2 deletions web/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -642,9 +642,9 @@ function Conversation({session, revision, paneControls}: {session: BrowserFronte
return <main ref={pane} className="conversation-pane" tabIndex={-1}>
<div className="conversation-heading"><div className="conversation-title"><span className="eyebrow">Conversation</span>
<div className="conversation-lockup"><h1>{thread?.title ?? (snapshot.newThreadIntent ? snapshot.newThreadDraft?.name || "New thread" : "Select a thread")}</h1>
<p className="conversation-meta">{thread ? [thread.cwd, classifyStatus(thread.status).text].filter(Boolean).join(" | ")
<p className="conversation-meta">{thread ? thread.cwd
: snapshot.newThreadIntent ? `${snapshot.newThreadDraft?.workspace ?? ""} | Send a message to create this thread.` : "Choose a thread from the left."}</p>
{thread?.lastActivityAt !== undefined && <p className="conversation-activity">{lastActivityText(thread.lastActivityAt)}</p>}</div></div>
{thread?.lastActivityAt !== undefined && <p className="conversation-activity">{lastActivityText(thread.lastActivityAt)} <span aria-hidden="true">|</span> <strong className={classifyStatus(thread.status).tone}>{classifyStatus(thread.status).text}</strong></p>}</div></div>
<div className={`conversation-heading-actions${paneControls ? " responsive" : ""}`}>
{paneControls && <div className="responsive-pane-controls">{paneControls}</div>}
<div className="conversation-view-controls" aria-label="Conversation presentation">
Expand Down
3 changes: 2 additions & 1 deletion web/src/styles.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions web/tests/responsive-layout.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ test("thread hierarchy exposes selected tree-item semantics", () => {
assert.match(markup, /role="treeitem" aria-level="1" aria-selected="true"/u);
assert.match(markup, /aria-current="true" aria-label="Open Accessible thread, \/workspace"/u);
assert.match(markup, /class="conversation-lockup"[\s\S]*Last activity:/u);
assert.match(markup, /Last activity:[\s\S]*<strong class="success">Completed<\/strong>/u);
session.dispose();
});

Expand All @@ -83,6 +84,7 @@ test("responsive CSS keeps the desktop grid and removes the old document-width f
assert.match(css, /@media \(max-width:\s*1160px\)[\s\S]*grid-template-columns:\s*220px minmax\(0, 1fr\)/u);
assert.match(css, /@media \(max-width:\s*760px\)[\s\S]*grid-template-columns:\s*minmax\(0, 1fr\)/u);
assert.match(css, /@media \(max-width:\s*760px\)[\s\S]*\.top-bar\s*\{[^}]*flex-wrap:\s*wrap/u);
assert.match(css, /\.conversation-heading \.conversation-activity\s*\{[^}]*color:\s*#1d2633/u);
assert.match(css, /\.responsive-drawer\s*\{[^}]*position:\s*fixed/u);
assert.match(css, /\.drawer-backdrop\s*\{[^}]*position:\s*fixed/u);
assert.match(css, /button:focus-visible[\s\S]*outline:\s*2px solid #6f98e8/u);
Expand Down
Loading