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
23 changes: 13 additions & 10 deletions lib/public/css/mobile-nav.css
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,7 @@
background: rgba(var(--overlay-rgb), 0.06);
}

/* Split "New session" row: main action on the last-used vendor + a chevron
that expands .mobile-vendor-list below it. Mirrors the desktop sidebar. */
/* Primary creation action + a labeled provider selector. */
.mobile-session-new-row {
display: flex;
gap: 6px;
Expand All @@ -377,26 +376,30 @@
color: var(--text);
gap: 8px;
}
.mobile-session-new-row .mobile-session-new-chevron {
flex: 0 0 auto;
width: 44px;
.mobile-session-new-row .mobile-session-new-provider {
flex: 0 1 132px;
width: 132px;
min-width: 104px;
margin-bottom: 0;
padding: 10px 0;
padding: 6px 9px;
border: 1px solid var(--border-subtle);
border-radius: 8px;
background: rgba(var(--overlay-rgb), 0.03);
color: var(--text-muted);
justify-content: center;
gap: 0;
display: grid;
gap: 2px;
}
.mobile-session-new-row .mobile-session-new-chevron.expanded svg {
.mobile-session-new-row .mobile-session-new-provider.expanded .session-create-provider-chevron svg {
transform: rotate(180deg);
}
.mobile-session-new-row .mobile-session-new-main:active,
.mobile-session-new-row .mobile-session-new-chevron:active {
.mobile-session-new-row .mobile-session-new-provider:active {
background: rgba(var(--overlay-rgb), 0.1);
}

.mobile-session-new-row .session-create-provider-label { font-size: 9px; }
.mobile-session-new-row .session-create-provider-name { font-size: 12px; }

.mobile-vendor-list {
display: flex;
flex-direction: column;
Expand Down
71 changes: 61 additions & 10 deletions lib/public/css/sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -1262,26 +1262,77 @@
opacity: 1;
}

/* Split button: main action + chevron sharing one grid cell. */
/* Primary creation action + a visibly grouped provider selector. */
.session-top-action-split {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 2px;
gap: 6px;
min-width: 0;
}

.session-top-action-split .split-main {
flex: 1 1 auto;
flex: 1 1 110px;
width: auto;
min-width: 110px;
border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
background: color-mix(in srgb, var(--accent) 13%, transparent);
color: var(--text);
font-weight: 600;
white-space: nowrap;
}

.session-top-action-split .split-chevron {
flex: 0 0 auto;
width: 28px;
min-width: 28px;
padding: 0;
justify-content: center;
gap: 0;
.session-top-action-split .split-main:hover {
border-color: color-mix(in srgb, var(--accent) 50%, transparent);
background: color-mix(in srgb, var(--accent) 19%, transparent);
}

.session-top-action-split .split-provider {
flex: 1 1 104px;
width: auto;
min-width: 104px;
padding: 5px 8px;
border: 1px solid var(--border-subtle);
background: color-mix(in srgb, var(--sidebar-hover) 48%, transparent);
border-radius: 9px;
display: grid;
gap: 1px;
}

.session-create-provider-label {
display: block;
color: var(--text-dimmer);
font-size: 9px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.03em;
text-transform: uppercase;
}

.session-create-provider-choice {
display: flex;
align-items: center;
gap: 5px;
min-width: 0;
}

.session-create-provider-name {
overflow: hidden;
color: var(--text-secondary);
font-size: 11px;
font-weight: 600;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}

.session-create-provider-chevron {
display: inline-flex;
margin-left: auto;
}

.session-create-provider[aria-expanded="true"] .session-create-provider-chevron svg {
transform: rotate(180deg);
}

.session-favorites-section {
Expand Down
33 changes: 33 additions & 0 deletions lib/public/modules/session-creation-cta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Shared, presentation-only session creation controls for desktop and mobile.

import { iconHtml } from './icons.js';

export function renderSessionCreationCta(options) {
var row = document.createElement("div");
row.className = options.rowClass + " session-create-cta";

var createButton = document.createElement("button");
createButton.className = options.createClass + " session-create-primary";
createButton.type = "button";
createButton.textContent = "Create session";
createButton.title = options.createTitle;
createButton.setAttribute("aria-label", options.createTitle);
createButton.addEventListener("click", options.onCreate);
row.appendChild(createButton);

var providerButton = document.createElement("button");
providerButton.className = options.providerClass + " session-create-provider";
providerButton.type = "button";
providerButton.title = options.providerTitle;
providerButton.setAttribute("aria-label", "AI provider: " + options.vendorName + ". " + options.providerTitle);
providerButton.setAttribute("aria-expanded", "false");
providerButton.setAttribute("aria-controls", options.menuId);
providerButton.innerHTML = '<span class="session-create-provider-label">AI provider</span>' +
'<span class="session-create-provider-choice"><img src="' + options.vendorAvatar +
'" class="' + options.iconClass + ' session-create-provider-icon" alt=""><span class="session-create-provider-name">' +
options.vendorName + '</span>' + iconHtml("chevron-down", "session-create-provider-chevron") + '</span>';
providerButton.addEventListener("click", options.onProvider);
row.appendChild(providerButton);

return { element: row, createButton: createButton, providerButton: providerButton };
}
71 changes: 41 additions & 30 deletions lib/public/modules/sidebar-mobile.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from './sidebar-sessions.js';
import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperimentalVendor } from './app-rendering.js';
import { defaultVendorUnavailable, resolvePreferredVendor, saveDefaultVendor } from './default-vendor.js';
import { renderSessionCreationCta } from './session-creation-cta.js';
import {
getCachedProjectList,
getCachedCurrentSlug,
Expand Down Expand Up @@ -671,42 +672,49 @@ function createMobileLoopGroup(loopId, children, groupKey) {
}

// Helper: render sorted sessions into a container with date groups (with loop session grouping)
function renderMobileSessionsInto(container) {
// Mirrors the desktop sidebar's split button: one "New session" action on
// the project's last-used vendor, plus a chevron that expands the full
// vendor list. The list expands inline rather than as a fixed-position
// popover because it lives inside a scrolling bottom sheet.
export function renderMobileSessionsInto(container) {
var mobileDefaultVendor = resolvePreferredVendor(store.get('installedVendors') || []) || resolveDefaultVendor();
var mobileInstalled = store.get('installedVendors') || [];
var mobileDefaultState = store.get('defaultVendorState') || {};

var newRow = document.createElement("div");
newRow.className = "mobile-session-new-row";

var mainBtn = document.createElement("button");
mainBtn.className = "mobile-session-new mobile-session-new-main";
mainBtn.title = "New " + (VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor) + " session";
if (defaultVendorUnavailable(mobileInstalled)) mainBtn.title += " (saved default is unavailable; using installed priority)";
mainBtn.innerHTML = '<img src="' + (VENDOR_AVATARS[mobileDefaultVendor] || VENDOR_AVATARS.claude) +
'" class="mobile-session-new-icon" alt=""><span>New ' + (VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor) + ' session</span>';
mainBtn.addEventListener("click", function () {
startNewSession(mobileDefaultVendor);
closeMobileSheet();
});
newRow.appendChild(mainBtn);

var vendorList = document.createElement("div");
vendorList.className = "mobile-vendor-list hidden";

var chevronBtn = document.createElement("button");
chevronBtn.className = "mobile-session-new mobile-session-new-chevron";
chevronBtn.setAttribute("aria-label", "Choose a vendor");
chevronBtn.innerHTML = iconHtml("chevron-down");
chevronBtn.addEventListener("click", function () {
vendorList.classList.toggle("hidden");
chevronBtn.classList.toggle("expanded");
vendorList.id = "mobile-session-provider-menu";
var mobileVendorName = VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor;
var mobileCreateTitle = "Create a " + mobileVendorName + " session";
if (defaultVendorUnavailable(mobileInstalled)) mobileCreateTitle += " (saved default is unavailable; using installed priority)";
var controls;
controls = renderSessionCreationCta({
rowClass: "mobile-session-new-row",
createClass: "mobile-session-new mobile-session-new-main",
providerClass: "mobile-session-new mobile-session-new-provider",
iconClass: "mobile-session-new-icon",
vendorName: mobileVendorName,
vendorAvatar: VENDOR_AVATARS[mobileDefaultVendor] || VENDOR_AVATARS.claude,
createTitle: mobileCreateTitle,
providerTitle: "Choose an AI provider",
menuId: vendorList.id,
onCreate: function () { startNewSession(mobileDefaultVendor); closeMobileSheet(); },
onProvider: function () {
var opening = vendorList.classList.contains("hidden");
vendorList.classList.toggle("hidden", !opening);
controls.providerButton.classList.toggle("expanded", opening);
controls.providerButton.setAttribute("aria-expanded", opening ? "true" : "false");
if (opening) {
var first = vendorList.querySelector("button:not(:disabled)");
if (first) first.focus();
}
},
});
var newRow = controls.element;
vendorList.addEventListener("keydown", function (e) {
if (e.key !== "Escape") return;
e.preventDefault();
vendorList.classList.add("hidden");
controls.providerButton.classList.remove("expanded");
controls.providerButton.setAttribute("aria-expanded", "false");
controls.providerButton.focus();
});
newRow.appendChild(chevronBtn);

if (defaultVendorUnavailable(mobileInstalled) && mobileDefaultState.preference) {
var mobileFallbackNote = document.createElement("div");
Expand All @@ -727,14 +735,17 @@ function renderMobileSessionsInto(container) {
if (!isInstalled) vBtn.classList.add("disabled");
if (vendor === mobileDefaultVendor) vBtn.classList.add("active");
vBtn.innerHTML = '<img src="' + VENDOR_AVATARS[vendor] + '" class="mobile-session-new-icon" alt="">' +
'<span>' + name + '</span>' +
'<span>' + (isInstalled ? "Create with " : "Learn about ") + name + '</span>' +
(isExperimentalVendor(vendor) ? '<span class="vendor-experimental-badge" aria-label="Experimental integration" title="Experimental integration; not yet validated through direct use testing">🧪</span>' : '') +
(isInstalled ? "" : '<span class="mobile-vendor-note">Not installed</span>');
vBtn.addEventListener("click", function () {
if (!isInstalled) {
window.open(VENDOR_HOMEPAGES[vendor], "_blank", "noopener");
return;
}
vendorList.classList.add("hidden");
controls.providerButton.classList.remove("expanded");
controls.providerButton.setAttribute("aria-expanded", "false");
startNewSession(vendor);
closeMobileSheet();
});
Expand Down
Loading
Loading