Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
6cd410f
feat(web): establish redesign primitives and shell
maria-rcks Oct 31, 2026
aa057fc
refactor(web): remove unused toggle pressed tokens
maria-rcks Oct 31, 2026
64b6fb8
fix(web): match catalog button and type specimens
maria-rcks Oct 31, 2026
6c2357d
fix(web): honor reduced motion in overlay primitives
maria-rcks Oct 31, 2026
1e0dc44
fix(web): match shell and overlay design geometry
maria-rcks Oct 31, 2026
2b3e000
fix(web): align project search and sidebar badge sizes
maria-rcks Oct 31, 2026
97ad3cc
fix(web): preserve shell type role and titlebar spacing
maria-rcks Oct 31, 2026
0caf125
fix(web): give provider tiles room for icons and labels
maria-rcks Oct 31, 2026
0377edc
fix(web): match browser address toolbar spacing to design
maria-rcks Oct 31, 2026
c3e5e5d
fix(chat): match inline request cards to the timeline design
maria-rcks Oct 31, 2026
87e9a1b
feat(ui): add relaxed picker spacing and compact key hints
maria-rcks Oct 31, 2026
ff43c9b
fix(ui): use the inset field surface and format picker variants
maria-rcks Oct 31, 2026
35ba5b5
fix(ui): preserve web context menu widths
maria-rcks Oct 31, 2026
eed7620
fix(ui): keep palette actions above provider notices
maria-rcks Oct 31, 2026
b7298d4
fix(ui): fit browser previews inside a landscape floating frame
maria-rcks Oct 31, 2026
dada6cc
fix(web): restore compact panel controls and touch targets
maria-rcks Oct 31, 2026
8727306
feat(web): redesign composer and conversation timeline
maria-rcks Oct 31, 2026
3b760d5
refactor(chat): remove unused work log helpers
maria-rcks Oct 31, 2026
f2976c6
fix(chat): stop tool shine while waiting for input
maria-rcks Oct 31, 2026
7ae082f
fix(chat): keep paused timer clear of question text
maria-rcks Oct 31, 2026
b943a1d
fix(chat): keep approval decisions together on narrow screens
maria-rcks Oct 31, 2026
f57a0ab
fix(chat): match composer corner and send slot dimensions
maria-rcks Oct 31, 2026
df899f2
fix(chat): match the collapsed working window dimensions
maria-rcks Oct 31, 2026
88501ae
fix(chat): acknowledge custom question answers in the footer
maria-rcks Oct 31, 2026
9f2306b
style(chat): format grouped approval actions
maria-rcks Oct 31, 2026
e782dc5
fix(chat): summarize wake-ups and retain the latest answer
maria-rcks Oct 31, 2026
d47dd57
fix(chat): use the pill radius token and accessible wake-up label
maria-rcks Oct 31, 2026
c34eeb9
style(chat): format the composer surface
maria-rcks Oct 31, 2026
5ba02df
fix(chat): align timeline rows and attachment layouts with design
maria-rcks Oct 31, 2026
12586e3
fix(chat): keep row class helper compiler eligible
maria-rcks Oct 31, 2026
278bb5a
fix(chat): expose the existing interrupt action on live work rows
maria-rcks Oct 31, 2026
6a69836
fix(chat): keep interrupt available in grouped live rows
maria-rcks Oct 31, 2026
2551e37
fix(chat): keep turn interrupt off task summary rows
maria-rcks Oct 31, 2026
d39d358
fix(chat): match composer suggestion and picker menus to the design
maria-rcks Oct 31, 2026
572e8ad
fix(chat): keep branch picker separators stable
maria-rcks Oct 31, 2026
4a8be56
fix(chat): let the picker text role own its weight
maria-rcks Oct 31, 2026
eee3b31
fix(chat): match the option c composer and integrated tray
maria-rcks Oct 31, 2026
1bbdfef
fix(chat): validate plan actions and queued reorder commands
maria-rcks Oct 31, 2026
dac2d4b
fix(chat): keep work and notices in the composer tray
maria-rcks Oct 31, 2026
12e987a
fix(chat): reserve the machine chip for remote threads
maria-rcks Oct 31, 2026
2cabfd9
fix(chat): wire approval shortcuts in the composer tray
maria-rcks Oct 31, 2026
b09acdd
fix(chat): settle completed tasks and cap the prompt at eight lines
maria-rcks Oct 31, 2026
fd03676
fix(chat): keep tray work bound to the active run
maria-rcks Oct 31, 2026
1e983a6
fix(chat): accept approval keys from the focused workspace
maria-rcks Oct 31, 2026
b8ffa82
fix(chat): collapse composer controls on narrow panes
maria-rcks Oct 31, 2026
7351555
fix(chat): keep picked attachments above the prompt
maria-rcks Oct 31, 2026
20b5040
fix(chat): preview queue order while dragging
maria-rcks Oct 31, 2026
85bc34b
fix(chat): show the branch picker chevron in the tray
maria-rcks Oct 31, 2026
f3dee8f
fix(chat): stage file rows without an automatic reference
maria-rcks Oct 31, 2026
ecb1367
fix(chat): keep question actions in the tray panel
maria-rcks Oct 31, 2026
5666053
fix(chat): align composer and requests with final design
maria-rcks Oct 31, 2026
a72b703
fix(chat): anchor composer and branch popup to design edges
maria-rcks Oct 31, 2026
241023b
fix(chat): keep approval actions together and remove unused drop overlay
maria-rcks Oct 31, 2026
9e5465e
fix(chat): keep tray border inside composer dimensions
maria-rcks Oct 31, 2026
816f649
fix(chat): reconcile node views outside React lifecycle
maria-rcks Oct 31, 2026
48c2845
fix(chat): preserve synchronous caret updates and popup width
maria-rcks Oct 31, 2026
33301e5
fix(web): Limits opens the usage limits popover directly
flamboh Nov 2, 2026
981bbe6
merge: bring current main into limits popover branch
maria-rcks Nov 3, 2026
37d04ba
fix(web): adapt merged controls to redesign primitives
maria-rcks Nov 3, 2026
4d6a9bb
fix(web): use redesign type roles in merged dialog
maria-rcks Nov 3, 2026
35daa25
merge: move limits popover onto the rebased redesign layer 2
maria-rcks Nov 3, 2026
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
2 changes: 1 addition & 1 deletion apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3114,7 +3114,7 @@ export default function ChatView(props: ChatViewProps) {
onOpenProviderSetup: openProviderSetupStable,
onOpenTerminal: () => setTerminalOpenStable(true),
onProviderModelSelect: onProviderModelSelectStable,
onShowLimits: usageLimitsOffered && usageLimitsKey !== null ? openUsageLimits : null,
limitsAvailable: usageLimitsOffered && usageLimitsKey !== null,
threadId: activeThread?.id ?? null,
threadError: timelineThreadError,
threadErrorClass: threadErrorIsServerRuntimeError
Expand Down
14 changes: 11 additions & 3 deletions apps/web/src/components/chat/ChatBannerLane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function useChatBannerLane(input: {
readonly onOpenProviderSetup: (instanceId: ProviderInstanceId) => void;
readonly onOpenTerminal: () => void;
readonly onProviderModelSelect: (instanceId: ProviderInstanceId, model: string) => void;
readonly onShowLimits: (() => void) | null;
readonly limitsAvailable: boolean;
readonly threadId: ThreadId | null;
readonly threadError: string | null;
readonly threadErrorClass: OrchestrationV2ProviderFailureClass | null;
Expand All @@ -54,7 +54,7 @@ export function useChatBannerLane(input: {
onOpenProviderSetup,
onOpenTerminal,
onProviderModelSelect,
onShowLimits,
limitsAvailable,
threadId,
threadError,
threadErrorClass,
Expand Down Expand Up @@ -106,7 +106,15 @@ export function useChatBannerLane(input: {
switchTarget: lowLimitSwitchTarget,
onSwitch: (target) =>
onProviderModelSelect(target.instanceId, getDefaultServerModel([target], target.driver)),
onShowLimits,
limits:
limitsAvailable && activeProviderStatus !== null
? {
environmentId,
instanceId: activeProviderStatus.instanceId,
providers: providerStatuses,
sources: usageLimitSources,
}
: null,
onDismissed: () => setTrayNoticeDismissTick((tick) => tick + 1),
}),
);
Expand Down
172 changes: 116 additions & 56 deletions apps/web/src/components/chat/ComposerUsageLimits.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type EnvironmentId,
isProviderAvailable,
type ProviderInstanceId,
type ServerProvider,
type ServerProviderUsageWindow,
type UsageLimitSourceSnapshots,
Expand All @@ -9,6 +10,7 @@ import {
import type { TimestampFormat } from "@t3tools/contracts/settings";
import {
collectLimitAccounts,
collectProviderUsageLimits,
formatResetsIn,
formatRunsOutIn,
isLowLimit,
Expand All @@ -17,11 +19,13 @@ import {
tightestSessionLimit,
} from "@t3tools/shared/usageLimits";
import { GaugeIcon } from "lucide-react";
import { useRef, useState } from "react";

import { useNowMinute } from "../../hooks/useNowMinute";
import { ensureLocalApi } from "../../localApi";
import { formatUpcomingTimestamp } from "../../timestampFormat";
import { Button } from "../ui/button";
import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
import { providerClients } from "../settings/providerDriverMeta";
import { RedactedSensitiveText } from "../settings/RedactedSensitiveText";
import { LimitWindows, ResetCredits } from "../usage/UsageLimits";
Expand Down Expand Up @@ -165,63 +169,77 @@ function UsageLimitsBannerBody({
readonly report: UsageLimitsReport;
readonly environmentId: EnvironmentId;
}) {
const now = Date.parse(report.createdAt);
return (
<ComposerBanner.Scroll>
<ComposerBanner.Body className="flex flex-col gap-3 ps-0 pt-0.5 pb-1.5 pe-2">
{report.accounts.map((account) => {
const resetCreditInput =
account.resetCreditInput ??
(account.instanceId ? { instanceId: account.instanceId } : undefined);
const notice = limitsNotice(account.limits);
const externalUsage = account.limits.externalUsage;
return (
<div key={account.id} className="flex min-w-0 flex-col gap-1">
<span className="truncate font-medium text-muted-foreground text-xs">
<AccountSummary account={account} />
</span>
{notice ? (
<span className="text-xs text-muted-foreground">{notice}</span>
) : (
<LimitWindows
compact
driver={account.driver}
accentColor={account.accentColor}
windows={account.limits.windows}
now={now}
/>
)}
{externalUsage ? (
<Button
size="sm"
variant="link"
className="self-start"
onClick={() => void ensureLocalApi().shell.openExternal(externalUsage.url)}
>
Manage usage
</Button>
) : null}
{resetCreditInput && account.limits.resetCredits ? (
<ResetCredits
environmentId={environmentId}
input={resetCreditInput}
credits={account.limits.resetCredits}
now={now}
/>
) : null}
</div>
);
})}
{report.notices.map((notice) => (
<span key={notice} className="text-meta text-muted-foreground">
{notice}
</span>
))}
<UsageLimitsAccounts report={report} environmentId={environmentId} />
</ComposerBanner.Body>
</ComposerBanner.Scroll>
);
}

function UsageLimitsAccounts({
report,
environmentId,
}: {
readonly report: UsageLimitsReport;
readonly environmentId: EnvironmentId;
}) {
const now = Date.parse(report.createdAt);
return (
<>
{report.accounts.map((account) => {
const resetCreditInput =
account.resetCreditInput ??
(account.instanceId ? { instanceId: account.instanceId } : undefined);
const notice = limitsNotice(account.limits);
const externalUsage = account.limits.externalUsage;
return (
<div key={account.id} className="flex min-w-0 flex-col gap-1">
<span className="truncate font-medium text-muted-foreground text-xs">
<AccountSummary account={account} />
</span>
{notice ? (
<span className="text-xs text-muted-foreground">{notice}</span>
) : (
<LimitWindows
compact
driver={account.driver}
accentColor={account.accentColor}
windows={account.limits.windows}
now={now}
/>
)}
{externalUsage ? (
<Button
size="sm"
variant="link"
className="self-start"
onClick={() => void ensureLocalApi().shell.openExternal(externalUsage.url)}
>
Manage usage
</Button>
) : null}
{resetCreditInput && account.limits.resetCredits ? (
<ResetCredits
environmentId={environmentId}
input={resetCreditInput}
credits={account.limits.resetCredits}
now={now}
/>
) : null}
</div>
);
})}
{report.notices.map((notice) => (
<span key={notice} className="text-meta text-muted-foreground">
{notice}
</span>
))}
</>
);
}

/**
* The session a low-limit notice watches: the selected instance's own account
* when it reports, else the tightest account of that driver a hub reports.
Expand Down Expand Up @@ -340,18 +358,64 @@ function LowLimitTiming({ window }: { readonly window: ServerProviderUsageWindow
);
}

export interface LimitsSource {
readonly environmentId: EnvironmentId;
readonly instanceId: ProviderInstanceId;
readonly providers: readonly ServerProvider[];
readonly sources: UsageLimitSourceSnapshots;
}

function LimitsPopover({ source }: { readonly source: LimitsSource }) {
const popupRef = useRef<HTMLDivElement>(null);
return (
<Popover>
<PopoverTrigger render={<Button size="xs" variant="ghost" />}>Limits</PopoverTrigger>
<PopoverPopup
ref={popupRef}
initialFocus={popupRef}
aria-label="Usage limits"
side="top"
align="end"
padding="compact"
className="w-[min(32rem,calc(100vw-2rem))]"
>
<LimitsPopoverReport source={source} />
</PopoverPopup>
</Popover>
);
}

function LimitsPopoverReport({ source }: { readonly source: LimitsSource }) {
const [openedAt] = useState(Date.now);
const report = collectProviderUsageLimits(
source.instanceId,
source.providers,
source.sources,
openedAt,
);
return (
<div className="flex flex-col gap-3">
{report ? (
<UsageLimitsAccounts report={report} environmentId={source.environmentId} />
) : (
<span className="text-meta text-muted-foreground">Usage limits are unavailable</span>
)}
</div>
);
}

/** The notice for a session under a quarter left, with a way over to another provider. */
export function lowLimitBannerItem({
signal,
switchTarget,
onSwitch,
onShowLimits,
limits,
onDismissed,
}: {
readonly signal: LowLimitSignal;
readonly switchTarget: ServerProvider | null;
readonly onSwitch: (provider: ServerProvider) => void;
readonly onShowLimits: (() => void) | null;
readonly limits: LimitsSource | null;
readonly onDismissed: () => void;
}): ComposerBannerStackItem {
return {
Expand All @@ -362,11 +426,7 @@ export function lowLimitBannerItem({
description: <LowLimitTiming window={signal.window} />,
actions: (
<>
{onShowLimits ? (
<Button size="xs" variant="ghost" onClick={onShowLimits}>
Limits
</Button>
) : null}
{limits ? <LimitsPopover source={limits} /> : null}
{switchTarget ? (
<Button size="xs" variant="secondary" onClick={() => onSwitch(switchTarget)}>
Switch to {providerName(switchTarget.driver, switchTarget.displayName)}
Expand Down
Loading