mirror of
https://github.com/andrewyng/openworker.git
synced 2026-08-30 22:53:41 +00:00
Design system: bundled Inter + JetBrains Mono, 6-step type scale, bright canvas
Canvas/chrome surface tokens (content brightest, chrome tinted; dark lifts canvas). Sweep of off-scale font sizes in styles.css and Tailwind arbitrary values. Light-mode contrast fixes for muted/faint/warn ink.
This commit is contained in:
@@ -1572,16 +1572,16 @@ export function App() {
|
||||
className="fixed top-3 right-3 z-[45] w-[290px] bg-panel border border-line rounded-xl shadow-lg px-3.5 pt-3 pb-2.5"
|
||||
data-testid="automation-toast"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[12.5px] font-semibold">
|
||||
<div className="flex items-center gap-2 text-[13px] font-semibold">
|
||||
<span className="w-[7px] h-[7px] rounded-full bg-faint toast-pulse" />
|
||||
Automation started
|
||||
</div>
|
||||
<div className="text-[12.5px] text-muted mt-0.5 ml-[15px] truncate">
|
||||
<div className="text-[13px] text-muted mt-0.5 ml-[15px] truncate">
|
||||
{runToast.title} · {runToast.time} run
|
||||
</div>
|
||||
<div className="flex items-center justify-between ml-[15px] mt-1.5">
|
||||
<button
|
||||
className="text-[12.5px] text-accent font-medium"
|
||||
className="text-[13px] text-accent font-medium"
|
||||
data-testid="toast-view-run"
|
||||
onClick={() => {
|
||||
selectSession(runToast.sessionId, runToast.workspace, runToast.agent);
|
||||
@@ -1836,7 +1836,7 @@ export function App() {
|
||||
it underneath the topbar; owner-reported CSS bug). */}
|
||||
{sessionId.startsWith("__run__") && (
|
||||
<div
|
||||
className="flex items-center gap-2 px-4 py-2 mb-1 rounded-lg text-[12.5px] border border-line bg-accentSoft/40"
|
||||
className="flex items-center gap-2 px-4 py-2 mb-1 rounded-lg text-[13px] border border-line bg-accentSoft/40"
|
||||
data-testid="run-banner"
|
||||
>
|
||||
<Icon name="clock" size={14} className="text-accent shrink-0" />
|
||||
|
||||
@@ -43,7 +43,7 @@ const platformOf = (channel: string) => (channel.includes(":") ? channel.split("
|
||||
|
||||
const SEC_H = "text-[11px] uppercase tracking-[0.05em] text-faint font-semibold";
|
||||
const TAG_CORE =
|
||||
"text-[10px] px-1.5 py-0.5 rounded-full bg-warnSoft/70 text-warnInk border border-warnInk/15";
|
||||
"text-[11px] px-1.5 py-0.5 rounded-full bg-warnSoft/70 text-warnInk border border-warnInk/15";
|
||||
const BTN_ACCENT = "text-[12px] px-2.5 py-1.5 rounded-lg bg-accent text-white shrink-0";
|
||||
const BTN_BORDERED =
|
||||
"text-[12px] px-2.5 py-1.5 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
@@ -297,7 +297,7 @@ export function AccessSection({
|
||||
<div className="flex items-center gap-2 py-1" key={c.connector}>
|
||||
<ConnectorBadge connector={visualFor(c.connector, "connector", byName)} size={24} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[12.5px] font-medium leading-tight truncate">
|
||||
<div className="text-[13px] font-medium leading-tight truncate">
|
||||
<span>{labelFor(c.connector, byName)}</span>
|
||||
{c.detail && <span className="text-faint font-normal"> · {c.detail}</span>}
|
||||
</div>
|
||||
@@ -328,7 +328,7 @@ export function AccessSection({
|
||||
{adding ? (
|
||||
<div className="mt-1.5">
|
||||
<input
|
||||
className="w-full px-2.5 py-1.5 rounded-lg border border-line bg-panel text-[12.5px] outline-none focus:border-accent"
|
||||
className="w-full px-2.5 py-1.5 rounded-lg border border-line bg-panel text-[13px] outline-none focus:border-accent"
|
||||
placeholder="Search connectors…"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
@@ -344,7 +344,7 @@ export function AccessSection({
|
||||
{results.length === 0 && (
|
||||
// Also covers a failed/empty catalog fetch: an open picker must never
|
||||
// be silently blank — point at the Connectors page either way.
|
||||
<div className="text-[11.5px] text-faint mt-1.5 px-0.5">
|
||||
<div className="text-[12px] text-faint mt-1.5 px-0.5">
|
||||
No match — see all on the Connectors page below.
|
||||
</div>
|
||||
)}
|
||||
@@ -363,7 +363,7 @@ export function AccessSection({
|
||||
>
|
||||
<ConnectorBadge connector={visualFor(c.name, "connector", byName)} size={22} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-[12.5px] font-medium leading-tight">
|
||||
<span className="block text-[13px] font-medium leading-tight">
|
||||
{c.title}
|
||||
</span>
|
||||
<span className="block text-[11px] text-faint truncate">{c.blurb}</span>
|
||||
@@ -405,7 +405,7 @@ export function AccessSection({
|
||||
<div className="flex items-center gap-2 py-1" key={r.connector}>
|
||||
<ConnectorBadge connector={visualFor(r.connector, "connector", byName)} size={24} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5 text-[12.5px] font-medium leading-tight">
|
||||
<div className="flex items-center gap-1.5 text-[13px] font-medium leading-tight">
|
||||
<span className="truncate">{labelFor(r.connector, byName)}</span>
|
||||
{r.tier === "core" && <span className={TAG_CORE}>core</span>}
|
||||
</div>
|
||||
@@ -517,7 +517,7 @@ function ConnectInline({
|
||||
</div>
|
||||
{/* Scope semantics, stated once (owner ask 2026-07-13): connecting is account-level,
|
||||
the toggle above is what scopes it to a session. */}
|
||||
<p className="text-[10.5px] text-faint mt-2 leading-snug">
|
||||
<p className="text-[11px] text-faint mt-2 leading-snug">
|
||||
Connecting makes {c.title} available to all your coworkers — the toggle in this list
|
||||
controls just this session.
|
||||
</p>
|
||||
@@ -567,12 +567,12 @@ function ChannelsInline({
|
||||
{channels.map((s) => (
|
||||
<div className="flex items-center gap-1.5 py-1" key={s.channel}>
|
||||
<Icon name="plug" size={13} className="text-muted shrink-0" />
|
||||
<span className="min-w-0 flex-1 text-[12.5px] truncate" title={s.channel}>
|
||||
<span className="min-w-0 flex-1 text-[13px] truncate" title={s.channel}>
|
||||
{s.channel_name ? `#${s.channel_name}` : s.channel}
|
||||
</span>
|
||||
{s.collision && (
|
||||
<span
|
||||
className="text-[10.5px] text-warnInk bg-warnSoft/70 border border-warnInk/15 rounded px-1 shrink-0"
|
||||
className="text-[11px] text-warnInk bg-warnSoft/70 border border-warnInk/15 rounded px-1 shrink-0"
|
||||
title="This channel is also this session's Inbox-routing target — inbound and outbound collide."
|
||||
>
|
||||
⚠
|
||||
@@ -601,7 +601,7 @@ function ChannelsInline({
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-[10.5px] text-faint mt-1.5 leading-snug">
|
||||
<p className="text-[11px] text-faint mt-1.5 leading-snug">
|
||||
The agent receives messages posted to these channels. Removing one stops this session
|
||||
from listening — the connector stays connected.
|
||||
</p>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { PanelHead } from "./IntegrationsView";
|
||||
// filterable, with sanitized arguments.
|
||||
const CARD = "rounded-xl2 border border-line bg-panel";
|
||||
const INPUT = "px-3 py-1.5 rounded-lg border border-line bg-paper text-[13px] text-ink outline-none focus:border-accent";
|
||||
const BTN_ACCENT = "text-[12.5px] px-3 py-1.5 rounded-lg bg-accent text-white shrink-0";
|
||||
const BTN_ACCENT = "text-[13px] px-3 py-1.5 rounded-lg bg-accent text-white shrink-0";
|
||||
|
||||
export function AuditView() {
|
||||
const [events, setEvents] = useState<AuditEvent[]>([]);
|
||||
@@ -66,20 +66,20 @@ function AuditRow({ ev }: { ev: AuditEvent }) {
|
||||
return (
|
||||
<div className={CARD + " p-3.5"}>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-mono text-[12.5px] font-medium text-ink">{ev.tool}</span>
|
||||
<span className="text-[11.5px] text-faint">
|
||||
<span className="font-mono text-[13px] font-medium text-ink">{ev.tool}</span>
|
||||
<span className="text-[12px] text-faint">
|
||||
{ev.connector || "tool"} · {ev.stage || ev.status || "event"} · {ev.timestamp}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-[11.5px] text-muted mt-0.5">
|
||||
<div className="text-[12px] text-muted mt-0.5">
|
||||
session {ev.session_id || "-"} {ev.approval ? `· ${ev.approval}` : ""} {ev.status ? `· ${ev.status}` : ""}
|
||||
</div>
|
||||
{ev.resource && <div className="text-[11.5px] text-faint mt-0.5">resource: {ev.resource}</div>}
|
||||
{ev.resource && <div className="text-[12px] text-faint mt-0.5">resource: {ev.resource}</div>}
|
||||
{ev.args && Object.keys(ev.args).length > 0 && (
|
||||
<div className="font-mono text-[11.5px] text-muted mt-1.5 break-words">{formatAuditArgs(ev.args)}</div>
|
||||
<div className="font-mono text-[12px] text-muted mt-1.5 break-words">{formatAuditArgs(ev.args)}</div>
|
||||
)}
|
||||
{(ev.reason || ev.result_preview) && (
|
||||
<div className="text-[11.5px] text-faint mt-1">{ev.reason || ev.result_preview}</div>
|
||||
<div className="text-[12px] text-faint mt-1">{ev.reason || ev.result_preview}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -301,7 +301,7 @@ export function AutomationQuickstart({
|
||||
|
||||
const label = "block text-[12px] text-muted mt-3 mb-1";
|
||||
const input =
|
||||
"w-full px-3 py-2 rounded-lg border border-line bg-panel text-[13.5px] outline-none focus:border-accent";
|
||||
"w-full px-3 py-2 rounded-lg border border-line bg-panel text-[13px] outline-none focus:border-accent";
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
@@ -323,7 +323,7 @@ export function AutomationQuickstart({
|
||||
}
|
||||
onClick={() => pick(t)}
|
||||
>
|
||||
<span className="text-[13.5px] font-semibold">{t.title}</span>
|
||||
<span className="text-[13px] font-semibold">{t.title}</span>
|
||||
<span className="text-[12px] text-muted leading-relaxed flex-1">{t.blurb}</span>
|
||||
<span className="flex items-center gap-1.5 mt-1">
|
||||
{t.conns.map((c) => {
|
||||
@@ -376,11 +376,11 @@ export function AutomationQuickstart({
|
||||
<div className="flex items-center gap-3 py-2.5">
|
||||
{c && <ConnectorBadge connector={c} size={26} title={c.title} />}
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-[13.5px] font-medium">{c?.title || name}</span>
|
||||
<span className="block text-[11.5px] text-faint">{why}</span>
|
||||
<span className="block text-[13px] font-medium">{c?.title || name}</span>
|
||||
<span className="block text-[12px] text-faint">{why}</span>
|
||||
</span>
|
||||
{c?.connected ? (
|
||||
<span className="text-[12.5px] text-ok">✓ Connected</span>
|
||||
<span className="text-[13px] text-ok">✓ Connected</span>
|
||||
) : flow ? (
|
||||
<span className="inline-flex items-center gap-2 text-[12px] text-muted">
|
||||
<Spinner />
|
||||
@@ -390,7 +390,7 @@ export function AutomationQuickstart({
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
className="px-3.5 py-1 rounded-full border border-line text-[12.5px] hover:bg-paper"
|
||||
className="px-3.5 py-1 rounded-full border border-line text-[13px] hover:bg-paper"
|
||||
onClick={() => startConnect(name)}
|
||||
data-testid={`ob-connect-${name}`}
|
||||
>
|
||||
@@ -427,7 +427,7 @@ export function AutomationQuickstart({
|
||||
|
||||
{pendingConn && !cloud?.signed_in && (
|
||||
<div
|
||||
className="bg-accentSoft/50 rounded-xl px-4 py-3 mt-3 text-[12.5px] text-muted"
|
||||
className="bg-accentSoft/50 rounded-xl px-4 py-3 mt-3 text-[13px] text-muted"
|
||||
data-testid="ob-cloudpane"
|
||||
>
|
||||
<span className="block text-[13px] text-ink font-medium">
|
||||
@@ -442,7 +442,7 @@ export function AutomationQuickstart({
|
||||
{signinPhase === "opening" ? "Opening browser…" : "Waiting for sign-in…"}
|
||||
</span>
|
||||
{signinPhase === "waiting" && (
|
||||
<span className="text-[11.5px] text-faint">
|
||||
<span className="text-[12px] text-faint">
|
||||
Finish signing in in your browser — this page updates by itself.{" "}
|
||||
<button
|
||||
className="underline hover:text-muted"
|
||||
@@ -456,7 +456,7 @@ export function AutomationQuickstart({
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
className="px-3.5 py-1 rounded-full border border-line text-[12.5px] text-accent hover:bg-panel"
|
||||
className="px-3.5 py-1 rounded-full border border-line text-[13px] text-accent hover:bg-panel"
|
||||
onClick={signInThenConnect}
|
||||
data-testid="ob-cloud-signin"
|
||||
>
|
||||
@@ -510,7 +510,7 @@ export function AutomationQuickstart({
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
className="w-28 px-3 py-2 rounded-lg border border-line bg-panel text-[13.5px] outline-none focus:border-accent"
|
||||
className="w-28 px-3 py-2 rounded-lg border border-line bg-panel text-[13px] outline-none focus:border-accent"
|
||||
type="time"
|
||||
aria-label="Time"
|
||||
value={time}
|
||||
@@ -532,7 +532,7 @@ export function AutomationQuickstart({
|
||||
</>
|
||||
)}
|
||||
{picked.consent ? (
|
||||
<label className="flex items-start gap-2.5 mt-3.5 text-[12.5px] text-muted select-none">
|
||||
<label className="flex items-start gap-2.5 mt-3.5 text-[13px] text-muted select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-0.5"
|
||||
@@ -550,7 +550,7 @@ export function AutomationQuickstart({
|
||||
</span>
|
||||
</label>
|
||||
) : picked.conns.length > 0 ? (
|
||||
<p className="text-[12.5px] text-muted mt-3">
|
||||
<p className="text-[13px] text-muted mt-3">
|
||||
This automation only <b className="text-ink">reads</b> on schedule — reading
|
||||
never needs approval.
|
||||
</p>
|
||||
@@ -560,14 +560,14 @@ export function AutomationQuickstart({
|
||||
|
||||
<div className="flex items-center gap-3 mt-4">
|
||||
<button
|
||||
className="text-[12.5px] text-faint hover:text-muted"
|
||||
className="text-[13px] text-faint hover:text-muted"
|
||||
onClick={() => setPickedKey(null)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{/* A silently-disabled primary reads as a bug — always name the missing piece. */}
|
||||
{gateHint && (
|
||||
<span className="ml-auto text-[11.5px] text-faint" data-testid="ob-create-hint">
|
||||
<span className="ml-auto text-[12px] text-faint" data-testid="ob-create-hint">
|
||||
{gateHint}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -452,7 +452,7 @@ export function Composer(props: Props) {
|
||||
{attachNotice && (
|
||||
<div
|
||||
data-testid="attach-notice"
|
||||
className="max-w-3xl mx-auto mb-1.5 flex items-center gap-2 rounded-lg border border-warnInk/30 bg-warnSoft px-3 py-1.5 text-[12.5px] text-warnInk"
|
||||
className="max-w-3xl mx-auto mb-1.5 flex items-center gap-2 rounded-lg border border-warnInk/30 bg-warnSoft px-3 py-1.5 text-[13px] text-warnInk"
|
||||
>
|
||||
<span className="flex-1">{attachNotice}</span>
|
||||
<button
|
||||
@@ -513,7 +513,7 @@ export function Composer(props: Props) {
|
||||
>
|
||||
<span className="text-[13px] font-medium text-accent shrink-0">/{s.name}</span>
|
||||
<span className="text-[12px] text-faint truncate flex-1">{s.description}</span>
|
||||
<span className="text-[10.5px] px-1.5 py-0.5 rounded-full border border-line text-faint shrink-0">
|
||||
<span className="text-[11px] px-1.5 py-0.5 rounded-full border border-line text-faint shrink-0">
|
||||
{s.scope}
|
||||
</span>
|
||||
</button>
|
||||
@@ -523,7 +523,7 @@ export function Composer(props: Props) {
|
||||
)}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className="w-full block px-3.5 pt-3.5 pb-1.5 text-[14.5px]"
|
||||
className="w-full block px-3.5 pt-3.5 pb-1.5 text-[14px]"
|
||||
placeholder={
|
||||
props.gateOpen
|
||||
? "Reply to adjust the proposal — or use the buttons above"
|
||||
@@ -596,7 +596,7 @@ export function Composer(props: Props) {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{dictationBusy === "Transcribing…" && <span className="text-[11.5px] text-accent">Transcribing…</span>}
|
||||
{dictationBusy === "Transcribing…" && <span className="text-[12px] text-accent">Transcribing…</span>}
|
||||
|
||||
<span className="ml-auto" />
|
||||
|
||||
@@ -727,7 +727,7 @@ function UsageChip({
|
||||
// across the whole session, never just the last turn; "Input" is the fresh
|
||||
// (uncached) share — the cached share sits in the cache rows at its own price.
|
||||
const stat = (label: string, value: number) => (
|
||||
<div className="flex items-baseline justify-between text-[11.5px] leading-snug">
|
||||
<div className="flex items-baseline justify-between text-[12px] leading-snug">
|
||||
<span className="text-faint">{label}</span>
|
||||
<span className="text-ink tabular-nums">{formatTokens(value)}</span>
|
||||
</div>
|
||||
@@ -735,7 +735,7 @@ function UsageChip({
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
className="inline-flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11.5px] text-muted hover:text-ink hover:bg-paper shrink-0"
|
||||
className="inline-flex items-center gap-1.5 px-2 py-1 rounded-lg text-[12px] text-muted hover:text-ink hover:bg-paper shrink-0"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
@@ -771,7 +771,7 @@ function UsageChip({
|
||||
>
|
||||
{contextWindow ? (
|
||||
<div className="mb-2.5">
|
||||
<div className="text-[10.5px] uppercase tracking-[0.06em] text-faint font-semibold mb-1">
|
||||
<div className="text-[11px] uppercase tracking-[0.06em] text-faint font-semibold mb-1">
|
||||
Context window
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-line overflow-hidden">
|
||||
@@ -780,16 +780,16 @@ function UsageChip({
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 text-[11.5px] text-muted tabular-nums">
|
||||
<div className="mt-1 text-[12px] text-muted tabular-nums">
|
||||
{formatTokens(usage.context)} of {formatTokens(contextWindow)} · {pct}%
|
||||
</div>
|
||||
</div>
|
||||
) : usage.context > 0 ? (
|
||||
<div className="mb-2.5 text-[11.5px] text-muted tabular-nums">
|
||||
<div className="mb-2.5 text-[12px] text-muted tabular-nums">
|
||||
In context now: {formatTokens(usage.context)} tokens
|
||||
</div>
|
||||
) : null}
|
||||
<div className="text-[10.5px] uppercase tracking-[0.06em] text-faint font-semibold mb-1">
|
||||
<div className="text-[11px] uppercase tracking-[0.06em] text-faint font-semibold mb-1">
|
||||
Session totals
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
@@ -818,12 +818,12 @@ function UsageChip({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-2 pt-2 border-t border-line flex items-baseline justify-between text-[11.5px]">
|
||||
<div className="mt-2 pt-2 border-t border-line flex items-baseline justify-between text-[12px]">
|
||||
<span className="text-faint">Total</span>
|
||||
<span className="text-ink tabular-nums">{formatTokens(total)} tokens</span>
|
||||
</div>
|
||||
{model && !modelLabels?.[model] && contextWindow === undefined && (
|
||||
<div className="mt-1 text-[10.5px] text-faint leading-snug">
|
||||
<div className="mt-1 text-[11px] text-faint leading-snug">
|
||||
Context meter unavailable for custom models.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -77,14 +77,14 @@ export function ConnectorMessageCard({
|
||||
>
|
||||
<ConnectorBadge connector={{ logo: source.connector, brand_color: color }} size={20} title={entry.label} />
|
||||
{showIds ? (
|
||||
<span className="font-mono text-[11.5px] text-faint">{ids}</span>
|
||||
<span className="font-mono text-[12px] text-faint">{ids}</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-[12.5px] font-semibold" style={{ color: "var(--brand)" }}>
|
||||
<span className="text-[13px] font-semibold" style={{ color: "var(--brand)" }}>
|
||||
{source.channel_name}
|
||||
</span>
|
||||
<span className="text-faint">·</span>
|
||||
<span className="text-[12.5px] font-medium">{source.sender_name}</span>
|
||||
<span className="text-[13px] font-medium">{source.sender_name}</span>
|
||||
<span className="text-[11px] text-faint ml-0.5">via {entry.label}</span>
|
||||
</>
|
||||
)}
|
||||
@@ -92,7 +92,7 @@ export function ConnectorMessageCard({
|
||||
{relativeTime(source.ts)}
|
||||
</time>
|
||||
</header>
|
||||
<div className="px-3.5 py-2.5 text-[14.5px] leading-relaxed whitespace-pre-wrap">{source.text}</div>
|
||||
<div className="px-3.5 py-2.5 text-[14px] leading-relaxed whitespace-pre-wrap">{source.text}</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ import { PersonaHero } from "./PersonaHero";
|
||||
|
||||
const CARD = "rounded-xl border border-line bg-panel/60";
|
||||
const BTN_ACCENT =
|
||||
"text-[12.5px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const CHIP = "text-[10.5px] px-1.5 py-0.5 rounded border border-line text-muted";
|
||||
"text-[13px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const CHIP = "text-[11px] px-1.5 py-0.5 rounded border border-line text-muted";
|
||||
|
||||
type Source = "all" | "openworker" | "team";
|
||||
|
||||
@@ -151,7 +151,7 @@ export function GalleryModal({
|
||||
<button
|
||||
key={key}
|
||||
className={
|
||||
"text-[11.5px] px-2.5 py-1 rounded-full border " +
|
||||
"text-[12px] px-2.5 py-1 rounded-full border " +
|
||||
(source === key
|
||||
? "border-accent text-accent bg-accentSoft"
|
||||
: "border-line text-muted hover:border-lineStrong")
|
||||
@@ -164,7 +164,7 @@ export function GalleryModal({
|
||||
</div>
|
||||
|
||||
{unavailable && cloud?.signed_in && (
|
||||
<div className="text-[12.5px] text-muted">
|
||||
<div className="text-[13px] text-muted">
|
||||
The gallery is unreachable right now — try again in a moment.
|
||||
</div>
|
||||
)}
|
||||
@@ -212,13 +212,13 @@ export function GalleryModal({
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="font-semibold text-[13.5px]">{p.name}</span>
|
||||
<span className="font-semibold text-[13px]">{p.name}</span>
|
||||
<span className={CHIP}>{p.family}</span>
|
||||
<span className="text-[11px] text-faint">
|
||||
v{p.version} · {p.publisher}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-[12.5px] text-muted mb-1.5">{p.tagline}</div>
|
||||
<div className="text-[13px] text-muted mb-1.5">{p.tagline}</div>
|
||||
{p.recommended_connectors.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{p.recommended_connectors.map((c) => (
|
||||
@@ -231,14 +231,14 @@ export function GalleryModal({
|
||||
{isInstalled ? (
|
||||
<span className="text-[12px] text-muted">Installed</span>
|
||||
) : (
|
||||
<span className="text-[12.5px] text-accent">View & install →</span>
|
||||
<span className="text-[13px] text-accent">View & install →</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{visible.length === 0 && !unavailable && (
|
||||
<div className="text-[12.5px] text-muted py-4">
|
||||
<div className="text-[13px] text-muted py-4">
|
||||
{source === "team"
|
||||
? "Nothing shared with your team yet."
|
||||
: q
|
||||
@@ -261,31 +261,31 @@ export function GalleryModal({
|
||||
const detailView = detailSlug && (
|
||||
<div data-testid="gallery-detail">
|
||||
<button
|
||||
className="text-[12.5px] text-muted hover:text-ink mb-3"
|
||||
className="text-[13px] text-muted hover:text-ink mb-3"
|
||||
onClick={() => setDetailSlug(null)}
|
||||
>
|
||||
← Gallery
|
||||
</button>
|
||||
{!detail ? (
|
||||
<div className="text-[12.5px] text-muted">Loading…</div>
|
||||
<div className="text-[13px] text-muted">Loading…</div>
|
||||
) : !detail.ok || !card ? (
|
||||
<div className="text-[12.5px] text-danger">{detail.error || "could not load details"}</div>
|
||||
<div className="text-[13px] text-danger">{detail.error || "could not load details"}</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-semibold text-[17px]">{card.name}</span>
|
||||
<span className="font-semibold text-[16px]">{card.name}</span>
|
||||
<span className={CHIP}>{card.family}</span>
|
||||
</div>
|
||||
<div className="text-[13px] text-muted">{card.tagline}</div>
|
||||
<div className="text-[11.5px] text-faint mt-1">
|
||||
<div className="text-[12px] text-faint mt-1">
|
||||
v{card.version} · {card.publisher} · {card.risk_summary}
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
{installed.has(detailSlug) ? (
|
||||
<span className="text-[12.5px] text-muted">Installed</span>
|
||||
<span className="text-[13px] text-muted">Installed</span>
|
||||
) : (
|
||||
<button className={BTN_ACCENT} onClick={() => install(detailSlug)} disabled={busy}>
|
||||
{busy ? "Installing…" : "Install"}
|
||||
@@ -293,11 +293,11 @@ export function GalleryModal({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{msg && <div className="text-[12.5px] text-danger">{msg}</div>}
|
||||
{msg && <div className="text-[13px] text-danger">{msg}</div>}
|
||||
|
||||
{justInstalled && (
|
||||
<div className="rounded-lg border border-okLine bg-okSoft px-3.5 py-2.5 flex items-center gap-3">
|
||||
<span className="flex-1 text-[12.5px] text-ok">
|
||||
<span className="flex-1 text-[13px] text-ok">
|
||||
Installed — it’s waiting in Personas, disabled until you approve and enable it.
|
||||
</span>
|
||||
<button className={BTN_ACCENT} onClick={onClose}>
|
||||
@@ -323,7 +323,7 @@ export function GalleryModal({
|
||||
Read by this app’s own parser, so it matches exactly what the install
|
||||
consent will ask you to approve. No executable code is installed.
|
||||
</div>
|
||||
<div className="space-y-2 text-[12.5px]">
|
||||
<div className="space-y-2 text-[13px]">
|
||||
<div>
|
||||
<span className="text-muted">Tools: </span>
|
||||
{caps.tools.join(", ") || "none"}
|
||||
@@ -352,7 +352,7 @@ export function GalleryModal({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[11.5px] text-faint mt-2">
|
||||
<div className="text-[12px] text-faint mt-2">
|
||||
You connect these yourself (one click when signed in) — installing the
|
||||
coworker grants it nothing until you do.
|
||||
</div>
|
||||
@@ -372,7 +372,7 @@ export function GalleryModal({
|
||||
<div className="absolute left-1/2 top-[6vh] -translate-x-1/2 w-[720px] max-w-[94vw] max-h-[88vh] rounded-xl2 border border-line bg-panel shadow-2xl overflow-hidden flex flex-col">
|
||||
<div className="px-5 pt-4 pb-3 border-b border-line flex items-center gap-3 shrink-0">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[15px] font-semibold">Coworker Gallery</div>
|
||||
<div className="text-[14px] font-semibold">Coworker Gallery</div>
|
||||
<div className="text-[12px] text-muted">
|
||||
Curated coworkers · installs stay disabled until you approve them
|
||||
</div>
|
||||
@@ -382,7 +382,7 @@ export function GalleryModal({
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search coworkers"
|
||||
className="w-[180px] px-3 py-1.5 rounded-lg border border-line bg-paper text-[12.5px] text-ink outline-none focus:border-accent"
|
||||
className="w-[180px] px-3 py-1.5 rounded-lg border border-line bg-paper text-[13px] text-ink outline-none focus:border-accent"
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
@@ -398,7 +398,7 @@ export function GalleryModal({
|
||||
<div className="overflow-y-auto hairline-scroll p-5">
|
||||
{loading ? (
|
||||
<div className="space-y-2" data-testid="gallery-loading" aria-busy="true">
|
||||
<div className="text-[12.5px] text-muted mb-3">Loading the gallery…</div>
|
||||
<div className="text-[13px] text-muted mb-3">Loading the gallery…</div>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<div key={i} className={CARD + " p-3.5 animate-pulse"}>
|
||||
<div className="h-3.5 w-44 rounded bg-line mb-2.5" />
|
||||
@@ -410,7 +410,7 @@ export function GalleryModal({
|
||||
<div className={CARD + " p-5 flex items-center gap-4"} data-testid="gallery-signin">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="font-semibold text-[14px] mb-1">Sign in to browse the Gallery</div>
|
||||
<div className="text-[12.5px] text-muted leading-relaxed">
|
||||
<div className="text-[13px] text-muted leading-relaxed">
|
||||
The Gallery is a curated set of coworkers from OpenWorker Cloud and needs a
|
||||
(free) cloud sign-in. Installing personas from a folder or Git URL — on the
|
||||
Personas page — always works without an account.
|
||||
|
||||
@@ -41,7 +41,7 @@ export function InboxConfigure() {
|
||||
the Inbox now (§28; previously with routing under Connectors, §26). */}
|
||||
<div className="mt-6" data-testid="unrouted-section">
|
||||
<h3 className="text-[14px] font-semibold mb-1">Unrouted</h3>
|
||||
<p className="text-[12.5px] text-muted mb-3">
|
||||
<p className="text-[13px] text-muted mb-3">
|
||||
Inbound messages and background-turn failures nothing claimed — nothing vanishes
|
||||
silently.
|
||||
</p>
|
||||
@@ -123,7 +123,7 @@ function InboxRoutingCard() {
|
||||
|
||||
return (
|
||||
<div className={CARD + " p-4"} data-testid="inbox-mirror-card">
|
||||
<div className="font-semibold text-[13.5px] mb-1">Unattended approvals</div>
|
||||
<div className="font-semibold text-[13px] mb-1">Unattended approvals</div>
|
||||
<p className="text-[12px] text-muted mb-3">
|
||||
Channel where an Unattended session posts Approve/Deny buttons. Currently mirroring to{" "}
|
||||
<strong className="text-ink font-medium" title={target || undefined}>
|
||||
@@ -150,11 +150,11 @@ function InboxRoutingCard() {
|
||||
)}
|
||||
</div>
|
||||
{missingSlackOwner && (
|
||||
<p className="text-[11.5px] text-warnInk mt-2">
|
||||
<p className="text-[12px] text-warnInk mt-2">
|
||||
Choose an approval owner under Integrations → Slack before routing approvals here.
|
||||
</p>
|
||||
)}
|
||||
{error && <p className="text-[11.5px] text-warnInk mt-2">{error}</p>}
|
||||
{error && <p className="text-[12px] text-warnInk mt-2">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -183,7 +183,7 @@ function DmRouteCard() {
|
||||
|
||||
return (
|
||||
<div className={CARD + " p-4"}>
|
||||
<div className="font-semibold text-[13.5px] mb-1">Direct messages</div>
|
||||
<div className="font-semibold text-[13px] mb-1">Direct messages</div>
|
||||
<p className="text-[12px] text-muted mb-3">
|
||||
Session that handles DMs to the bot. With none, DMs park under Unrouted below.
|
||||
</p>
|
||||
@@ -242,7 +242,7 @@ function SubscriptionsCard() {
|
||||
<span className="text-muted shrink-0">
|
||||
<Icon name="plug" size={15} />
|
||||
</span>
|
||||
<span className="font-semibold text-[13.5px]">Channel subscriptions</span>
|
||||
<span className="font-semibold text-[13px]">Channel subscriptions</span>
|
||||
<span className="text-[12px] text-muted">— sessions that listen to a channel (inbound)</span>
|
||||
</div>
|
||||
|
||||
@@ -296,7 +296,7 @@ function SubscriptionsCard() {
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="px-4 py-3 text-[12.5px] text-muted">
|
||||
<div className="px-4 py-3 text-[13px] text-muted">
|
||||
No channel subscriptions yet — add one below or ask a coworker to watch a channel.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -20,13 +20,13 @@ import {
|
||||
// Shared styles (mock parity — same language as SourcesDrawer/PersonaView).
|
||||
const SEC = "text-[11px] uppercase tracking-[0.05em] text-faint font-semibold";
|
||||
const BTN_PRIMARY =
|
||||
"px-3 py-1.5 rounded-lg bg-accent text-white text-[12.5px] font-medium hover:brightness-105 disabled:opacity-40 disabled:hover:brightness-100";
|
||||
"px-3 py-1.5 rounded-lg bg-accent text-white text-[13px] font-medium hover:brightness-105 disabled:opacity-40 disabled:hover:brightness-100";
|
||||
const BTN_BORDERED =
|
||||
"px-3 py-1.5 rounded-lg border border-line bg-paper text-[12.5px] hover:border-lineStrong";
|
||||
"px-3 py-1.5 rounded-lg border border-line bg-paper text-[13px] hover:border-lineStrong";
|
||||
// §35 approval buttons: blue border for the primary, quiet Deny (matches ApprovalCard).
|
||||
const BTN_ACCENT =
|
||||
"px-3 py-1.5 rounded-lg border border-accent text-accent text-[12.5px] font-semibold hover:bg-accentSoft";
|
||||
const BTN_QUIET = "px-3 py-1.5 text-[12.5px] text-faint hover:text-danger";
|
||||
"px-3 py-1.5 rounded-lg border border-accent text-accent text-[13px] font-semibold hover:bg-accentSoft";
|
||||
const BTN_QUIET = "px-3 py-1.5 text-[13px] text-faint hover:text-danger";
|
||||
const OPT_BASE =
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full border text-[13px] transition-colors";
|
||||
const OPT_OFF = "border-line bg-paper text-ink hover:border-accent hover:bg-accentSoft/50";
|
||||
@@ -111,7 +111,7 @@ function QuestionBlock({ spec, onAnswer }: { spec: QSpec; onAnswer: (a: string)
|
||||
const preview = previewIdx >= 0 ? options[previewIdx].preview : "";
|
||||
|
||||
const recommendedTag = (
|
||||
<span className="text-[10px] uppercase tracking-[0.04em] font-semibold text-ok bg-okSoft border border-okLine rounded-full px-1.5 py-px shrink-0">
|
||||
<span className="text-[11px] uppercase tracking-[0.04em] font-semibold text-ok bg-okSoft border border-okLine rounded-full px-1.5 py-px shrink-0">
|
||||
Recommended
|
||||
</span>
|
||||
);
|
||||
@@ -277,7 +277,7 @@ function QuestionCard({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-[15px] font-semibold mt-0.5 leading-snug">{spec.question}</div>
|
||||
<div className="text-[14px] font-semibold mt-0.5 leading-snug">{spec.question}</div>
|
||||
{item.body ? (
|
||||
<div className="text-[13px] text-muted mt-1 whitespace-pre-wrap">{item.body}</div>
|
||||
) : null}
|
||||
@@ -326,7 +326,7 @@ export function InboxItemCard({
|
||||
) : isQuestion ? null : ( // QuestionCard owns its header + title (stepper needs them)
|
||||
<>
|
||||
<div className={SEC}>{item.kind}</div>
|
||||
<div className="text-[15px] font-semibold mt-0.5 leading-snug">{item.title}</div>
|
||||
<div className="text-[14px] font-semibold mt-0.5 leading-snug">{item.title}</div>
|
||||
</>
|
||||
)}
|
||||
{item.kind === "approval" && item.data?.tool === "save_skill" ? (
|
||||
|
||||
@@ -30,7 +30,7 @@ const KIND_TABS: { key: string; label: string }[] = [
|
||||
];
|
||||
|
||||
const CHIP = (active: boolean) =>
|
||||
"text-[11.5px] px-2.5 py-1 rounded-full border " +
|
||||
"text-[12px] px-2.5 py-1 rounded-full border " +
|
||||
(active
|
||||
? "border-accent text-accent bg-accentSoft"
|
||||
: "border-line text-muted hover:border-lineStrong");
|
||||
|
||||
@@ -26,7 +26,7 @@ export function IntegrationsView() {
|
||||
return (
|
||||
<main className="flex-1 min-w-0 flex bg-paper">
|
||||
<nav className="page-subnav w-[208px] shrink-0 border-r border-line bg-panel/40 px-3 py-4">
|
||||
<div className="px-2 text-[13.5px] font-semibold mb-3 flex items-center gap-2">
|
||||
<div className="px-2 text-[13px] font-semibold mb-3 flex items-center gap-2">
|
||||
<Icon name="plug" size={16} /> Connectors
|
||||
</div>
|
||||
<button className="w-full text-left px-2.5 py-2 rounded-lg text-[13px] flex items-center justify-between bg-paper text-accent font-medium">
|
||||
@@ -57,8 +57,8 @@ export function IntegrationsView() {
|
||||
export function PanelHead({ title, sub }: { title: string; sub: string }) {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<h2 className="text-[18px] font-semibold tracking-tight">{title}</h2>
|
||||
<p className="text-[12.5px] text-muted mt-0.5">{sub}</p>
|
||||
<h2 className="text-[20px] font-semibold tracking-tight">{title}</h2>
|
||||
<p className="text-[13px] text-muted mt-0.5">{sub}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ const relTime = (epoch?: number | null): string | null => {
|
||||
// Integrations ▸ Connectors (the MCP tab retired into the Connectors page, UX-034).
|
||||
const SEC_H = "text-[11px] uppercase tracking-[0.05em] text-faint font-semibold";
|
||||
const BTN_BORDERED =
|
||||
"text-[12.5px] px-3 py-1.5 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
const BTN_ACCENT = "text-[12.5px] px-3 py-1.5 rounded-lg bg-accent text-white shrink-0 disabled:opacity-50";
|
||||
"text-[13px] px-3 py-1.5 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
const BTN_ACCENT = "text-[13px] px-3 py-1.5 rounded-lg bg-accent text-white shrink-0 disabled:opacity-50";
|
||||
|
||||
/** Two-letter initials for a chip/avatar (first+last word, else first two chars). */
|
||||
function initials(name: string): string {
|
||||
@@ -85,7 +85,7 @@ export function ModelsTab() {
|
||||
footer={
|
||||
ps.credentialed ? (
|
||||
<button
|
||||
className="text-[12.5px] text-danger/80 hover:text-danger hover:underline underline-offset-2"
|
||||
className="text-[13px] text-danger/80 hover:text-danger hover:underline underline-offset-2"
|
||||
data-testid="set-remove-key"
|
||||
onClick={() => {
|
||||
if (window.confirm(`Remove the ${info?.title} key from this computer?`)) ps.removeKey();
|
||||
@@ -256,10 +256,10 @@ export function UnauthorizedBlock({
|
||||
<span>in {m.chat_name || m.chat_id}</span>
|
||||
<span className="ml-auto shrink-0">{relTime(m.ts) || ""}</span>
|
||||
</div>
|
||||
<div className="text-[12.5px] mt-1 break-words">{m.text}</div>
|
||||
<div className="text-[13px] mt-1 break-words">{m.text}</div>
|
||||
<div className="flex items-center gap-1.5 mt-2">
|
||||
<button
|
||||
className="text-[11.5px] px-2 py-1 rounded-md bg-accent text-white"
|
||||
className="text-[12px] px-2 py-1 rounded-md bg-accent text-white"
|
||||
data-testid={`parked-allow-deliver-${m.id}`}
|
||||
title="Add the sender to the allow-list and deliver this message now"
|
||||
onClick={() => act(m.id, "allow_deliver")}
|
||||
@@ -275,7 +275,7 @@ export function UnauthorizedBlock({
|
||||
Allow only
|
||||
</button>
|
||||
<button
|
||||
className="text-[11.5px] px-2 py-1 rounded-md text-faint hover:text-danger"
|
||||
className="text-[12px] px-2 py-1 rounded-md text-faint hover:text-danger"
|
||||
data-testid={`parked-dismiss-${m.id}`}
|
||||
title="Throw this message away"
|
||||
onClick={() => act(m.id, "dismiss")}
|
||||
@@ -312,7 +312,7 @@ export function ListeningSessionsBlock({ c }: { c: Connector }) {
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{mine.map((s) => (
|
||||
<div className="flex items-center gap-2 text-[12.5px]" key={s.session_id + s.channel}>
|
||||
<div className="flex items-center gap-2 text-[13px]" key={s.session_id + s.channel}>
|
||||
<span className="min-w-0 truncate" title={s.session_id}>
|
||||
{s.session_title || s.session_id}
|
||||
{s.agent ? <span className="text-faint"> · {s.agent}</span> : null}
|
||||
@@ -401,7 +401,7 @@ export function AllowlistBlock({
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{unknownRecent.map((r) => (
|
||||
<div className="flex items-center gap-2 text-[12.5px]" key={r.user_id}>
|
||||
<div className="flex items-center gap-2 text-[13px]" key={r.user_id}>
|
||||
<span className="w-5 h-5 rounded-full bg-paper border border-line grid place-items-center text-[9px] font-bold text-muted shrink-0">
|
||||
{initials(r.user_name || "?")}
|
||||
</span>
|
||||
@@ -409,7 +409,7 @@ export function AllowlistBlock({
|
||||
{r.user_name || "unknown"} <span className="text-faint">· {r.chat_type}</span>
|
||||
</span>
|
||||
<button
|
||||
className="ml-auto text-[11.5px] px-2 py-0.5 rounded-md bg-accent text-white shrink-0"
|
||||
className="ml-auto text-[12px] px-2 py-0.5 rounded-md bg-accent text-white shrink-0"
|
||||
onClick={async () => {
|
||||
await allowUser(c.name, r.user_id, teamId);
|
||||
onChanged();
|
||||
@@ -433,7 +433,7 @@ export function ConnectorTools({ c, onChanged }: { c: Connector; onChanged: () =
|
||||
};
|
||||
if (!c.tools?.length)
|
||||
return (
|
||||
<div className="border-t border-line px-3.5 py-3 text-[12.5px] text-muted">
|
||||
<div className="border-t border-line px-3.5 py-3 text-[13px] text-muted">
|
||||
No tools for this connector yet.
|
||||
</div>
|
||||
);
|
||||
@@ -454,10 +454,10 @@ export function ConnectorTools({ c, onChanged }: { c: Connector; onChanged: () =
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[13px]">{tool.label}</span>
|
||||
<span className="block text-[11.5px] text-faint">
|
||||
<span className="block text-[12px] text-faint">
|
||||
{tool.name} · {tool.kind} · asks approval
|
||||
</span>
|
||||
<span className="block text-[11.5px] text-faint">{tool.description}</span>
|
||||
<span className="block text-[12px] text-faint">{tool.description}</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
@@ -522,7 +522,7 @@ export function ConnectSetup({
|
||||
{waiting ? "Check your browser…" : `Connect ${c.title} with one click`}
|
||||
</button>
|
||||
{c.fields.length > 0 && (
|
||||
<div className="text-[11.5px] text-faint">or connect manually:</div>
|
||||
<div className="text-[12px] text-faint">or connect manually:</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -538,7 +538,7 @@ export function ConnectSetup({
|
||||
Coming soon
|
||||
</span>
|
||||
</button>
|
||||
<div className="text-[11.5px] text-faint">
|
||||
<div className="text-[12px] text-faint">
|
||||
One-click sign-in is coming soon — connect manually below for now:
|
||||
</div>
|
||||
</>
|
||||
@@ -556,12 +556,12 @@ export function ConnectSetup({
|
||||
<CloudStatusPending />
|
||||
)}
|
||||
{!c.managed_paused && cloud?.signed_in && (
|
||||
<div className="text-[11.5px] text-faint">or connect manually:</div>
|
||||
<div className="text-[12px] text-faint">or connect manually:</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{c.instructions.length > 0 && (
|
||||
<ol className="list-decimal pl-4 text-[12.5px] text-muted leading-relaxed space-y-1">
|
||||
<ol className="list-decimal pl-4 text-[13px] text-muted leading-relaxed space-y-1">
|
||||
{c.instructions.map((step, i) => (
|
||||
<li key={i}>{step}</li>
|
||||
))}
|
||||
@@ -588,7 +588,7 @@ export function ConnectSetup({
|
||||
{busy ? "Validating…" : "Connect"}
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ import { Toggle } from "./Toggle";
|
||||
// no scope vocabulary, no markdown, no files. Everything else memory does happens in
|
||||
// chat (toast §5.1, attribution §5.2).
|
||||
const CARD = "rounded-xl2 border border-line bg-panel";
|
||||
const FIELD_LABEL = "text-[12.5px] font-medium text-ink";
|
||||
const FIELD_LABEL = "text-[13px] font-medium text-ink";
|
||||
const FIELD_HELP = "text-[12px] text-muted mt-1.5 leading-relaxed";
|
||||
const BTN_ACCENT =
|
||||
"text-[12.5px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
"text-[13px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
|
||||
export function MemorySection() {
|
||||
const [settings, setSettings] = useState<MemorySettings | null>(null);
|
||||
@@ -101,7 +101,7 @@ export function MemorySection() {
|
||||
</div>
|
||||
</div>
|
||||
{toggleMsg && (
|
||||
<div className="text-[12.5px] text-muted mt-3 pt-3 border-t border-line" data-testid="memory-toggle-msg">
|
||||
<div className="text-[13px] text-muted mt-3 pt-3 border-t border-line" data-testid="memory-toggle-msg">
|
||||
{toggleMsg}
|
||||
</div>
|
||||
)}
|
||||
@@ -128,7 +128,7 @@ export function MemorySection() {
|
||||
they knew when they started.
|
||||
</div>
|
||||
{listMsg && (
|
||||
<div className="text-[12.5px] text-muted mt-2.5" data-testid="memory-list-msg">
|
||||
<div className="text-[13px] text-muted mt-2.5" data-testid="memory-list-msg">
|
||||
{listMsg}
|
||||
</div>
|
||||
)}
|
||||
@@ -199,7 +199,7 @@ function UserRulesCard({
|
||||
Save
|
||||
</button>
|
||||
{savedMsg && (
|
||||
<span className="text-[12.5px] text-muted">
|
||||
<span className="text-[13px] text-muted">
|
||||
Saved — applies to new conversations. Ones you already have open keep the
|
||||
instructions they started with.
|
||||
</span>
|
||||
@@ -245,7 +245,7 @@ function MemoryRow({ entry, onChanged }: { entry: MemoryEntry; onChanged: () =>
|
||||
<button className={BTN_ACCENT} onClick={() => void save()}>
|
||||
Save
|
||||
</button>
|
||||
<button className="text-[12.5px] text-muted hover:text-ink" onClick={() => setEditing(false)}>
|
||||
<button className="text-[13px] text-muted hover:text-ink" onClick={() => setEditing(false)}>
|
||||
cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -119,7 +119,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
{step === 0 && (
|
||||
<section data-testid="ob-step-model" className="flex-1 min-h-0 flex flex-col">
|
||||
{/* Persistent header — stays put while the region below swaps (§39). */}
|
||||
<h1 className="text-[19px] font-semibold">Welcome to OpenWorker<span className="beta-tag">BETA</span></h1>
|
||||
<h1 className="text-[20px] font-semibold">Welcome to OpenWorker<span className="beta-tag">BETA</span></h1>
|
||||
<p className="text-[13px] text-muted mt-0.5 mb-4">
|
||||
Pick a model provider to get started — OpenWorker runs on your own key, and your
|
||||
key and your data stay on this computer.
|
||||
@@ -140,11 +140,11 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
{/* Persistent footer (§39). */}
|
||||
<div className="flex items-center gap-3 pt-5">
|
||||
{!skipConfirm ? (
|
||||
<button className="text-[12.5px] text-faint hover:text-muted" onClick={() => setSkipConfirm(true)}>
|
||||
<button className="text-[13px] text-faint hover:text-muted" onClick={() => setSkipConfirm(true)}>
|
||||
Skip setup
|
||||
</button>
|
||||
) : (
|
||||
<span className="text-[12.5px] text-muted">
|
||||
<span className="text-[13px] text-muted">
|
||||
Nothing works without a model —{" "}
|
||||
<button className="text-accent" onClick={() => finish()}>
|
||||
skip anyway
|
||||
@@ -173,7 +173,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
slot keeps its place but flips to a green congrats, and every row grows a quiet
|
||||
Connect pill. The gated Google pair is ONE combined grayed row. */
|
||||
<section data-testid="ob-step-tools" className="flex-1 min-h-0 flex flex-col">
|
||||
<h1 className="text-[19px] font-semibold">Connect your everyday tools</h1>
|
||||
<h1 className="text-[20px] font-semibold">Connect your everyday tools</h1>
|
||||
<p className="text-[13px] text-muted mt-0.5 mb-3">
|
||||
Chat can only advise. Connected, your coworker does the actual work:
|
||||
</p>
|
||||
@@ -190,7 +190,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
>
|
||||
<ConnectorBadge connector={c} size={34} title={c.title} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-[13.5px] font-semibold leading-tight">{benefit}</span>
|
||||
<span className="block text-[13px] font-semibold leading-tight">{benefit}</span>
|
||||
<span className="block text-[12px] text-muted truncate">{detail}</span>
|
||||
</span>
|
||||
{cloud?.signed_in &&
|
||||
@@ -200,7 +200,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
<span className="text-[12px] text-muted shrink-0">Check your browser…</span>
|
||||
) : (
|
||||
<button
|
||||
className="shrink-0 rounded-full border border-line px-4 py-1.5 text-[12.5px] font-medium hover:border-lineStrong"
|
||||
className="shrink-0 rounded-full border border-line px-4 py-1.5 text-[13px] font-medium hover:border-lineStrong"
|
||||
onClick={() => startTool(name)}
|
||||
>
|
||||
Connect
|
||||
@@ -218,14 +218,14 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
})}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-[13.5px] font-semibold leading-tight text-faint">
|
||||
<span className="block text-[13px] font-semibold leading-tight text-faint">
|
||||
Gmail & Google Calendar
|
||||
</span>
|
||||
<span className="block text-[12px] text-faint truncate">
|
||||
Coming soon — pending Google’s app verification.
|
||||
</span>
|
||||
</span>
|
||||
{cloud?.signed_in && <span className="text-[11.5px] text-faint shrink-0">Coming soon</span>}
|
||||
{cloud?.signed_in && <span className="text-[12px] text-faint shrink-0">Coming soon</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -234,7 +234,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
returns from the browser (§41). */}
|
||||
{!cloud?.signed_in ? (
|
||||
<div className="mt-3.5 rounded-xl border border-line bg-paper px-4 py-3 flex items-center gap-3.5 shrink-0">
|
||||
<span className="flex-1 text-[12.5px] text-muted leading-snug">
|
||||
<span className="flex-1 text-[13px] text-muted leading-snug">
|
||||
<span className="block text-[13px] font-semibold text-ink mb-0.5">
|
||||
Sign in for one-click connections
|
||||
</span>
|
||||
@@ -242,7 +242,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
Tokens stay on this computer.
|
||||
</span>
|
||||
{signinPhase ? (
|
||||
<span className="inline-flex items-center gap-2 text-[12.5px] text-muted shrink-0">
|
||||
<span className="inline-flex items-center gap-2 text-[13px] text-muted shrink-0">
|
||||
<Spinner />
|
||||
{signinPhase === "opening" ? (
|
||||
"Opening browser…"
|
||||
@@ -281,7 +281,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
<span className="block text-[13px] font-semibold text-ok mb-0.5">
|
||||
🎉 You’re signed in{cloud.account ? ` as ${cloud.account}` : ""}
|
||||
</span>
|
||||
<span className="block text-[12.5px] text-muted">
|
||||
<span className="block text-[13px] text-muted">
|
||||
Connect a tool above with one click — or add them anytime later from the
|
||||
Connectors page.
|
||||
</span>
|
||||
@@ -321,7 +321,7 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
<div className="w-12 h-12 rounded-full bg-okSoft text-ok grid place-items-center mx-auto mb-3 text-[22px]">
|
||||
✓
|
||||
</div>
|
||||
<h1 className="text-[19px] font-semibold mb-1">You're set up</h1>
|
||||
<h1 className="text-[20px] font-semibold mb-1">You're set up</h1>
|
||||
<p className="text-[13px] text-muted mb-5">Two good ways to start:</p>
|
||||
</div>
|
||||
|
||||
@@ -330,11 +330,11 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
onClick={() => finish("automations")}
|
||||
data-testid="ob-cta-automation"
|
||||
>
|
||||
<span className="w-9 h-9 rounded-lg bg-accentSoft text-accent grid place-items-center text-[15px] shrink-0">
|
||||
<span className="w-9 h-9 rounded-lg bg-accentSoft text-accent grid place-items-center text-[14px] shrink-0">
|
||||
◷
|
||||
</span>
|
||||
<span className="flex-1 min-w-0 text-left">
|
||||
<b className="block text-[13.5px]">Create your first automation</b>
|
||||
<b className="block text-[13px]">Create your first automation</b>
|
||||
<span className="text-[12px] text-muted">
|
||||
A weekly digest, a morning brief — pick a template, running in two minutes.
|
||||
</span>
|
||||
@@ -346,11 +346,11 @@ export function Onboarding({ onDone }: { onDone: (next?: "work" | "gallery" | "a
|
||||
onClick={() => finish("work")}
|
||||
data-testid="ob-start"
|
||||
>
|
||||
<span className="w-9 h-9 rounded-lg bg-accentSoft text-accent grid place-items-center text-[15px] shrink-0">
|
||||
<span className="w-9 h-9 rounded-lg bg-accentSoft text-accent grid place-items-center text-[14px] shrink-0">
|
||||
✦
|
||||
</span>
|
||||
<span className="flex-1 min-w-0 text-left">
|
||||
<b className="block text-[13.5px]">Start working with Coworker</b>
|
||||
<b className="block text-[13px]">Start working with Coworker</b>
|
||||
<span className="text-[12px] text-muted">
|
||||
Open a session and just ask — analyze files, draft, research, build.
|
||||
</span>
|
||||
|
||||
@@ -31,8 +31,8 @@ import { indexConnectors, labelFor, visualFor, type ConnectorMap } from "../conn
|
||||
|
||||
const SEC_H = "text-[11px] uppercase tracking-[0.05em] text-faint font-semibold";
|
||||
const TAG_CORE =
|
||||
"text-[10px] px-1.5 py-0.5 rounded-full bg-warnSoft/70 text-warnInk border border-warnInk/15";
|
||||
const TAG_MCP = "text-[10px] px-1.5 py-0.5 rounded border border-line text-faint";
|
||||
"text-[11px] px-1.5 py-0.5 rounded-full bg-warnSoft/70 text-warnInk border border-warnInk/15";
|
||||
const TAG_MCP = "text-[11px] px-1.5 py-0.5 rounded border border-line text-faint";
|
||||
const BTN_ACCENT = "text-[12px] px-2.5 py-1.5 rounded-lg bg-accent text-white shrink-0";
|
||||
const BTN_BORDERED =
|
||||
"text-[12px] px-2.5 py-1.5 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0 disabled:opacity-40";
|
||||
@@ -117,7 +117,7 @@ export function PersonaView({
|
||||
{onBack && (
|
||||
<>
|
||||
<button
|
||||
className="inline-flex items-center gap-1 text-[12.5px] text-muted hover:text-ink"
|
||||
className="inline-flex items-center gap-1 text-[13px] text-muted hover:text-ink"
|
||||
onClick={onBack}
|
||||
>
|
||||
<Icon name="arrowLeft" size={15} /> Back
|
||||
@@ -252,7 +252,7 @@ export function PersonaView({
|
||||
<section>
|
||||
<div className={`${SEC_H} mb-1.5 flex items-baseline`}>
|
||||
<span>Connectors</span>
|
||||
<span className="ml-auto flex font-semibold text-[10.5px] text-faint normal-case tracking-normal">
|
||||
<span className="ml-auto flex font-semibold text-[11px] text-faint normal-case tracking-normal">
|
||||
<span className={COL_STATUS}>Status</span>
|
||||
<span className={COL_ENABLE}>Enable</span>
|
||||
</span>
|
||||
@@ -340,7 +340,7 @@ export function PersonaView({
|
||||
)}
|
||||
|
||||
{/* defaults footer */}
|
||||
<section className="flex flex-wrap gap-x-8 gap-y-2 text-[12.5px]">
|
||||
<section className="flex flex-wrap gap-x-8 gap-y-2 text-[13px]">
|
||||
{detail.recommended_models.length > 0 && (
|
||||
<div>
|
||||
<span className="text-faint">Models</span> ·{" "}
|
||||
@@ -364,7 +364,7 @@ export function PersonaView({
|
||||
</section>
|
||||
|
||||
{/* management — the controls that left the list page (UX-035) */}
|
||||
<section className="border-t border-line pt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-[12.5px]">
|
||||
<section className="border-t border-line pt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-[13px]">
|
||||
<label className="flex items-center gap-2 text-muted select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -408,7 +408,7 @@ export function PersonaView({
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
className="text-[12.5px] text-danger/80 hover:text-danger"
|
||||
className="text-[13px] text-danger/80 hover:text-danger"
|
||||
data-testid="persona-delete"
|
||||
onClick={() => setConfirmDel(true)}
|
||||
>
|
||||
|
||||
@@ -20,12 +20,12 @@ const CARD = "rounded-xl2 border border-line bg-panel";
|
||||
const SELECT = "px-2.5 py-2 rounded-lg border border-line bg-paper text-[13px] text-ink shrink-0";
|
||||
const INPUT =
|
||||
"flex-1 min-w-0 px-3 py-2 rounded-lg border border-line bg-paper text-[13px] text-ink outline-none focus:border-accent";
|
||||
const BTN_ACCENT = "text-[12.5px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const BTN_ACCENT = "text-[13px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const BTN_BORDERED =
|
||||
"text-[12.5px] px-2.5 py-1.5 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0 disabled:opacity-40 disabled:hover:border-line";
|
||||
"text-[13px] px-2.5 py-1.5 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0 disabled:opacity-40 disabled:hover:border-line";
|
||||
|
||||
const QUIET_ROW =
|
||||
"w-full flex items-center gap-2 px-4 pt-2 mt-6 text-[12.5px] text-muted select-none";
|
||||
"w-full flex items-center gap-2 px-4 pt-2 mt-6 text-[13px] text-muted select-none";
|
||||
|
||||
export function PersonasTab({ onOpenPersona }: { onOpenPersona?: (id: string) => void }) {
|
||||
const [personas, setPersonas] = useState<Persona[]>([]);
|
||||
@@ -332,7 +332,7 @@ export function PersonasTab({ onOpenPersona }: { onOpenPersona?: (id: string) =>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{msg && <div className="text-[12.5px] text-muted mt-2.5">{msg}</div>}
|
||||
{msg && <div className="text-[13px] text-muted mt-2.5">{msg}</div>}
|
||||
|
||||
{consent && consent.length > 0 && (
|
||||
<div className="mt-4 space-y-2" data-testid="consent-review">
|
||||
@@ -340,7 +340,7 @@ export function PersonasTab({ onOpenPersona }: { onOpenPersona?: (id: string) =>
|
||||
are a one-line summary with the exact tools under a collapsed chevron. A
|
||||
coworker runs no third-party code, so this list is complete — but a prompt
|
||||
still steers an agent, so who it came from genuinely matters. */}
|
||||
<div className="flex items-start gap-2.5 rounded-xl border border-warnInk/30 bg-warnSoft px-3.5 py-2.5 text-[12.5px] text-warnInk">
|
||||
<div className="flex items-start gap-2.5 rounded-xl border border-warnInk/30 bg-warnSoft px-3.5 py-2.5 text-[13px] text-warnInk">
|
||||
<Icon name="shield" size={15} className="shrink-0 mt-0.5" />
|
||||
<span>
|
||||
Only enable coworkers from someone you trust. Nothing here runs third-party
|
||||
@@ -389,7 +389,7 @@ function ConsentCard({
|
||||
const recommends = c.recommends || [];
|
||||
return (
|
||||
<div className={CARD + " p-3.5"} data-testid={`consent-${c.id}`}>
|
||||
<div className="text-[13.5px] font-medium flex items-center gap-2">
|
||||
<div className="text-[13px] font-medium flex items-center gap-2">
|
||||
<span>{c.name}</span>
|
||||
{c.version && <span className="text-[11px] text-faint font-normal">v{c.version}</span>}
|
||||
</div>
|
||||
@@ -404,7 +404,7 @@ function ConsentCard({
|
||||
: " Same capabilities as before — it stays enabled."}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-[12.5px] text-ink mt-2">
|
||||
<div className="text-[13px] text-ink mt-2">
|
||||
Can {summary}
|
||||
{c.connectors === "all"
|
||||
? " · use ALL your connected services"
|
||||
@@ -438,7 +438,7 @@ function ConsentCard({
|
||||
{/* Enable right here (owner ask 2026-08-11) — the old "enable it above" copy
|
||||
sent the user hunting back up the list. */}
|
||||
{enabled ? (
|
||||
<span className="text-[12.5px] text-muted" data-testid="consent-enabled">
|
||||
<span className="text-[13px] text-muted" data-testid="consent-enabled">
|
||||
✓ Enabled — it's in your coworker picker.
|
||||
</span>
|
||||
) : (
|
||||
|
||||
@@ -126,7 +126,7 @@ export function ScheduledView({ onOpenRun, onRunNow, initialOpenId }: Props) {
|
||||
<PanelHead title="Automations" sub="Recurring tasks OpenWorker runs on a schedule." />
|
||||
</div>
|
||||
<button
|
||||
className="text-[12.5px] px-3 py-1.5 rounded-lg border border-lineStrong bg-panel hover:border-accent hover:text-accent shrink-0"
|
||||
className="text-[13px] px-3 py-1.5 rounded-lg border border-lineStrong bg-panel hover:border-accent hover:text-accent shrink-0"
|
||||
onClick={() => setShowForm((v) => !v)}
|
||||
>
|
||||
+ New automation
|
||||
@@ -155,7 +155,7 @@ export function ScheduledView({ onOpenRun, onRunNow, initialOpenId }: Props) {
|
||||
|
||||
{empty ? (
|
||||
!showForm && (
|
||||
<div className={CARD + " p-4 text-[12.5px] text-muted"}>
|
||||
<div className={CARD + " p-4 text-[13px] text-muted"}>
|
||||
No scheduled tasks yet — use a template above, click <strong>+ New automation</strong>,
|
||||
or just ask OpenWorker in a session.
|
||||
</div>
|
||||
@@ -169,7 +169,7 @@ export function ScheduledView({ onOpenRun, onRunNow, initialOpenId }: Props) {
|
||||
onClick={() => setOpenId(t.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2.5 mb-1">
|
||||
<span className="text-[13.5px] font-semibold truncate">{t.title}</span>
|
||||
<span className="text-[13px] font-semibold truncate">{t.title}</span>
|
||||
<button
|
||||
className="sched-card-del"
|
||||
title="Delete automation"
|
||||
@@ -379,7 +379,7 @@ function TaskDetail({
|
||||
placeholder="Title"
|
||||
/>
|
||||
) : (
|
||||
<h2 className="text-[18px] font-semibold tracking-tight">{task.title}</h2>
|
||||
<h2 className="text-[20px] font-semibold tracking-tight">{task.title}</h2>
|
||||
)}
|
||||
<div className="sched-actions">
|
||||
{editing ? (
|
||||
|
||||
@@ -96,12 +96,12 @@ export function SearchModal({
|
||||
onMouseEnter={() => setActive(idx)}
|
||||
onClick={() => choose(s)}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate text-[13.5px] text-ink">
|
||||
<span className="min-w-0 flex-1 truncate text-[13px] text-ink">
|
||||
{s.title || s.session_id}
|
||||
</span>
|
||||
<span className="text-[12px] text-faint shrink-0">{tagFor(s)}</span>
|
||||
{idx < 9 && (
|
||||
<kbd className="text-[10.5px] text-faint bg-paper border border-line rounded px-1.5 py-0.5 shrink-0 font-sans">
|
||||
<kbd className="text-[11px] text-faint bg-paper border border-line rounded px-1.5 py-0.5 shrink-0 font-sans">
|
||||
⌘{idx + 1}
|
||||
</kbd>
|
||||
)}
|
||||
@@ -120,9 +120,9 @@ export function SearchModal({
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search chats"
|
||||
className="flex-1 bg-transparent outline-none text-[15px] text-ink placeholder:text-faint"
|
||||
className="flex-1 bg-transparent outline-none text-[14px] text-ink placeholder:text-faint"
|
||||
/>
|
||||
<kbd className="text-[10.5px] text-faint bg-paper border border-line rounded px-1.5 py-0.5 font-sans">
|
||||
<kbd className="text-[11px] text-faint bg-paper border border-line rounded px-1.5 py-0.5 font-sans">
|
||||
Esc
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ export function SelectMenu({
|
||||
o.group && o.group !== options[i - 1]?.group ? (
|
||||
<div
|
||||
className={
|
||||
"px-2.5 pb-1 text-[10.5px] uppercase tracking-[0.06em] text-faint font-semibold " +
|
||||
"px-2.5 pb-1 text-[11px] uppercase tracking-[0.06em] text-faint font-semibold " +
|
||||
(i === 0 ? "pt-1" : "pt-2.5 mt-1.5 border-t border-line")
|
||||
}
|
||||
>
|
||||
@@ -92,7 +92,7 @@ export function SelectMenu({
|
||||
>
|
||||
{o.label}
|
||||
</span>
|
||||
{o.sub && <span className="block text-[11.5px] text-faint truncate">{o.sub}</span>}
|
||||
{o.sub && <span className="block text-[12px] text-faint truncate">{o.sub}</span>}
|
||||
</span>
|
||||
{sel && <span className="text-accent text-[12px] shrink-0">✓</span>}
|
||||
<span
|
||||
|
||||
@@ -51,10 +51,10 @@ export function SendFolderDialog({ coworkerName, onPick, onTemp, onCancel }: Pro
|
||||
data-testid="send-folder-dialog"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className="text-[14.5px] font-semibold text-ink mb-1">
|
||||
<h3 className="text-[14px] font-semibold text-ink mb-1">
|
||||
Where should {coworkerName} work?
|
||||
</h3>
|
||||
<p className="text-[12.5px] text-muted mb-3">
|
||||
<p className="text-[13px] text-muted mb-3">
|
||||
Code work happens inside a folder — pick your project, or start somewhere temporary.
|
||||
</p>
|
||||
{recents
|
||||
@@ -68,20 +68,20 @@ export function SendFolderDialog({ coworkerName, onPick, onTemp, onCancel }: Pro
|
||||
title={w.path}
|
||||
>
|
||||
<Icon name="folder" size={13} className="shrink-0 text-muted" />
|
||||
<span className="text-[12.5px] text-ink truncate">{baseName(w.path)}</span>
|
||||
<span className="ml-auto text-[11.5px] text-faint truncate max-w-[45%]">{w.path}</span>
|
||||
<span className="text-[13px] text-ink truncate">{baseName(w.path)}</span>
|
||||
<span className="ml-auto text-[12px] text-faint truncate max-w-[45%]">{w.path}</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="flex gap-2 mt-3">
|
||||
<button
|
||||
className="flex-1 text-center text-[12.5px] px-2.5 py-2 rounded-lg border border-lineStrong text-ink hover:bg-paper"
|
||||
className="flex-1 text-center text-[13px] px-2.5 py-2 rounded-lg border border-lineStrong text-ink hover:bg-paper"
|
||||
onClick={() => void browse()}
|
||||
disabled={busy}
|
||||
>
|
||||
Choose a folder…
|
||||
</button>
|
||||
<button
|
||||
className="flex-1 text-center text-[12.5px] px-2.5 py-2 rounded-lg bg-accent text-white font-semibold hover:opacity-95"
|
||||
className="flex-1 text-center text-[13px] px-2.5 py-2 rounded-lg bg-accent text-white font-semibold hover:opacity-95"
|
||||
data-testid="start-temp-folder"
|
||||
onClick={() => {
|
||||
if (busy) return;
|
||||
@@ -93,7 +93,7 @@ export function SendFolderDialog({ coworkerName, onPick, onTemp, onCancel }: Pro
|
||||
Use temporary folder
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="mt-2 text-[11.5px] text-warnInk">{error}</div>}
|
||||
{error && <div className="mt-2 text-[12px] text-warnInk">{error}</div>}
|
||||
<p className="text-[11px] text-faint mt-2.5">
|
||||
A temporary folder is created only when you send, with git ready — you can save it as a
|
||||
project later. Your message sends as soon as you choose.
|
||||
|
||||
@@ -57,7 +57,7 @@ export function SessionSetupRow(props: Props) {
|
||||
};
|
||||
|
||||
const chip =
|
||||
"relative inline-flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-[12.5px] text-muted hover:text-ink hover:bg-paper cursor-pointer select-none whitespace-nowrap";
|
||||
"relative inline-flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-[13px] text-muted hover:text-ink hover:bg-paper cursor-pointer select-none whitespace-nowrap";
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto mb-1.5 px-1 flex items-center gap-1.5" data-testid="setup-row">
|
||||
@@ -87,7 +87,7 @@ export function SessionSetupRow(props: Props) {
|
||||
{fullPersonaName(p.name, p.id)}
|
||||
</span>
|
||||
{p.tagline && (
|
||||
<span className="block text-[11.5px] text-muted truncate">{p.tagline}</span>
|
||||
<span className="block text-[12px] text-muted truncate">{p.tagline}</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
@@ -139,7 +139,7 @@ export function SessionSetupRow(props: Props) {
|
||||
<Icon name="folder" size={13} className="mt-0.5 shrink-0 text-muted" />
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[13px] font-medium text-ink truncate">{baseName(w.path)}</span>
|
||||
<span className="block text-[11.5px] text-faint truncate">{w.path}</span>
|
||||
<span className="block text-[12px] text-faint truncate">{w.path}</span>
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
@@ -151,7 +151,7 @@ export function SessionSetupRow(props: Props) {
|
||||
Choose another folder…
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="px-2.5 py-1 text-[11.5px] text-warnInk">{error}</div>}
|
||||
{error && <div className="px-2.5 py-1 text-[12px] text-warnInk">{error}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -54,13 +54,13 @@ import { showPersonas } from "../flags";
|
||||
type SetTab = "appearance" | "models" | "context" | "skills" | "voice" | "memory" | "personas";
|
||||
|
||||
const CARD = "rounded-xl2 border border-line bg-panel";
|
||||
const FIELD_LABEL = "text-[12.5px] font-medium text-ink";
|
||||
const FIELD_LABEL = "text-[13px] font-medium text-ink";
|
||||
const FIELD_HELP = "text-[12px] text-muted mt-1.5 leading-relaxed";
|
||||
const INPUT =
|
||||
"flex-1 min-w-0 px-3 py-2 rounded-lg border border-line bg-paper text-[13px] text-ink outline-none focus:border-accent";
|
||||
const BTN_ACCENT = "text-[12.5px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const BTN_ACCENT = "text-[13px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const BTN_BORDERED =
|
||||
"text-[12.5px] px-3 py-2 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
"text-[13px] px-3 py-2 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
|
||||
const SET_TABS: {
|
||||
key: SetTab;
|
||||
@@ -98,7 +98,7 @@ export function SettingsView({
|
||||
return (
|
||||
<main className="flex-1 min-w-0 flex bg-paper">
|
||||
<nav className="page-subnav w-[208px] shrink-0 border-r border-line bg-panel/40 px-3 py-4">
|
||||
<div className="px-2 text-[13.5px] font-semibold mb-3 flex items-center gap-2">
|
||||
<div className="px-2 text-[13px] font-semibold mb-3 flex items-center gap-2">
|
||||
<Icon name="gear" size={16} /> Settings
|
||||
</div>
|
||||
{tabs.map((t) => {
|
||||
@@ -289,7 +289,7 @@ function VoiceInputSection() {
|
||||
<div className={CARD + " p-4 text-[13px] text-muted"}>Voice Input setup is available in the OpenWorker desktop app.</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-green-200 bg-green-50/70 px-4 py-3 text-[12.5px] text-green-800">
|
||||
<div className="rounded-xl border border-green-200 bg-green-50/70 px-4 py-3 text-[13px] text-green-800">
|
||||
<span className="font-medium">Private by design.</span> Audio is held in memory only while you record and is transcribed locally.
|
||||
</div>
|
||||
|
||||
@@ -297,12 +297,12 @@ function VoiceInputSection() {
|
||||
<div className="p-4 flex items-start gap-3">
|
||||
<Icon name="code" size={18} className="text-accent mt-0.5" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[13.5px] font-medium">This device</div>
|
||||
<div className="text-[13px] font-medium">This device</div>
|
||||
<div className="text-[12px] text-muted mt-1">{status?.device_summary || "Checking compatibility…"}</div>
|
||||
{status?.compatibility_reason && <div className="text-[12px] text-red-600 mt-1.5">{status.compatibility_reason}</div>}
|
||||
</div>
|
||||
{status && (
|
||||
<span className={"text-[11.5px] px-2 py-1 rounded-full " + (status.supported ? "bg-green-50 text-green-700" : "bg-red-50 text-red-600")}>
|
||||
<span className={"text-[12px] px-2 py-1 rounded-full " + (status.supported ? "bg-green-50 text-green-700" : "bg-red-50 text-red-600")}>
|
||||
{status.supported ? "● Compatible" : "Unsupported"}
|
||||
</span>
|
||||
)}
|
||||
@@ -319,14 +319,14 @@ function VoiceInputSection() {
|
||||
<div className="p-4 flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-accentSoft text-accent grid place-items-center font-semibold">W</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[13.5px] font-medium">Whisper Base · English</div>
|
||||
<div className="text-[13px] font-medium">Whisper Base · English</div>
|
||||
<div className="text-[12px] text-muted mt-0.5">
|
||||
{status?.model_verified ? `Installed and verified · ${formatBytes(status.model_bytes)}` : `Local voice model · ${formatBytes(status?.model_bytes || 147_964_211)}`}
|
||||
</div>
|
||||
</div>
|
||||
{status?.model_verified ? (
|
||||
<>
|
||||
<span className="text-[11.5px] px-2 py-1 rounded-full bg-green-50 text-green-700">Verified</span>
|
||||
<span className="text-[12px] px-2 py-1 rounded-full bg-green-50 text-green-700">Verified</span>
|
||||
<button className={BTN_BORDERED} onClick={() => void repair()}>Repair</button>
|
||||
<button className="text-[12px] text-red-600 px-2 py-2" onClick={() => void remove()}>Delete</button>
|
||||
</>
|
||||
@@ -341,7 +341,7 @@ function VoiceInputSection() {
|
||||
{downloading && (
|
||||
<div className="border-t border-line px-4 py-3">
|
||||
<div className="h-1.5 rounded-full bg-line overflow-hidden"><div className="h-full bg-accent transition-all" style={{ width: `${progressPercent}%` }} /></div>
|
||||
<div className="mt-1.5 text-[11.5px] text-muted flex"><span>{formatBytes(progress?.downloaded_bytes || 0)} of {formatBytes(progressTotal)}</span><span className="ml-auto">{progressPercent}%</span></div>
|
||||
<div className="mt-1.5 text-[12px] text-muted flex"><span>{formatBytes(progress?.downloaded_bytes || 0)} of {formatBytes(progressTotal)}</span><span className="ml-auto">{progressPercent}%</span></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -350,12 +350,12 @@ function VoiceInputSection() {
|
||||
<div className="p-4 flex items-center gap-3">
|
||||
<Icon name="mic" size={18} className={ready ? "text-green-600" : "text-muted"} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[13.5px] font-medium">Microphone test</div>
|
||||
<div className="text-[13px] font-medium">Microphone test</div>
|
||||
<div className="text-[12px] text-muted mt-0.5">
|
||||
{ready ? "Your microphone and local transcription engine are working." : "Record a short phrase to enable the composer microphone."}
|
||||
</div>
|
||||
</div>
|
||||
{ready && <span className="text-[11.5px] px-2 py-1 rounded-full bg-green-50 text-green-700">● Ready</span>}
|
||||
{ready && <span className="text-[12px] px-2 py-1 rounded-full bg-green-50 text-green-700">● Ready</span>}
|
||||
<button className={BTN_BORDERED} disabled={!status?.supported || !status?.model_verified || phase === "transcribing"} onClick={() => void toggleTest()}>
|
||||
{status?.recording ? "Stop and check" : phase === "transcribing" ? "Transcribing…" : ready ? "Test again" : "Test microphone"}
|
||||
</button>
|
||||
@@ -506,8 +506,8 @@ function TrustedWorkspacesCard() {
|
||||
{workspaces.map((workspace) => (
|
||||
<div key={workspace.workspace} className="py-2.5 flex items-start gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[12.5px] text-ink break-all">{workspace.workspace}</div>
|
||||
<div className="text-[11.5px] text-muted mt-0.5">
|
||||
<div className="text-[13px] text-ink break-all">{workspace.workspace}</div>
|
||||
<div className="text-[12px] text-muted mt-0.5">
|
||||
{workspace.requested_commands.length
|
||||
? `${workspace.requested_commands.length} project command allowance${workspace.requested_commands.length === 1 ? "" : "s"}`
|
||||
: "No project command allowances currently declared"}
|
||||
@@ -668,7 +668,7 @@ function TokenSavingsCard() {
|
||||
className="w-16 px-2 py-1.5 rounded-lg border border-line bg-paper text-[13px] text-ink outline-none focus:border-accent"
|
||||
onChange={(e) => save({ pdf_max_mb: Math.max(1, Math.min(Number(e.target.value) || 10, 10)) })}
|
||||
/>
|
||||
<span className="text-[12.5px] text-muted">MB</span>
|
||||
<span className="text-[13px] text-muted">MB</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={FIELD_HELP}>
|
||||
@@ -741,7 +741,7 @@ function CompactionCard() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-[12.5px] text-muted">% of the context window</span>
|
||||
<span className="text-[13px] text-muted">% of the context window</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2.5">
|
||||
<span className="text-[13px] text-ink">or at</span>
|
||||
@@ -762,7 +762,7 @@ function CompactionCard() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-[12.5px] text-muted">tokens, whichever is smaller</span>
|
||||
<span className="text-[13px] text-muted">tokens, whichever is smaller</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={FIELD_HELP}>
|
||||
@@ -936,7 +936,7 @@ function FilesCard() {
|
||||
Each conversation gets its own folder under this location. Existing conversations keep their current
|
||||
folder; you can grant access to more folders inside any conversation.
|
||||
</div>
|
||||
{scratchMsg && <div className="text-[12.5px] text-muted mt-2.5">{scratchMsg}</div>}
|
||||
{scratchMsg && <div className="text-[13px] text-muted mt-2.5">{scratchMsg}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ function AttnBadge({ n }: { n: number }) {
|
||||
if (!n) return null;
|
||||
return (
|
||||
<span
|
||||
className="text-[10px] font-semibold text-ink bg-faint/30 rounded-full px-1.5 leading-[15px] shrink-0"
|
||||
className="text-[11px] font-semibold text-ink bg-faint/30 rounded-full px-1.5 leading-[15px] shrink-0"
|
||||
title={`${n} awaiting your attention`}
|
||||
>
|
||||
{n > 99 ? "99+" : n}
|
||||
@@ -63,7 +63,7 @@ function UnseenBadge({ n, failed }: { n: number; failed?: boolean }) {
|
||||
if (!n) return null;
|
||||
return (
|
||||
<span
|
||||
className="text-[10px] font-semibold text-ink bg-faint/30 rounded-full px-1.5 leading-[15px] shrink-0"
|
||||
className="text-[11px] font-semibold text-ink bg-faint/30 rounded-full px-1.5 leading-[15px] shrink-0"
|
||||
title={failed ? `${n} new run${n > 1 ? "s" : ""} — the latest failed` : `${n} new run${n > 1 ? "s" : ""}`}
|
||||
>
|
||||
{n > 99 ? "99+" : n}
|
||||
@@ -351,7 +351,7 @@ export function Sidebar(props: Props) {
|
||||
<button
|
||||
className={
|
||||
"w-full flex items-center gap-2.5 px-3 py-1.5 text-[13px] text-left " +
|
||||
(active ? "text-ink bg-paper" : "hover:bg-paper")
|
||||
(active ? "text-ink bg-chromeHover" : "hover:bg-chromeHover")
|
||||
}
|
||||
onClick={() => {
|
||||
setAppMenuOpen(false);
|
||||
@@ -439,7 +439,7 @@ export function Sidebar(props: Props) {
|
||||
const menuOpen = rowMenu?.id === s.session_id;
|
||||
const item = (testid: string, icon: IconName, label: string, onClick: () => void) => (
|
||||
<button
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[12.5px] text-left hover:bg-paper"
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[13px] text-left hover:bg-paper"
|
||||
data-testid={testid}
|
||||
role="menuitem"
|
||||
onClick={() => {
|
||||
@@ -464,8 +464,8 @@ export function Sidebar(props: Props) {
|
||||
aria-expanded={menuOpen}
|
||||
data-testid="row-menu"
|
||||
className={
|
||||
"w-5 h-5 grid place-items-center rounded hover:bg-paper " +
|
||||
(menuOpen ? "text-ink bg-paper" : "text-faint hover:text-ink")
|
||||
"w-5 h-5 grid place-items-center rounded hover:bg-chromeHover " +
|
||||
(menuOpen ? "text-ink bg-chromeHover" : "text-faint hover:text-ink")
|
||||
}
|
||||
onClick={(e) => (menuOpen ? closeRowMenu() : openRowMenu(s.session_id, e.currentTarget))}
|
||||
>
|
||||
@@ -494,7 +494,7 @@ export function Sidebar(props: Props) {
|
||||
{confirmDelId === s.session_id ? (
|
||||
<button
|
||||
title="Click again to permanently delete"
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[12.5px] text-left font-medium text-danger hover:bg-paper"
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[13px] text-left font-medium text-danger hover:bg-paper"
|
||||
data-testid="row-menu-delete"
|
||||
role="menuitem"
|
||||
onClick={() => {
|
||||
@@ -507,7 +507,7 @@ export function Sidebar(props: Props) {
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[12.5px] text-left text-danger hover:bg-paper"
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[13px] text-left text-danger hover:bg-paper"
|
||||
data-testid="row-menu-delete"
|
||||
role="menuitem"
|
||||
onClick={() => setConfirmDelId(s.session_id)}
|
||||
@@ -613,7 +613,7 @@ export function Sidebar(props: Props) {
|
||||
"group w-full flex items-center gap-2.5 px-2 py-2 rounded-lg cursor-pointer text-left " +
|
||||
(active
|
||||
? "bg-ink/[0.055]"
|
||||
: "hover:bg-paper")
|
||||
: "hover:bg-chromeHover")
|
||||
}
|
||||
title={editing ? undefined : title}
|
||||
onClick={() => {
|
||||
@@ -665,7 +665,7 @@ export function Sidebar(props: Props) {
|
||||
const pinnedBand = () =>
|
||||
pinnedSessions.length > 0 ? (
|
||||
<div>
|
||||
<div className="px-1.5 text-[10.5px] uppercase tracking-[0.07em] text-faint font-semibold mb-1">
|
||||
<div className="px-1.5 text-[11px] uppercase tracking-[0.07em] text-faint font-semibold mb-1">
|
||||
Pinned
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
@@ -680,14 +680,14 @@ export function Sidebar(props: Props) {
|
||||
const scheduledBand = () =>
|
||||
automations.length > 0 ? (
|
||||
<div data-testid="scheduled-band">
|
||||
<div className="px-1.5 text-[10.5px] uppercase tracking-[0.07em] text-faint font-semibold mb-1">
|
||||
<div className="px-1.5 text-[11px] uppercase tracking-[0.07em] text-faint font-semibold mb-1">
|
||||
Scheduled
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
{automations.map((a) => (
|
||||
<button
|
||||
key={a.id}
|
||||
className="w-full flex items-center gap-2 px-1.5 py-1 rounded-lg text-left hover:bg-paper"
|
||||
className="w-full flex items-center gap-2 px-1.5 py-1 rounded-lg text-left hover:bg-chromeHover"
|
||||
data-testid={`scheduled-${a.id}`}
|
||||
title={a.title}
|
||||
onClick={() => props.onOpenAutomation(a.id)}
|
||||
@@ -712,11 +712,11 @@ export function Sidebar(props: Props) {
|
||||
);
|
||||
return (
|
||||
<div className="relative flex items-center justify-between px-1.5 mb-1" data-testid="recent-header">
|
||||
<span className="text-[10.5px] uppercase tracking-[0.07em] text-faint font-semibold">
|
||||
<span className="text-[11px] uppercase tracking-[0.07em] text-faint font-semibold">
|
||||
Recent
|
||||
</span>
|
||||
<button
|
||||
className="w-6 h-6 grid place-items-center rounded-md text-faint hover:text-ink hover:bg-paper -mr-1"
|
||||
className="w-6 h-6 grid place-items-center rounded-md text-faint hover:text-ink hover:bg-chromeHover -mr-1"
|
||||
title="Group & filter conversations"
|
||||
aria-label="Group and filter conversations"
|
||||
onClick={() => setGroupMenuOpen((v) => !v)}
|
||||
@@ -731,7 +731,7 @@ export function Sidebar(props: Props) {
|
||||
role="menu"
|
||||
data-testid="group-filter-menu"
|
||||
>
|
||||
<div className="px-2 pt-1 pb-1 text-[10.5px] uppercase tracking-[0.06em] text-faint font-semibold">
|
||||
<div className="px-2 pt-1 pb-1 text-[11px] uppercase tracking-[0.06em] text-faint font-semibold">
|
||||
Group by
|
||||
</div>
|
||||
{([["grouped", "Coworker"], ["flat", "Chronological"]] as ["flat" | "grouped", string][]).map(
|
||||
@@ -750,7 +750,7 @@ export function Sidebar(props: Props) {
|
||||
<>
|
||||
<div className="my-1 border-t border-line" />
|
||||
<div className="px-2 pt-1 pb-1 flex items-center justify-between">
|
||||
<span className="text-[10.5px] uppercase tracking-[0.06em] text-faint font-semibold">
|
||||
<span className="text-[11px] uppercase tracking-[0.06em] text-faint font-semibold">
|
||||
Filter by coworker
|
||||
</span>
|
||||
{filterPersonas.size > 0 && (
|
||||
@@ -870,7 +870,7 @@ export function Sidebar(props: Props) {
|
||||
{/* Codex-style Projects: a "+" header affordance, then collapsible folders whose
|
||||
rows carry a right-aligned compact age and truncate to PROJECT_PEEK + "Show more". */}
|
||||
<div className="flex items-center justify-between px-1.5 pt-1">
|
||||
<span className="text-[10.5px] uppercase tracking-[0.07em] text-faint font-semibold">
|
||||
<span className="text-[11px] uppercase tracking-[0.07em] text-faint font-semibold">
|
||||
Projects
|
||||
</span>
|
||||
<button
|
||||
@@ -912,7 +912,7 @@ export function Sidebar(props: Props) {
|
||||
<Icon name="folder" size={15} className="shrink-0" />
|
||||
<span
|
||||
className={
|
||||
"truncate min-w-0 text-[12.5px] " + (isActive ? "font-semibold" : "font-medium")
|
||||
"truncate min-w-0 text-[13px] " + (isActive ? "font-semibold" : "font-medium")
|
||||
}
|
||||
>
|
||||
{baseName(proj)}
|
||||
@@ -994,7 +994,7 @@ export function Sidebar(props: Props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="sidebar flex flex-col min-h-0 bg-panel border-r border-line"
|
||||
className="sidebar flex flex-col min-h-0 bg-chrome border-r border-line"
|
||||
onMouseLeave={props.onPeekLeave}
|
||||
>
|
||||
{/* Header: collapse/pin control FIRST + wordmark. The pin sits at the same screen position
|
||||
@@ -1005,7 +1005,7 @@ export function Sidebar(props: Props) {
|
||||
{/* Collapse (dock) / pin the sidebar. ⌘B mirrors this. */}
|
||||
{props.onCollapse && (
|
||||
<button
|
||||
className="nav-pin-btn w-7 h-7 grid place-items-center rounded-md text-faint hover:text-ink hover:bg-paper shrink-0"
|
||||
className="nav-pin-btn w-7 h-7 grid place-items-center rounded-md text-faint hover:text-ink hover:bg-chromeHover shrink-0"
|
||||
title={props.collapsed ? "Dock sidebar (⌘B)" : "Collapse sidebar (⌘B)"}
|
||||
aria-label={props.collapsed ? "Dock sidebar" : "Collapse sidebar"}
|
||||
onClick={props.onCollapse}
|
||||
@@ -1013,7 +1013,7 @@ export function Sidebar(props: Props) {
|
||||
<Icon name="sidebar" size={16} />
|
||||
</button>
|
||||
)}
|
||||
<div className="brand-wordmark text-[15px]">OpenWorker<span className="beta-tag">BETA</span></div>
|
||||
<div className="brand-wordmark text-[14px]">OpenWorker<span className="beta-tag">BETA</span></div>
|
||||
</div>
|
||||
|
||||
{/* New session: a plain button — the coworker pick moved to the composer's setup
|
||||
@@ -1032,7 +1032,7 @@ export function Sidebar(props: Props) {
|
||||
SearchModal over the whole app. Matches the bottom-nav rows to reduce the boxy look. */}
|
||||
<div className="px-2.5 mt-1">
|
||||
<button
|
||||
className="w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[13px] text-left text-muted hover:bg-paper hover:text-ink"
|
||||
className="w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[13px] text-left text-muted hover:bg-chromeHover hover:text-ink"
|
||||
onClick={() => setSearchModalOpen(true)}
|
||||
>
|
||||
<Icon name="search" size={15} className="shrink-0" /> Search
|
||||
@@ -1044,8 +1044,8 @@ export function Sidebar(props: Props) {
|
||||
<div className="px-2.5 mt-1">
|
||||
<button
|
||||
className={
|
||||
"w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[13px] text-left hover:bg-paper hover:text-ink " +
|
||||
(props.scheduledActive ? "text-ink bg-paper" : "text-muted")
|
||||
"w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[13px] text-left hover:bg-chromeHover hover:text-ink " +
|
||||
(props.scheduledActive ? "text-ink bg-chromeHover" : "text-muted")
|
||||
}
|
||||
data-testid="nav-automations"
|
||||
onClick={props.onOpenScheduled}
|
||||
@@ -1072,7 +1072,7 @@ export function Sidebar(props: Props) {
|
||||
// the block with its sessions (one connected group). Collapsed = a plain row.
|
||||
<div
|
||||
key={s.key}
|
||||
className={expanded ? "rounded-xl bg-paper/70 overflow-hidden" : ""}
|
||||
className={expanded ? "rounded-xl bg-chromeHover/70 overflow-hidden" : ""}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
@@ -1080,8 +1080,8 @@ export function Sidebar(props: Props) {
|
||||
(expanded
|
||||
? ""
|
||||
: isCurrent(s.key)
|
||||
? "rounded-lg bg-paper"
|
||||
: "rounded-lg hover:bg-paper")
|
||||
? "rounded-lg bg-chromeHover"
|
||||
: "rounded-lg hover:bg-chromeHover")
|
||||
}
|
||||
onClick={() => onHeaderClick(s.key)}
|
||||
>
|
||||
@@ -1219,7 +1219,7 @@ export function Sidebar(props: Props) {
|
||||
<button
|
||||
className={
|
||||
"w-full flex items-center gap-2 px-2.5 py-2 rounded-lg text-[13px] text-left " +
|
||||
(appMenuOpen ? "bg-paper text-ink" : "hover:bg-paper")
|
||||
(appMenuOpen ? "bg-chromeHover text-ink" : "hover:bg-chromeHover")
|
||||
}
|
||||
data-testid="account-row"
|
||||
onClick={() => {
|
||||
@@ -1232,10 +1232,10 @@ export function Sidebar(props: Props) {
|
||||
>
|
||||
<span
|
||||
className={
|
||||
"w-6 h-6 rounded-full grid place-items-center text-[10.5px] font-semibold shrink-0 " +
|
||||
"w-6 h-6 rounded-full grid place-items-center text-[11px] font-semibold shrink-0 " +
|
||||
(cloud?.signed_in
|
||||
? "bg-accentSoft text-accent"
|
||||
: "bg-paper text-faint border border-line")
|
||||
: "bg-panel text-faint border border-line")
|
||||
}
|
||||
aria-hidden
|
||||
>
|
||||
@@ -1255,7 +1255,7 @@ export function Sidebar(props: Props) {
|
||||
{inboxUnlocked && (
|
||||
<span
|
||||
className={
|
||||
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] shrink-0 cursor-pointer " +
|
||||
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[12px] shrink-0 cursor-pointer " +
|
||||
(totalAttention > 0
|
||||
? "bg-accentSoft text-accent font-semibold"
|
||||
: "text-faint hover:text-ink")
|
||||
|
||||
@@ -22,13 +22,13 @@ import { Icon } from "./Icon";
|
||||
// Persona-bundled skills arrive with personas (§10), managed on the persona page, not here.
|
||||
|
||||
const CARD = "rounded-xl2 border border-line bg-panel";
|
||||
const FIELD_LABEL = "text-[12.5px] font-medium text-ink";
|
||||
const FIELD_LABEL = "text-[13px] font-medium text-ink";
|
||||
const INPUT =
|
||||
"w-full min-w-0 px-3 py-2 rounded-lg border border-line bg-paper text-[13px] text-ink outline-none focus:border-accent";
|
||||
const BTN_ACCENT =
|
||||
"text-[12.5px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
"text-[13px] px-3 py-2 rounded-lg bg-accent text-white shrink-0 disabled:opacity-40";
|
||||
const BTN_BORDERED =
|
||||
"text-[12.5px] px-3 py-2 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
"text-[13px] px-3 py-2 rounded-lg border border-line bg-paper hover:border-lineStrong shrink-0";
|
||||
const BADGE =
|
||||
"text-[11px] px-2 py-0.5 rounded-full border border-line bg-paper text-muted shrink-0";
|
||||
|
||||
@@ -165,7 +165,7 @@ export function SkillsTab({
|
||||
<div className="flex items-start justify-between gap-3 mb-4">
|
||||
<div>
|
||||
<h2 className="text-[16px] font-semibold">Skills</h2>
|
||||
<p className="text-[12.5px] text-muted mt-1 leading-relaxed">
|
||||
<p className="text-[13px] text-muted mt-1 leading-relaxed">
|
||||
Reusable instructions the worker can follow in every conversation. Off here means
|
||||
off everywhere.
|
||||
</p>
|
||||
@@ -199,7 +199,7 @@ export function SkillsTab({
|
||||
}}
|
||||
>
|
||||
<div className="text-[13px] font-medium">Write it myself</div>
|
||||
<div className="text-[11.5px] text-muted">
|
||||
<div className="text-[12px] text-muted">
|
||||
A name, a description, and the instructions
|
||||
</div>
|
||||
</button>
|
||||
@@ -212,7 +212,7 @@ export function SkillsTab({
|
||||
}}
|
||||
>
|
||||
<div className="text-[13px] font-medium">Import a file</div>
|
||||
<div className="text-[11.5px] text-muted">
|
||||
<div className="text-[12px] text-muted">
|
||||
A .zip or SKILL.md someone shared — you review before it installs
|
||||
</div>
|
||||
</button>
|
||||
@@ -226,7 +226,7 @@ export function SkillsTab({
|
||||
}}
|
||||
>
|
||||
<div className="text-[13px] font-medium">Create with OpenWorker</div>
|
||||
<div className="text-[11.5px] text-muted">
|
||||
<div className="text-[12px] text-muted">
|
||||
Starts a conversation — the worker builds it and asks before adding it to
|
||||
your skills
|
||||
</div>
|
||||
@@ -249,7 +249,7 @@ export function SkillsTab({
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
<div className="text-[12.5px] text-red-500 mb-3" role="alert">
|
||||
<div className="text-[13px] text-red-500 mb-3" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -257,7 +257,7 @@ export function SkillsTab({
|
||||
<div
|
||||
role="status"
|
||||
className={
|
||||
"mb-3 flex items-start gap-2 rounded-lg border px-3 py-2 text-[12.5px] " +
|
||||
"mb-3 flex items-start gap-2 rounded-lg border px-3 py-2 text-[13px] " +
|
||||
(notice.tone === "ok"
|
||||
? "bg-tealSoft/70 text-tealInk border-tealInk/20"
|
||||
: "bg-warnSoft/70 text-warnInk border-warnInk/20")
|
||||
@@ -279,7 +279,7 @@ export function SkillsTab({
|
||||
{upload ? (
|
||||
<div className={`${CARD} p-4 mb-4`}>
|
||||
<div className="text-[13px] font-medium mb-1">Review before installing</div>
|
||||
<p className="text-[12.5px] text-muted mb-3">
|
||||
<p className="text-[13px] text-muted mb-3">
|
||||
Read the instructions — installing a skill means the worker will follow them.
|
||||
</p>
|
||||
<div className="text-[13px] mb-1">
|
||||
|
||||
@@ -186,7 +186,7 @@ export function ChannelPicker({
|
||||
inputRef.current?.blur();
|
||||
}}
|
||||
>
|
||||
<span className="text-[12.5px] text-ink">
|
||||
<span className="text-[13px] text-ink">
|
||||
{c.name ? `#${c.name}` : c.channel}
|
||||
</span>
|
||||
{c.name && <span className="ml-1.5 text-[11px] text-faint">{c.channel}</span>}
|
||||
@@ -226,7 +226,7 @@ export function ChannelPicker({
|
||||
inputRef.current?.blur();
|
||||
}}
|
||||
>
|
||||
<span className="text-[12.5px] text-ink">
|
||||
<span className="text-[13px] text-ink">
|
||||
{r.is_private ? "🔒 " : "#"}
|
||||
{r.name}
|
||||
</span>
|
||||
|
||||
@@ -21,7 +21,7 @@ function ClampedUserText({ text }: { text: string }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
className="block ml-auto mt-1.5 text-[12.5px] font-medium opacity-75 hover:opacity-100"
|
||||
className="block ml-auto mt-1.5 text-[13px] font-medium opacity-75 hover:opacity-100"
|
||||
>
|
||||
{open ? "less…" : "more…"}
|
||||
</button>
|
||||
@@ -51,7 +51,7 @@ function BubbleMeta({ text, ts, align }: { text: string; ts?: number; align: "le
|
||||
<div className="relative h-0 select-none">
|
||||
<div
|
||||
className={
|
||||
"absolute top-1 flex items-center gap-1.5 text-[10.5px] leading-none text-faint whitespace-nowrap opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity " +
|
||||
"absolute top-1 flex items-center gap-1.5 text-[11px] leading-none text-faint whitespace-nowrap opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity " +
|
||||
(align === "right" ? "right-0" : "left-0")
|
||||
}
|
||||
>
|
||||
@@ -157,10 +157,10 @@ function buildRows(items: TurnItem[]): TurnRow[] {
|
||||
|
||||
function approvalChip(resolved: ApprovalDecision | undefined) {
|
||||
if (resolved === "deny")
|
||||
return <span className="text-[10.5px] px-1.5 rounded-full bg-dangerSoft text-danger shrink-0">✕ declined</span>;
|
||||
return <span className="text-[11px] px-1.5 rounded-full bg-dangerSoft text-danger shrink-0">✕ declined</span>;
|
||||
return (
|
||||
<span
|
||||
className="text-[10.5px] px-1.5 rounded-full bg-okSoft text-ok shrink-0"
|
||||
className="text-[11px] px-1.5 rounded-full bg-okSoft text-ok shrink-0"
|
||||
title={resolved ? `approved · ${resolved.replace(/_/g, " ")}` : "approved"}
|
||||
>
|
||||
✓ approved
|
||||
@@ -185,7 +185,7 @@ function StepRow({ tool, approval }: { tool: ToolItem; approval?: ApprovalItem }
|
||||
return (
|
||||
<div>
|
||||
<div className="group flex items-baseline gap-2 px-2 py-0.5 rounded-lg hover:bg-paper" data-testid="turn-step">
|
||||
<span className={"w-3.5 text-center text-[10px] shrink-0 " + (failed ? "text-danger" : running ? "text-accent" : "text-ok")}>
|
||||
<span className={"w-3.5 text-center text-[11px] shrink-0 " + (failed ? "text-danger" : running ? "text-accent" : "text-ok")}>
|
||||
{running ? <span className="spinner" data-testid="step-running" /> : "●"}
|
||||
</span>
|
||||
<LineText
|
||||
@@ -200,7 +200,7 @@ function StepRow({ tool, approval }: { tool: ToolItem; approval?: ApprovalItem }
|
||||
{approval && approvalChip(approval.resolved)}
|
||||
{!!tool.standingRule && (
|
||||
<span
|
||||
className="text-[10.5px] px-1.5 rounded-full bg-tealSoft text-tealInk shrink-0"
|
||||
className="text-[11px] px-1.5 rounded-full bg-tealSoft text-tealInk shrink-0"
|
||||
data-testid="tool-standing-rule"
|
||||
title={`Auto-allowed by this automation's standing approval: ${tool.standingRule}. Revoke on its Automations page.`}
|
||||
>
|
||||
@@ -227,7 +227,7 @@ function StepRow({ tool, approval }: { tool: ToolItem; approval?: ApprovalItem }
|
||||
)}
|
||||
</div>
|
||||
{raw && (
|
||||
<pre className="ml-8 mr-2 my-1 px-2.5 py-1.5 rounded-lg border border-line bg-paper font-mono text-[11.5px] leading-relaxed text-muted whitespace-pre-wrap break-words max-h-56 overflow-auto">
|
||||
<pre className="ml-8 mr-2 my-1 px-2.5 py-1.5 rounded-lg border border-line bg-paper font-mono text-[12px] leading-relaxed text-muted whitespace-pre-wrap break-words max-h-56 overflow-auto">
|
||||
{`${tool.name} ${shortArgs(tool.args)}`}
|
||||
{tool.preview ? `\n→ ${tool.preview.length > 1500 ? tool.preview.slice(0, 1500) + "\n…" : tool.preview}` : ""}
|
||||
</pre>
|
||||
@@ -265,7 +265,7 @@ function TurnGroup({
|
||||
return (
|
||||
<details className="stepgroup" open={open}>
|
||||
<summary
|
||||
className="stepgroup-head flex items-center gap-2 py-0.5 cursor-pointer select-none text-[12.5px] text-faint hover:text-muted"
|
||||
className="stepgroup-head flex items-center gap-2 py-0.5 cursor-pointer select-none text-[13px] text-faint hover:text-muted"
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // drive open/closed from state, not the native toggle
|
||||
setUserToggle(!open);
|
||||
@@ -306,7 +306,7 @@ function TurnGroup({
|
||||
</div>
|
||||
) : row.type === "ask" ? (
|
||||
<div className="flex items-baseline gap-2 px-2 py-0.5" key={i} data-testid="turn-ask">
|
||||
<span className={"w-3.5 text-center text-[10px] shrink-0 " + (row.approval.resolved === "deny" ? "text-danger" : "text-ok")}>●</span>
|
||||
<span className={"w-3.5 text-center text-[11px] shrink-0 " + (row.approval.resolved === "deny" ? "text-danger" : "text-ok")}>●</span>
|
||||
<LineText line={humanizeAsk(row.approval.name, row.approval.args)} />
|
||||
{approvalChip(row.approval.resolved)}
|
||||
</div>
|
||||
@@ -426,7 +426,7 @@ export function Transcript({ items, running, streamingText, onRetry, onUndoMemor
|
||||
case "user":
|
||||
return (
|
||||
<div className="group self-end max-w-[78%] flex flex-col items-end" key={bi}>
|
||||
<div className="bubble-user px-3.5 py-2.5 rounded-[14px_14px_4px_14px] bg-solid text-onSolid text-[14.5px] leading-relaxed whitespace-pre-wrap">
|
||||
<div className="bubble-user px-3.5 py-2.5 rounded-[14px_14px_4px_14px] bg-solid text-onSolid text-[14px] leading-relaxed whitespace-pre-wrap">
|
||||
{item.attachments && item.attachments.length > 0 && (
|
||||
<div className="bubble-attachments">
|
||||
{item.attachments.map((a, i) =>
|
||||
|
||||
@@ -93,13 +93,13 @@ export function UpdateBanner() {
|
||||
OpenWorker v{update.version} is ready to install.
|
||||
</div>
|
||||
{phase === "error" && (
|
||||
<div className="text-[11.5px] text-warnInk mt-1.5">
|
||||
<div className="text-[12px] text-warnInk mt-1.5">
|
||||
The update couldn't be installed — it will be offered again next launch.
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 mt-2.5">
|
||||
<button
|
||||
className="px-3 py-1.5 rounded-full bg-accent text-white text-[12.5px] disabled:opacity-50"
|
||||
className="px-3 py-1.5 rounded-full bg-accent text-white text-[13px] disabled:opacity-50"
|
||||
onClick={install}
|
||||
disabled={busy}
|
||||
data-testid="update-install"
|
||||
@@ -107,7 +107,7 @@ export function UpdateBanner() {
|
||||
{busy ? "Downloading…" : "Restart to update"}
|
||||
</button>
|
||||
<button
|
||||
className="px-2 py-1.5 text-[12.5px] text-faint hover:text-muted"
|
||||
className="px-2 py-1.5 text-[13px] text-faint hover:text-muted"
|
||||
onClick={() => {
|
||||
dismissed.current = update.version;
|
||||
setUpdate(null);
|
||||
|
||||
@@ -35,12 +35,12 @@ export function WorkspaceTrustPrompt({
|
||||
</p>
|
||||
<div className="rounded-lg border border-line bg-paper px-3 py-2.5 max-h-48 overflow-y-auto">
|
||||
{request.requested_commands.map((command) => (
|
||||
<code key={command} className="block text-[12.5px] py-1 text-ink">
|
||||
<code key={command} className="block text-[13px] py-1 text-ink">
|
||||
{command}
|
||||
</code>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[11.5px] text-muted mt-2 break-all">{request.workspace}</div>
|
||||
<div className="text-[12px] text-muted mt-2 break-all">{request.workspace}</div>
|
||||
{error && <div className="gate-error">{error}</div>}
|
||||
<div className="gate-foot justify-end gap-2">
|
||||
<button className="btn" onClick={onClose} disabled={saving}>
|
||||
|
||||
@@ -38,7 +38,7 @@ export function AccountsDetail({ c, cloud, slack: _slack, onChanged }: DetailPro
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">
|
||||
{c.title}
|
||||
</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
{c.connected ? (
|
||||
<>
|
||||
<span className="w-2 h-2 rounded-full bg-ok" />
|
||||
|
||||
@@ -65,7 +65,7 @@ export function AddConnectionModal({
|
||||
<div className="flex-1 font-semibold text-[16px] tracking-tight">
|
||||
{title || `Connect ${c.title}`}
|
||||
</div>
|
||||
<button className="text-faint hover:text-ink text-[18px] leading-none" onClick={onClose} title="Close">
|
||||
<button className="text-faint hover:text-ink text-[20px] leading-none" onClick={onClose} title="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@ export function AddConnectionModal({
|
||||
{twoModes ? (
|
||||
<>
|
||||
<div className="px-5 pt-4">
|
||||
<div className="inline-flex rounded-full p-0.5 bg-paper text-[12.5px] font-medium">
|
||||
<div className="inline-flex rounded-full p-0.5 bg-paper text-[13px] font-medium">
|
||||
{(["one", "manual"] as const).map((p) => (
|
||||
<button
|
||||
key={p}
|
||||
@@ -163,7 +163,7 @@ function McpOneClick({ c, onConnected }: { c: Connector; onConnected: () => void
|
||||
>
|
||||
{waiting ? "Check your browser…" : `Connect ${c.title}`}
|
||||
</button>
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
<p className="text-[12px] text-faint text-center flex items-center justify-center gap-1.5">
|
||||
<span className={TAG_ACCENT}>Recommended</span> agents get a curated set of{" "}
|
||||
{c.title} tools · tokens stay on this computer
|
||||
@@ -203,7 +203,7 @@ function GenericOneClick({ c, cloud }: { c: Connector; cloud: CloudStatus | null
|
||||
) : (
|
||||
<CloudStatusPending />
|
||||
)}
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
<p className="text-[12px] text-faint text-center flex items-center justify-center gap-1.5">
|
||||
<span className={TAG_ACCENT}>Recommended</span> tokens stay on this computer
|
||||
</p>
|
||||
@@ -235,7 +235,7 @@ function SlackOneClick({ c, cloud }: { c: Connector; cloud: CloudStatus | null }
|
||||
) : (
|
||||
<CloudStatusPending />
|
||||
)}
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
<p className="text-[12px] text-faint text-center flex items-center justify-center gap-1.5">
|
||||
<span className={TAG_ACCENT}>Recommended</span> relay · tokens stay on this computer
|
||||
</p>
|
||||
@@ -271,7 +271,7 @@ function GithubOneClick({ c, cloud }: { c: Connector; cloud: CloudStatus | null
|
||||
) : (
|
||||
<CloudStatusPending />
|
||||
)}
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
<p className="text-[12px] text-faint text-center flex items-center justify-center gap-1.5">
|
||||
<span className={TAG_ACCENT}>Recommended</span> relay · short-lived tokens, never stored
|
||||
</p>
|
||||
@@ -327,7 +327,7 @@ function HubSpotOneClick({ c, cloud }: { c: Connector; cloud: CloudStatus | null
|
||||
) : (
|
||||
<CloudStatusPending />
|
||||
)}
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
<p className="text-[12px] text-faint text-center">
|
||||
Works for any number of portals · tokens stay on this computer
|
||||
</p>
|
||||
@@ -360,7 +360,7 @@ function SlackManual({ onConnected }: { onConnected: () => void }) {
|
||||
<button className={PILL_LINE + " w-full !py-2"} onClick={submit} disabled={busy || !bot.trim() || !app.trim()}>
|
||||
{busy ? "Validating…" : "Connect"}
|
||||
</button>
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
<p className="text-[12px] text-warnInk text-center">
|
||||
One mode at a time — this pauses any relay workspaces.
|
||||
</p>
|
||||
|
||||
@@ -29,7 +29,7 @@ export function AvailableDetail({
|
||||
<ConnectorBadge connector={c} size={44} title={c.title} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">{c.title}</h2>
|
||||
<div className="text-[12.5px] text-muted">{c.blurb}</div>
|
||||
<div className="text-[13px] text-muted">{c.blurb}</div>
|
||||
</div>
|
||||
<button
|
||||
className={PILL_ACCENT}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function CalendarDetail({ c, cloud, slack: _slack, onChanged }: DetailPro
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">
|
||||
Google Calendar
|
||||
</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
{c.connected ? (
|
||||
<>
|
||||
<span className="w-2 h-2 rounded-full bg-ok" />
|
||||
@@ -64,7 +64,7 @@ export function CalendarDetail({ c, cloud, slack: _slack, onChanged }: DetailPro
|
||||
|
||||
{!c.connected && (
|
||||
<div className={GRP}>
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>
|
||||
<div className={ROW + " text-[13px] text-muted"}>
|
||||
Sign in with Google — each account stays separate, agents say which one they use.
|
||||
{cloud?.signed_in ? "" : " Requires cloud sign-in."}
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function CloudSignInInline({ blurb }: { blurb?: string }) {
|
||||
>
|
||||
{waiting ? "Check your browser…" : "Sign in to OpenWorker Cloud"}
|
||||
</button>
|
||||
<div className="text-[11.5px] text-faint">
|
||||
<div className="text-[12px] text-faint">
|
||||
{blurb || "Sign-in unlocks one-click connects — or switch to Manual, which works without it."}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,11 +74,11 @@ export function ConnectorsList({
|
||||
>
|
||||
<ConnectorBadge connector={c} size={34} title={c.title} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="font-medium text-[13.5px]">{c.title}</span>
|
||||
<span className="font-medium text-[13px]">{c.title}</span>
|
||||
<span className="block text-[12px] text-muted">{statusLine(c)}</span>
|
||||
</span>
|
||||
{healthChip(c, slack)}
|
||||
<span className="text-faint text-[15px] shrink-0">›</span>
|
||||
<span className="text-faint text-[14px] shrink-0">›</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@ export function ConnectorsList({
|
||||
>
|
||||
<ConnectorBadge connector={c} size={34} title={c.title} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="font-medium text-[13.5px]">{c.title}</span>
|
||||
<span className="font-medium text-[13px]">{c.title}</span>
|
||||
<span className="block text-[12px] text-muted truncate">{c.blurb}</span>
|
||||
</span>
|
||||
<span
|
||||
@@ -120,7 +120,7 @@ export function ConnectorsList({
|
||||
</button>
|
||||
))}
|
||||
{shown.length === 0 && (
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>Nothing matches.</div>
|
||||
<div className={ROW + " text-[13px] text-muted"}>Nothing matches.</div>
|
||||
)}
|
||||
</div>
|
||||
{!showAll && !q && available.length > AVAILABLE_FOLD && (
|
||||
|
||||
@@ -165,14 +165,14 @@ function GenericDetail({
|
||||
<ConnectorBadge connector={c} size={44} title={c.title} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">{c.title}</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
<span className="w-2 h-2 rounded-full bg-ok" />
|
||||
{c.account || (c.auth === "none" ? "Built in" : "Connected")}
|
||||
</div>
|
||||
</div>
|
||||
{c.auth !== "none" && (
|
||||
<button
|
||||
className="text-[12.5px] text-danger/80 hover:text-danger shrink-0"
|
||||
className="text-[13px] text-danger/80 hover:text-danger shrink-0"
|
||||
onClick={async () => {
|
||||
await disconnectConnector(c.name);
|
||||
onChanged();
|
||||
|
||||
@@ -112,18 +112,18 @@ export function CustomMcpGroup({
|
||||
>
|
||||
<McpGlyph />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="font-medium text-[13.5px]">{s.name}</span>
|
||||
<span className="font-medium text-[13px]">{s.name}</span>
|
||||
<span className="block text-[12px] text-muted truncate">{mcpStatusLine(s)}</span>
|
||||
</span>
|
||||
{mcpChip(s)}
|
||||
<span className="text-faint text-[15px] shrink-0">›</span>
|
||||
<span className="text-faint text-[14px] shrink-0">›</span>
|
||||
</button>
|
||||
))}
|
||||
{presets.map((p) => (
|
||||
<div key={p.name} className={ROW} data-testid={`mcp-preset-${p.name}`}>
|
||||
<McpGlyph />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="font-medium text-[13.5px]">{p.label}</span>
|
||||
<span className="font-medium text-[13px]">{p.label}</span>
|
||||
<span className="block text-[12px] text-muted truncate">{p.blurb}</span>
|
||||
</span>
|
||||
<span
|
||||
@@ -229,7 +229,7 @@ export function AddMcpModal({
|
||||
<div className="absolute inset-0 bg-black/30" onClick={onClose} />
|
||||
<div className="absolute left-1/2 top-24 -translate-x-1/2 w-[540px] max-w-[92vw] rounded-xl2 border border-line bg-panel shadow-xl p-5 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-[15px] font-semibold">Add custom MCP server</div>
|
||||
<div className="text-[14px] font-semibold">Add custom MCP server</div>
|
||||
<button className="text-faint hover:text-ink text-[16px] leading-none" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
@@ -244,7 +244,7 @@ export function AddMcpModal({
|
||||
</div>
|
||||
{tab === "url" ? (
|
||||
<>
|
||||
<div className="text-[12.5px] text-muted">
|
||||
<div className="text-[13px] text-muted">
|
||||
Connect a hosted MCP server. If it needs sign-in, the row will offer it after the
|
||||
first test.
|
||||
</div>
|
||||
@@ -267,7 +267,7 @@ export function AddMcpModal({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-[12.5px] text-muted">Paste server JSON (name → config):</div>
|
||||
<div className="text-[13px] text-muted">Paste server JSON (name → config):</div>
|
||||
<textarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
@@ -281,11 +281,11 @@ export function AddMcpModal({
|
||||
<button className={PILL_ACCENT} onClick={tab === "url" ? saveUrl : saveJson}>
|
||||
{tab === "url" ? "Add & test" : "Add"}
|
||||
</button>
|
||||
<button className="text-[12.5px] text-muted hover:text-ink" onClick={onClose}>
|
||||
<button className="text-[13px] text-muted hover:text-ink" onClick={onClose}>
|
||||
cancel
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="text-[12.5px] text-danger">{error}</div>}
|
||||
{error && <div className="text-[13px] text-danger">{error}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -363,7 +363,7 @@ export function McpServerDetail({
|
||||
<div className={ROW}>
|
||||
<span className="text-[13px] flex-1">
|
||||
Test connection
|
||||
<span className="block text-[11.5px] text-faint">
|
||||
<span className="block text-[12px] text-faint">
|
||||
Starts the server and lists its tools — without opening a session.
|
||||
</span>
|
||||
</span>
|
||||
@@ -397,17 +397,17 @@ export function McpServerDetail({
|
||||
)}
|
||||
</div>
|
||||
{server.last_error && server.status !== "connected" && (
|
||||
<div className="px-4 py-2.5 text-[12.5px] text-danger break-words">
|
||||
<div className="px-4 py-2.5 text-[13px] text-danger break-words">
|
||||
{server.last_error}
|
||||
</div>
|
||||
)}
|
||||
<div className={ROW}>
|
||||
<span className="text-[13px] flex-1">Tools</span>
|
||||
<button className="text-[12.5px] text-muted hover:text-ink" onClick={loadTools} disabled={busy}>
|
||||
<button className="text-[13px] text-muted hover:text-ink" onClick={loadTools} disabled={busy}>
|
||||
{busy ? "…" : tools ? "hide" : "show"}
|
||||
</button>
|
||||
</div>
|
||||
{toolErr && <div className="px-4 py-2.5 text-[12.5px] text-danger">{toolErr}</div>}
|
||||
{toolErr && <div className="px-4 py-2.5 text-[13px] text-danger">{toolErr}</div>}
|
||||
{tools && (
|
||||
<div className="px-4 py-3 flex flex-wrap gap-1.5">
|
||||
{tools.length === 0 && <div className="text-[12px] text-faint">No tools.</div>}
|
||||
@@ -415,7 +415,7 @@ export function McpServerDetail({
|
||||
<span
|
||||
key={t.name}
|
||||
title={t.description}
|
||||
className="font-mono text-[11.5px] px-1.5 py-0.5 rounded-md bg-paper border border-line"
|
||||
className="font-mono text-[12px] px-1.5 py-0.5 rounded-md bg-paper border border-line"
|
||||
>
|
||||
{t.name}
|
||||
</span>
|
||||
@@ -427,7 +427,7 @@ export function McpServerDetail({
|
||||
<div className={GRP}>
|
||||
<div className="px-4 py-3">
|
||||
<div className="text-[12px] font-semibold text-muted mb-1.5">Configuration</div>
|
||||
<pre className="font-mono text-[11.5px] text-muted whitespace-pre-wrap break-all">
|
||||
<pre className="font-mono text-[12px] text-muted whitespace-pre-wrap break-all">
|
||||
{JSON.stringify(server.config, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -436,7 +436,7 @@ export function McpServerDetail({
|
||||
<div className="flex items-center gap-4">
|
||||
{isOauth && server.status === "connected" && (
|
||||
<button
|
||||
className="text-[12.5px] text-muted hover:text-ink"
|
||||
className="text-[13px] text-muted hover:text-ink"
|
||||
onClick={async () => {
|
||||
await signoutMcp(server.name);
|
||||
onChanged();
|
||||
@@ -447,7 +447,7 @@ export function McpServerDetail({
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="text-[12.5px] text-danger/80 hover:text-danger"
|
||||
className="text-[13px] text-danger/80 hover:text-danger"
|
||||
onClick={async () => {
|
||||
await deleteMcpServer(server.name);
|
||||
onChanged();
|
||||
|
||||
@@ -25,7 +25,7 @@ import { FOOT, GRP, GRP_H, PILL_ACCENT, PILL_LINE, ROW, TAG_WARN, XBTN } from ".
|
||||
// thread, which the GUI can't map back to an installation). Adding an
|
||||
// installation goes through the ONE entry point: header button → modal.
|
||||
|
||||
const LABEL = "text-[12.5px] text-muted w-24 shrink-0";
|
||||
const LABEL = "text-[13px] text-muted w-24 shrink-0";
|
||||
|
||||
/** The relay status line, one honest layer at a time (the Slack rule). */
|
||||
function relayHealth(gh: GithubStatus | null): { dot: string; text: string } {
|
||||
@@ -65,7 +65,7 @@ export function GithubDetail({ c, cloud, onChanged }: DetailProps) {
|
||||
<ConnectorBadge connector={c} size={44} title="GitHub" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">GitHub</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
{c.connected ? (
|
||||
<>
|
||||
<span
|
||||
@@ -97,7 +97,7 @@ export function GithubDetail({ c, cloud, onChanged }: DetailProps) {
|
||||
|
||||
{!c.connected && (
|
||||
<div className={GRP}>
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>
|
||||
<div className={ROW + " text-[13px] text-muted"}>
|
||||
One @ocw-agent App, installed per account or org — you pick the repos on
|
||||
GitHub; each installation keeps its own allow-list.
|
||||
{cloud?.signed_in ? "" : " One-click needs cloud sign-in; a PAT works without it."}
|
||||
@@ -119,7 +119,7 @@ export function GithubDetail({ c, cloud, onChanged }: DetailProps) {
|
||||
{/* Manual PAT: request/response tools only — no inbound triggers. */}
|
||||
{c.connected && !relay && (
|
||||
<div className={GRP} data-testid="github-manual-card">
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>
|
||||
<div className={ROW + " text-[13px] text-muted"}>
|
||||
Personal access token · tools only. Install the GitHub App to let
|
||||
@-mentions and the agent label reach this computer.
|
||||
</div>
|
||||
@@ -196,7 +196,7 @@ function InstallationGroup({
|
||||
<div className={GRP}>
|
||||
{empty ? (
|
||||
<div className={ROW}>
|
||||
<span className="min-w-0 flex-1 text-[12.5px] text-muted">
|
||||
<span className="min-w-0 flex-1 text-[13px] text-muted">
|
||||
No one allowed yet — @ocw-agent mentions show up here for your OK.
|
||||
</span>
|
||||
<DisconnectBtn id={inst.installation_id} busy={busy} onClick={disconnect} />
|
||||
@@ -225,7 +225,7 @@ function InstallationGroup({
|
||||
function DisconnectBtn({ id, busy, onClick }: { id: string; busy: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
className="text-[12.5px] text-danger/80 hover:text-danger shrink-0"
|
||||
className="text-[13px] text-danger/80 hover:text-danger shrink-0"
|
||||
data-testid={`disconnect-install-${id}`}
|
||||
title="Stops relaying this installation to this computer. The App stays installed on GitHub."
|
||||
onClick={onClick}
|
||||
@@ -255,7 +255,7 @@ function PeopleRow({
|
||||
{allowed.map((login) => (
|
||||
<span
|
||||
key={login}
|
||||
className="inline-flex items-center gap-1.5 pl-2 pr-2 py-0.5 rounded-full bg-paper border border-line text-[12.5px]"
|
||||
className="inline-flex items-center gap-1.5 pl-2 pr-2 py-0.5 rounded-full bg-paper border border-line text-[13px]"
|
||||
>
|
||||
{/* GitHub logins ARE the readable identity — no resolution needed. */}
|
||||
@{login}
|
||||
@@ -283,8 +283,8 @@ function WaitingRow({ m, onChanged }: { m: ParkedMessage; onChanged: () => void
|
||||
<span className={LABEL}>Waiting</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="font-medium text-[13px]">@{m.user_name || m.user_id}</span>{" "}
|
||||
<span className="text-[12.5px] text-muted">in {m.chat_name || m.chat_id}</span>
|
||||
<span className="block text-[12.5px] text-muted truncate">“{m.text}”</span>
|
||||
<span className="text-[13px] text-muted">in {m.chat_name || m.chat_id}</span>
|
||||
<span className="block text-[13px] text-muted truncate">“{m.text}”</span>
|
||||
</span>
|
||||
<button
|
||||
className={PILL_ACCENT + " !py-1"}
|
||||
@@ -315,7 +315,7 @@ function ListeningRows({ subs, onChanged }: { subs: Subscription[]; onChanged: (
|
||||
<span className={LABEL}>Listening</span>
|
||||
<span className="min-w-0 flex-1 space-y-1">
|
||||
{subs.map((s) => (
|
||||
<span key={s.session_id + s.channel} className="flex items-center gap-2 text-[12.5px]">
|
||||
<span key={s.session_id + s.channel} className="flex items-center gap-2 text-[13px]">
|
||||
<span className="font-medium truncate" title={s.session_id}>
|
||||
{s.session_title || s.session_id}
|
||||
</span>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { FOOT, GRP, GRP_H, PILL_ACCENT, ROW, TAG_ACCENT, TAG_WARN, XBTN } from "
|
||||
// Adding an account launches managed OAuth DIRECTLY — Gmail has one connect mode,
|
||||
// so no modal (the pill-modal is only for ≥2-mode connectors like Slack).
|
||||
|
||||
const LABEL = "text-[12.5px] text-muted w-24 shrink-0";
|
||||
const LABEL = "text-[13px] text-muted w-24 shrink-0";
|
||||
|
||||
export function GmailDetail({ c, cloud, slack: _slack, onChanged }: DetailProps) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -34,7 +34,7 @@ export function GmailDetail({ c, cloud, slack: _slack, onChanged }: DetailProps)
|
||||
<ConnectorBadge connector={c} size={44} title="Gmail" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">Gmail</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
{c.connected ? (
|
||||
<>
|
||||
<span className="w-2 h-2 rounded-full bg-ok" />
|
||||
@@ -66,7 +66,7 @@ export function GmailDetail({ c, cloud, slack: _slack, onChanged }: DetailProps)
|
||||
|
||||
{!c.connected && (
|
||||
<div className={GRP}>
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>
|
||||
<div className={ROW + " text-[13px] text-muted"}>
|
||||
Sign in with Google — each mailbox stays separate, agents say which one they use.
|
||||
{cloud?.signed_in ? "" : " Requires cloud sign-in."}
|
||||
</div>
|
||||
@@ -195,7 +195,7 @@ function ChipListRow({
|
||||
{values.map((v) => (
|
||||
<span
|
||||
key={v}
|
||||
className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-paper border border-line text-[12.5px]"
|
||||
className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-paper border border-line text-[13px]"
|
||||
>
|
||||
{v}
|
||||
<button
|
||||
@@ -208,7 +208,7 @@ function ChipListRow({
|
||||
</span>
|
||||
))}
|
||||
<input
|
||||
className="flex-1 min-w-[140px] bg-transparent text-[12.5px] outline-none placeholder:text-faint"
|
||||
className="flex-1 min-w-[140px] bg-transparent text-[13px] outline-none placeholder:text-faint"
|
||||
placeholder={placeholder}
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { FOOT, GRP, GRP_H, PILL_ACCENT, ROW, TAG_ACCENT, TAG_QUIET, TAG_WARN, XB
|
||||
// are the ACL against humans) + collapsed Tools. Adding a portal goes through
|
||||
// the ONE entry point: header button → modal (One click w/ access radios | Manual).
|
||||
|
||||
const LABEL = "text-[12.5px] text-muted w-24 shrink-0";
|
||||
const LABEL = "text-[13px] text-muted w-24 shrink-0";
|
||||
|
||||
export function HubSpotDetail({ c, cloud, slack: _slack, onChanged }: DetailProps) {
|
||||
const [adding, setAdding] = useState(false);
|
||||
@@ -29,7 +29,7 @@ export function HubSpotDetail({ c, cloud, slack: _slack, onChanged }: DetailProp
|
||||
<ConnectorBadge connector={c} size={44} title="HubSpot" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">HubSpot</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
{c.connected ? (
|
||||
<>
|
||||
<span className="w-2 h-2 rounded-full bg-ok" />
|
||||
@@ -49,7 +49,7 @@ export function HubSpotDetail({ c, cloud, slack: _slack, onChanged }: DetailProp
|
||||
|
||||
{!c.connected && (
|
||||
<div className={GRP}>
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>
|
||||
<div className={ROW + " text-[13px] text-muted"}>
|
||||
Connect a portal — read-only or read & write is chosen at consent; there are no
|
||||
delete tools either way.
|
||||
</div>
|
||||
@@ -158,7 +158,7 @@ function PrivacyGroup({ c, onChanged }: Pick<DetailProps, "c" | "onChanged">) {
|
||||
{fields.map((f) => (
|
||||
<span
|
||||
key={f}
|
||||
className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-paper border border-line text-[12.5px] font-mono"
|
||||
className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-paper border border-line text-[13px] font-mono"
|
||||
>
|
||||
{f}
|
||||
<button className={XBTN} title="remove" onClick={() => save(fields.filter((x) => x !== f))}>
|
||||
@@ -167,7 +167,7 @@ function PrivacyGroup({ c, onChanged }: Pick<DetailProps, "c" | "onChanged">) {
|
||||
</span>
|
||||
))}
|
||||
<input
|
||||
className="flex-1 min-w-[140px] bg-transparent text-[12.5px] outline-none placeholder:text-faint"
|
||||
className="flex-1 min-w-[140px] bg-transparent text-[13px] outline-none placeholder:text-faint"
|
||||
placeholder="Property name, e.g. salary"
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
|
||||
@@ -37,7 +37,7 @@ function initials(name: string): string {
|
||||
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
||||
}
|
||||
|
||||
const LABEL = "text-[12.5px] text-muted w-24 shrink-0";
|
||||
const LABEL = "text-[13px] text-muted w-24 shrink-0";
|
||||
|
||||
/** The relay status line, one honest layer at a time: sign-in → socket → live.
|
||||
* Dot color + text; never a synthetic "Slack is down" claim. */
|
||||
@@ -73,7 +73,7 @@ export function SlackDetail({ c, cloud, slack, onChanged }: DetailProps) {
|
||||
<ConnectorBadge connector={c} size={44} title="Slack" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[20px] font-semibold tracking-tight leading-tight">Slack</h2>
|
||||
<div className="text-[12.5px] text-muted flex items-center gap-1.5">
|
||||
<div className="text-[13px] text-muted flex items-center gap-1.5">
|
||||
{c.connected ? (
|
||||
<>
|
||||
<span
|
||||
@@ -101,7 +101,7 @@ export function SlackDetail({ c, cloud, slack, onChanged }: DetailProps) {
|
||||
|
||||
{!c.connected && (
|
||||
<div className={GRP}>
|
||||
<div className={ROW + " text-[12.5px] text-muted"}>
|
||||
<div className={ROW + " text-[13px] text-muted"}>
|
||||
One @ocw app, installed per workspace — each keeps its own allow-list.
|
||||
{cloud?.signed_in ? "" : " One-click needs cloud sign-in; Manual works without it."}
|
||||
</div>
|
||||
@@ -220,7 +220,7 @@ function WorkspaceGroup({
|
||||
{empty ? (
|
||||
<>
|
||||
<div className={ROW}>
|
||||
<span className="min-w-0 flex-1 text-[12.5px] text-muted flex items-center gap-2 flex-wrap">
|
||||
<span className="min-w-0 flex-1 text-[13px] text-muted flex items-center gap-2 flex-wrap">
|
||||
<span>No one allowed yet — mentions of the bot show up here for your OK.</span>
|
||||
<PersonPicker teamId={w.team_id} allowed={[]} onChanged={onChanged} />
|
||||
</span>
|
||||
@@ -273,7 +273,7 @@ function WorkspaceGroup({
|
||||
function DisconnectBtn({ teamId, busy, onClick }: { teamId: string; busy: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
className="text-[12.5px] text-danger/80 hover:text-danger shrink-0"
|
||||
className="text-[13px] text-danger/80 hover:text-danger shrink-0"
|
||||
data-testid={`disconnect-workspace-${teamId}`}
|
||||
title="Stops relaying this workspace to this computer. The app stays installed in Slack."
|
||||
onClick={onClick}
|
||||
@@ -317,7 +317,7 @@ function PeopleRow({
|
||||
{allowed.map((u) => (
|
||||
<span
|
||||
key={u}
|
||||
className="inline-flex items-center gap-1.5 pl-1 pr-2 py-0.5 rounded-full bg-paper border border-line text-[12.5px]"
|
||||
className="inline-flex items-center gap-1.5 pl-1 pr-2 py-0.5 rounded-full bg-paper border border-line text-[13px]"
|
||||
title={`id ${u}`}
|
||||
data-testid={u === installerId ? "people-chip-you" : undefined}
|
||||
>
|
||||
@@ -325,10 +325,10 @@ function PeopleRow({
|
||||
{initials(label(u))}
|
||||
</span>
|
||||
{label(u)}
|
||||
{u === installerId && <span className="text-[10.5px] text-faint">· you</span>}
|
||||
{u === installerId && <span className="text-[11px] text-faint">· you</span>}
|
||||
{protectedIds?.includes(u) ? (
|
||||
<span
|
||||
className="text-[10.5px] text-faint"
|
||||
className="text-[11px] text-faint"
|
||||
title="Remove approval-owner access before removing this person."
|
||||
>
|
||||
· owner
|
||||
@@ -422,7 +422,7 @@ function PersonPicker({
|
||||
<span className="relative" ref={wrap}>
|
||||
<button
|
||||
ref={btn}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-full border border-dashed border-line text-[12.5px] text-muted hover:text-ink hover:border-faint"
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-full border border-dashed border-line text-[13px] text-muted hover:text-ink hover:border-faint"
|
||||
data-testid={testId || `add-person-${teamId || "default"}`}
|
||||
title="Pick from the workspace directory"
|
||||
onClick={toggle}
|
||||
@@ -437,7 +437,7 @@ function PersonPicker({
|
||||
>
|
||||
<input
|
||||
autoFocus
|
||||
className="w-full bg-paper border border-line rounded-lg px-2 py-1 text-[12.5px] outline-none placeholder:text-faint"
|
||||
className="w-full bg-paper border border-line rounded-lg px-2 py-1 text-[13px] outline-none placeholder:text-faint"
|
||||
placeholder="Type a name…"
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
@@ -463,10 +463,10 @@ function PersonPicker({
|
||||
pick(m);
|
||||
}}
|
||||
>
|
||||
<span className="text-[12.5px] font-medium">{m.name}</span>{" "}
|
||||
<span className="text-[11.5px] text-faint">@{m.handle}</span>
|
||||
<span className="text-[13px] font-medium">{m.name}</span>{" "}
|
||||
<span className="text-[12px] text-faint">@{m.handle}</span>
|
||||
{m.guest && (
|
||||
<span className="ml-1.5 text-[10.5px] text-warnInk bg-warnSoft/70 border border-warnInk/15 rounded px-1 py-0.5">
|
||||
<span className="ml-1.5 text-[11px] text-warnInk bg-warnSoft/70 border border-warnInk/15 rounded px-1 py-0.5">
|
||||
guest
|
||||
</span>
|
||||
)}
|
||||
@@ -474,7 +474,7 @@ function PersonPicker({
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="px-2 pb-1 text-[10.5px] text-faint">
|
||||
<div className="px-2 pb-1 text-[11px] text-faint">
|
||||
From your workspace directory — stays on this computer.
|
||||
</div>
|
||||
</div>
|
||||
@@ -522,7 +522,7 @@ function ApprovalOwnersRow({
|
||||
{owners.map((u) => (
|
||||
<span
|
||||
key={u}
|
||||
className="inline-flex items-center gap-1.5 pl-1 pr-2 py-0.5 rounded-full bg-paper border border-line text-[12.5px]"
|
||||
className="inline-flex items-center gap-1.5 pl-1 pr-2 py-0.5 rounded-full bg-paper border border-line text-[13px]"
|
||||
title={`id ${u}`}
|
||||
data-testid={`approval-owner-${u}`}
|
||||
>
|
||||
@@ -530,7 +530,7 @@ function ApprovalOwnersRow({
|
||||
{initials(label(u))}
|
||||
</span>
|
||||
{label(u)}
|
||||
{u === installerId && <span className="text-[10.5px] text-faint">· installer</span>}
|
||||
{u === installerId && <span className="text-[11px] text-faint">· installer</span>}
|
||||
{editable && (
|
||||
<button className={XBTN} title="remove approval owner" onClick={() => remove(u)}>
|
||||
×
|
||||
@@ -549,9 +549,9 @@ function ApprovalOwnersRow({
|
||||
/>
|
||||
)}
|
||||
{!editable && owners.length > 0 && (
|
||||
<span className="text-[11.5px] text-faint">Set by the workspace installer.</span>
|
||||
<span className="text-[12px] text-faint">Set by the workspace installer.</span>
|
||||
)}
|
||||
{err && <span className="basis-full text-[11.5px] text-warnInk">{err}</span>}
|
||||
{err && <span className="basis-full text-[12px] text-warnInk">{err}</span>}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -567,8 +567,8 @@ function WaitingRow({ m, onChanged }: { m: ParkedMessage; onChanged: () => void
|
||||
<span className={LABEL}>Waiting</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="font-medium text-[13px]">{m.user_name || m.user_id}</span>{" "}
|
||||
<span className="text-[12.5px] text-muted">in {m.chat_name || m.chat_id}</span>
|
||||
<span className="block text-[12.5px] text-muted truncate">“{m.text}”</span>
|
||||
<span className="text-[13px] text-muted">in {m.chat_name || m.chat_id}</span>
|
||||
<span className="block text-[13px] text-muted truncate">“{m.text}”</span>
|
||||
</span>
|
||||
<button
|
||||
className={PILL_ACCENT + " !py-1"}
|
||||
@@ -600,7 +600,7 @@ function ListeningRows({ subs, onChanged }: { subs: Subscription[]; onChanged: (
|
||||
<span className={LABEL}>Listening</span>
|
||||
<span className="min-w-0 flex-1 space-y-1">
|
||||
{subs.map((s) => (
|
||||
<span key={s.session_id + s.channel} className="flex items-center gap-2 text-[12.5px]">
|
||||
<span key={s.session_id + s.channel} className="flex items-center gap-2 text-[13px]">
|
||||
<span className="font-medium truncate" title={s.session_id}>
|
||||
{s.session_title || s.session_id}
|
||||
</span>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function SlackHowItWorks({ workspaces }: { workspaces: SlackWorkspace[] }
|
||||
// the only boxes on screen are the two mini-windows, which own their frames.
|
||||
<div className="mb-5" data-testid="slack-howitworks">
|
||||
<div className="flex items-baseline gap-2.5">
|
||||
<h3 className="text-[13.5px] font-semibold tracking-tight">
|
||||
<h3 className="text-[13px] font-semibold tracking-tight">
|
||||
Getting started with Slack & OpenWorker
|
||||
</h3>
|
||||
<button
|
||||
|
||||
@@ -11,8 +11,8 @@ export function ToolsDisclosure({ c, onChanged }: { c: Connector; onChanged: ()
|
||||
<div className={GRP + " mt-6"}>
|
||||
<details>
|
||||
<summary className={ROW + " cursor-pointer hover:bg-paper/60 list-none [&::-webkit-details-marker]:hidden"}>
|
||||
<span className="text-[12.5px] text-muted w-24 shrink-0">› Tools</span>
|
||||
<span className="min-w-0 flex-1 text-[12.5px] text-muted">
|
||||
<span className="text-[13px] text-muted w-24 shrink-0">› Tools</span>
|
||||
<span className="min-w-0 flex-1 text-[13px] text-muted">
|
||||
{enabled} of {c.tools.length} enabled
|
||||
</span>
|
||||
</summary>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -220,18 +220,18 @@ export function useProviderSetup(opts?: { onSaved?: () => void }): ProviderSetup
|
||||
if (p.configured && p.needs_key) {
|
||||
const used = o?.lastUsed ? relTime(p.last_used_at) : null;
|
||||
return (
|
||||
<span className="block text-[11.5px] text-ok font-medium truncate">
|
||||
<span className="block text-[12px] text-ok font-medium truncate">
|
||||
✓ Connected{used ? <span className="text-muted font-normal"> · used {used}</span> : ""}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (!p.needs_key)
|
||||
return (
|
||||
<span className="block text-[11.5px] text-faint truncate">
|
||||
<span className="block text-[12px] text-faint truncate">
|
||||
{keylessOk.has(p.name) ? <span className="text-ok font-medium">✓ Running</span> : "No key needed"}
|
||||
</span>
|
||||
);
|
||||
return <span className="block text-[11.5px] text-faint truncate">Not set up</span>;
|
||||
return <span className="block text-[12px] text-faint truncate">Not set up</span>;
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -323,7 +323,7 @@ export function ProviderForm({
|
||||
const { info, sel } = ps;
|
||||
const label = "block text-[12px] text-muted mt-3 mb-1";
|
||||
const input =
|
||||
"w-full px-3 py-2 rounded-lg border bg-panel text-[13.5px] outline-none focus:border-accent";
|
||||
"w-full px-3 py-2 rounded-lg border bg-panel text-[13px] outline-none focus:border-accent";
|
||||
const fieldsAll = info?.fields || [];
|
||||
const keyed = fieldsAll.some((x) => x.secret);
|
||||
// Cloud providers declare a segmented auth-method choice; the selected method's
|
||||
@@ -387,23 +387,23 @@ export function ProviderForm({
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{f.help && <p className="text-[11.5px] text-faint mt-1">{f.help}</p>}
|
||||
{f.help && <p className="text-[12px] text-faint mt-1">{f.help}</p>}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button className="text-[12.5px] text-muted hover:text-ink" onClick={ps.backToGallery} data-testid={`${tp}-back`}>
|
||||
<button className="text-[13px] text-muted hover:text-ink" onClick={ps.backToGallery} data-testid={`${tp}-back`}>
|
||||
‹ All providers
|
||||
</button>
|
||||
<div className="flex items-center gap-3 mt-3 mb-1">
|
||||
<ProviderMark name={info?.name || ""} title={info?.title || ""} size={36} />
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[15px] font-semibold leading-tight">{info?.title}</span>
|
||||
<span className="block text-[14px] font-semibold leading-tight">{info?.title}</span>
|
||||
{info ? ps.statusFor(info) : null}
|
||||
</span>
|
||||
</div>
|
||||
{info?.blurb && <p className="text-[11.5px] text-faint mt-1">{info.blurb}</p>}
|
||||
{info?.blurb && <p className="text-[12px] text-faint mt-1">{info.blurb}</p>}
|
||||
|
||||
{fieldsAll
|
||||
.filter(
|
||||
@@ -433,7 +433,7 @@ export function ProviderForm({
|
||||
role="radio"
|
||||
aria-checked={active}
|
||||
className={
|
||||
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[12.5px] whitespace-nowrap transition-colors " +
|
||||
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[13px] whitespace-nowrap transition-colors " +
|
||||
(active
|
||||
? "bg-panel text-ink font-medium shadow-sm ring-1 ring-line"
|
||||
: "text-muted hover:text-ink")
|
||||
@@ -468,11 +468,11 @@ export function ProviderForm({
|
||||
{methodFields.map((f) => fieldRow(f, false))}
|
||||
<div className="mt-3.5 flex items-center justify-between gap-3 border-t border-line pt-3">
|
||||
{ps.savedState ? (
|
||||
<span className="text-[11.5px] font-medium text-ok" data-testid={`${tp}-saved-pill`}>
|
||||
<span className="text-[12px] font-medium text-ok" data-testid={`${tp}-saved-pill`}>
|
||||
✓ Tested & saved
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[11.5px] text-faint">Runs one read-only check, then saves.</span>
|
||||
<span className="text-[12px] text-faint">Runs one read-only check, then saves.</span>
|
||||
)}
|
||||
<button
|
||||
className="shrink-0 rounded-lg border border-accent bg-accent px-4 py-1.5 text-[13px] font-medium text-white hover:brightness-105 disabled:opacity-40"
|
||||
@@ -488,7 +488,7 @@ export function ProviderForm({
|
||||
)}
|
||||
|
||||
{info?.needs_key && KEY_HELP[sel] && (
|
||||
<p className="text-[11.5px] text-faint mt-2">
|
||||
<p className="text-[12px] text-faint mt-2">
|
||||
No key yet?{" "}
|
||||
<button
|
||||
className="text-muted underline decoration-line underline-offset-2 hover:text-ink"
|
||||
@@ -500,7 +500,7 @@ export function ProviderForm({
|
||||
</p>
|
||||
)}
|
||||
{info && !info.needs_key && (
|
||||
<p className="text-[11.5px] text-faint mt-2">
|
||||
<p className="text-[12px] text-faint mt-2">
|
||||
No API key needed — Ollama runs models on this computer.{" "}
|
||||
<button
|
||||
className="text-muted underline decoration-line underline-offset-2 hover:text-ink"
|
||||
@@ -521,7 +521,7 @@ export function ProviderForm({
|
||||
if (!ps.showEndpoint)
|
||||
return (
|
||||
<button
|
||||
className="block self-start text-[12.5px] text-muted hover:text-ink mt-4"
|
||||
className="block self-start text-[13px] text-muted hover:text-ink mt-4"
|
||||
onClick={() => ps.setShowEndpoint(true)}
|
||||
data-testid={`${tp}-endpoint-link`}
|
||||
>
|
||||
@@ -550,13 +550,13 @@ export function ProviderForm({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{ep.help && <p className="text-[11.5px] text-faint mt-1">{ep.help}</p>}
|
||||
{ep.help && <p className="text-[12px] text-faint mt-1">{ep.help}</p>}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Error line: fixed height so failures never reflow the form. */}
|
||||
<div className="mt-3 min-h-[19px] text-[12.5px]">
|
||||
<div className="mt-3 min-h-[19px] text-[13px]">
|
||||
{ps.verify.state === "error" && <span className="text-warnInk">{ps.verify.msg}</span>}
|
||||
</div>
|
||||
{footer}
|
||||
|
||||
+199
-138
@@ -1,4 +1,48 @@
|
||||
/* Manrope — the wordmark typeface, self-hosted (bundled woff2, no external font call). */
|
||||
/* Self-hosted typefaces (bundled woff2, no external font calls) so Mac and Windows render
|
||||
identically: Inter for all UI/reading text, JetBrains Mono for machine-made content,
|
||||
Manrope for the wordmark only. */
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("./fonts/inter-400.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("./fonts/inter-500.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("./fonts/inter-600.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("./fonts/inter-700.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("./fonts/jetbrains-mono-400.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("./fonts/jetbrains-mono-500.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Manrope";
|
||||
font-style: normal;
|
||||
@@ -15,11 +59,16 @@
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--paper: #f5f6f7; /* cool light gray — reads "tool", not "writing app" */
|
||||
--paper: #f5f6f7; /* cool light gray — washes/chips over panel or canvas */
|
||||
--panel: #ffffff;
|
||||
/* UX-039 surface hierarchy: content brightest, chrome tinted. The chat column sits on
|
||||
--canvas; the sidebar + right rail sit on --chrome and recede. */
|
||||
--canvas: #ffffff;
|
||||
--chrome: #f2f3f5;
|
||||
--chrome-hover: #e9ebee;
|
||||
--ink: #17191c;
|
||||
--muted: #5b616b;
|
||||
--faint: #9aa1aa;
|
||||
--muted: #4d535c; /* OPE-126 contrast fix (was #5b616b) */
|
||||
--faint: #6f7680; /* OPE-126 contrast fix — old #9aa1aa was ~2.5:1 on white */
|
||||
--line: #e8eaed;
|
||||
--line-strong: #d8dce1;
|
||||
--accent: #2563eb; /* cobalt — our signature, distinct from the coral-leaning tools */
|
||||
@@ -28,7 +77,7 @@
|
||||
--ok-soft: #eef6f0;
|
||||
--ok-line: #cfe6d6;
|
||||
--ok-dot: #3f9c5a; /* brighter green for small status dots */
|
||||
--warn-ink: #b45309;
|
||||
--warn-ink: #92400e; /* OPE-126 contrast fix (was #b45309, borderline at 11px) */
|
||||
--warn-soft: #fef3c7;
|
||||
--danger: #b91c1c;
|
||||
--danger-soft: #f9e7e5;
|
||||
@@ -45,8 +94,16 @@
|
||||
--check-b: #fafbfc;
|
||||
/* Headings are sans now (was a Palatino serif) — less editorial, more product. */
|
||||
--serif: var(--sans);
|
||||
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
|
||||
--mono: "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
||||
--sans: "Inter", "Segoe UI", system-ui, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
|
||||
/* Type scale (OPE-126): six sizes, nothing else ships. The hiw-* Slack miniature is
|
||||
illustration artwork, exempt; glyph-only sizes (✕, marks) aren't text. */
|
||||
--fs-title: 20px; /* 600 — page titles */
|
||||
--fs-heading: 16px; /* 600 — section headings, letter-spacing -0.01em */
|
||||
--fs-body: 14px; /* 400 — transcript/reading, lh ~1.6 */
|
||||
--fs-ui: 13px; /* 400 — UI default: nav, rows, controls */
|
||||
--fs-mono: 12px; /* 400 mono — machine content, chips, meta */
|
||||
--fs-label: 11px; /* 600 — uppercase labels (+0.08em) and captions */
|
||||
/* The right rail's width while reading an artifact (wider than the 332px inspector). */
|
||||
--artifact-rail-w: min(62vw, 960px);
|
||||
}
|
||||
@@ -57,6 +114,10 @@ html[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--paper: #131417;
|
||||
--panel: #1c1e22;
|
||||
/* Dark keeps the same hierarchy: canvas lifted a step ABOVE the chrome. */
|
||||
--canvas: #191b1f;
|
||||
--chrome: #121316;
|
||||
--chrome-hover: #1d2025;
|
||||
--ink: #e6e8eb;
|
||||
--muted: #9aa1ab;
|
||||
--faint: #62686f;
|
||||
@@ -187,11 +248,11 @@ body {
|
||||
.sub-chip-wrap { position: relative; display: inline-flex; }
|
||||
.wschip.sub-chip.active { border-color: var(--accent); color: var(--accent); }
|
||||
.sub-pop { position: absolute; bottom: calc(100% + 8px); right: 0; width: 300px; z-index: 40; background: var(--panel); border: 1px solid var(--line-strong); border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.14); padding: 12px; }
|
||||
.sub-pop-head { font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin-bottom: 8px; }
|
||||
.sub-pop-empty { font-size: 12.5px; padding: 2px 0 8px; }
|
||||
.sub-pop-head { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin-bottom: 8px; }
|
||||
.sub-pop-empty { font-size: 13px; padding: 2px 0 8px; }
|
||||
.sub-pop-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 4px 0; font-size: 13px; }
|
||||
.sub-pop-chan { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.sub-pop-x { border: 0; background: none; color: var(--faint); font-size: 17px; line-height: 1; cursor: pointer; padding: 0 4px; }
|
||||
.sub-pop-x { border: 0; background: none; color: var(--faint); font-size: 16px; line-height: 1; cursor: pointer; padding: 0 4px; }
|
||||
.sub-pop-x:hover { color: var(--ink); }
|
||||
.sub-pop-add { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
|
||||
.sub-pop-add .chan-input { min-width: 0; flex: 1; }
|
||||
@@ -200,7 +261,7 @@ body {
|
||||
.main {
|
||||
position: relative; display: flex; flex-direction: column;
|
||||
min-height: 0; overflow: hidden;
|
||||
background: var(--paper); /* plain paper, matching the mock (was a dotted graph texture) */
|
||||
background: var(--canvas); /* UX-039: the content column is the brightest surface */
|
||||
}
|
||||
.main-topbar {
|
||||
position: absolute;
|
||||
@@ -211,7 +272,7 @@ body {
|
||||
padding: 0 14px 0 20px; color: var(--ink); user-select: none;
|
||||
/* Edgeless glass (§22 amendment): no border, translucent tint + blur — invisible over the
|
||||
flat paper at rest, frosts only when the transcript actually scrolls under it. */
|
||||
background: color-mix(in srgb, var(--paper) 72%, transparent);
|
||||
background: color-mix(in srgb, var(--canvas) 72%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
z-index: 6;
|
||||
@@ -274,12 +335,12 @@ body {
|
||||
.suggest-head { font-size: 13px; color: var(--muted); margin-bottom: 6px; }
|
||||
.suggest {
|
||||
display: flex; align-items: center; gap: 11px; padding: 11px 6px;
|
||||
border-top: 1px solid var(--line); cursor: pointer; color: var(--ink); font-size: 14.5px;
|
||||
border-top: 1px solid var(--line); cursor: pointer; color: var(--ink); font-size: 14px;
|
||||
}
|
||||
.suggest:hover { color: var(--accent); }
|
||||
.suggest .ico {
|
||||
width: 28px; height: 28px; border: 1px solid var(--line); border-radius: 7px;
|
||||
display: grid; place-items: center; background: var(--panel); font-size: 13.5px;
|
||||
display: grid; place-items: center; background: var(--panel); font-size: 13px;
|
||||
color: var(--muted); flex: none;
|
||||
}
|
||||
|
||||
@@ -287,11 +348,11 @@ body {
|
||||
/* Conversation column — mock's `max-w-3xl mx-auto space-y-5` (the px-6/py-6 gutter is supplied by
|
||||
.main-scroll). The user bubble is restyled inline with Tailwind utilities on the element. */
|
||||
.transcript { max-width: 768px; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
|
||||
.bubble-assistant { font-size: 15px; line-height: 1.55; white-space: pre-wrap; }
|
||||
.bubble-assistant { font-size: var(--fs-body); line-height: 1.6; white-space: pre-wrap; }
|
||||
.bubble-assistant .who { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--faint); margin-bottom: 4px; }
|
||||
|
||||
/* markdown-rendered assistant content (the bubble's pre-wrap would double-space block output) */
|
||||
.md { white-space: normal; font-size: 15px; line-height: 1.68; color: var(--ink); }
|
||||
.md { white-space: normal; font-size: var(--fs-body); line-height: 1.6; color: var(--ink); }
|
||||
.md > :first-child { margin-top: 0; }
|
||||
.md > :last-child { margin-bottom: 0; }
|
||||
.md p { margin: 0 0 12px; }
|
||||
@@ -301,10 +362,10 @@ body {
|
||||
.md h1, .md h2, .md h3, .md h4, .md h5 {
|
||||
margin: 22px 0 9px; line-height: 1.3; font-weight: 650; letter-spacing: -0.01em; color: var(--ink);
|
||||
}
|
||||
.md h1 { font-size: 20px; }
|
||||
.md h2 { font-size: 17px; }
|
||||
.md h3 { font-size: 15px; }
|
||||
.md h4, .md h5 { font-size: 13.5px; }
|
||||
.md h1 { font-size: var(--fs-title); }
|
||||
.md h2 { font-size: var(--fs-heading); }
|
||||
.md h3 { font-size: var(--fs-body); }
|
||||
.md h4, .md h5 { font-size: var(--fs-ui); }
|
||||
|
||||
/* lists — visible markers + comfortable item spacing (was a run-together wall of text) */
|
||||
.md ul, .md ol { margin: 4px 0 14px; padding-left: 22px; }
|
||||
@@ -318,21 +379,21 @@ body {
|
||||
|
||||
/* code */
|
||||
.md code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.86em;
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
|
||||
font-family: var(--mono); font-size: var(--fs-mono);
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px;
|
||||
}
|
||||
.md pre {
|
||||
margin: 4px 0 14px; padding: 12px 14px; overflow-x: auto;
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
|
||||
}
|
||||
.md pre code { background: none; border: none; padding: 0; font-size: 12.5px; line-height: 1.55; }
|
||||
.md pre code { background: none; border: none; padding: 0; font-size: var(--fs-mono); line-height: 1.55; }
|
||||
|
||||
/* blockquote */
|
||||
.md blockquote { margin: 4px 0 14px; padding: 4px 14px; border-left: 3px solid var(--line-strong); color: var(--muted); }
|
||||
.md blockquote p:last-child { margin-bottom: 0; }
|
||||
|
||||
/* tables — header tint, zebra rows, roomier cells (kept block-scrollable for wide tables) */
|
||||
.md table { display: block; overflow-x: auto; border-collapse: collapse; margin: 6px 0 14px; font-size: 13.5px; }
|
||||
.md table { display: block; overflow-x: auto; border-collapse: collapse; margin: 6px 0 14px; font-size: var(--fs-ui); }
|
||||
.md th, .md td { border: 1px solid var(--line); padding: 7px 12px; text-align: left; vertical-align: top; }
|
||||
.md th { background: var(--paper); font-weight: 600; color: var(--muted); white-space: nowrap; }
|
||||
.md tbody tr:nth-child(even) { background: color-mix(in srgb, var(--paper) 45%, transparent); }
|
||||
@@ -359,8 +420,8 @@ body {
|
||||
border: 1px solid var(--line); border-radius: 9px; background: var(--panel);
|
||||
padding: 8px 11px; font-size: 13px; color: var(--muted);
|
||||
}
|
||||
.tool .name { font-family: var(--mono); color: var(--ink); font-size: 12.5px; }
|
||||
.tool .args { font-family: var(--mono); font-size: 11.5px; color: var(--faint); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.tool .name { font-family: var(--mono); color: var(--ink); font-size: 13px; }
|
||||
.tool .args { font-family: var(--mono); font-size: 12px; color: var(--faint); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.tool .status { float: right; font-size: 11px; }
|
||||
.tool .status.ok { color: var(--ok); }
|
||||
.tool .status.denied, .tool .status.error { color: var(--accent); }
|
||||
@@ -385,7 +446,7 @@ body {
|
||||
.approval-title { font-size: 14px; color: var(--ink); min-width: 0; }
|
||||
.approval-title b { font-weight: 600; }
|
||||
.approval-tool {
|
||||
font-family: var(--mono); font-size: 11.5px; font-weight: 400; color: var(--muted);
|
||||
font-family: var(--mono); font-size: 12px; font-weight: 400; color: var(--muted);
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; margin-left: 5px;
|
||||
}
|
||||
/* plain-words scope note replaces the "local action" badge */
|
||||
@@ -402,16 +463,16 @@ body {
|
||||
max-height: 42vh; overflow-y: auto;
|
||||
}
|
||||
.approval-prev-more {
|
||||
display: block; margin-top: 4px; font: inherit; font-family: -apple-system, sans-serif;
|
||||
font-size: 11.5px; color: var(--accent); background: none; border: 0; padding: 0; cursor: pointer;
|
||||
display: block; margin-top: 4px; font: inherit; font-family: var(--sans);
|
||||
font-size: 12px; color: var(--accent); background: none; border: 0; padding: 0; cursor: pointer;
|
||||
}
|
||||
.approval-with { margin-top: 9px; font-size: 12.5px; color: var(--muted); }
|
||||
.approval-with { margin-top: 9px; font-size: 13px; color: var(--muted); }
|
||||
/* long outbound messages: same box as code previews but reads as text, not code */
|
||||
.approval-prev.prose { font-family: inherit; font-size: 12.5px; }
|
||||
.approval-prev.prose { font-family: inherit; font-size: 13px; }
|
||||
.approval-filechip {
|
||||
display: inline-flex; align-items: center; gap: 7px; margin-top: 11px; align-self: flex-start;
|
||||
border: 1px solid var(--line); border-radius: 9px; background: var(--paper);
|
||||
padding: 5px 11px 5px 7px; font-size: 12.5px; color: var(--ink);
|
||||
padding: 5px 11px 5px 7px; font-size: 13px; color: var(--ink);
|
||||
}
|
||||
.approval-filechip .ico {
|
||||
width: 22px; height: 22px; border-radius: 6px; background: var(--accent-soft);
|
||||
@@ -421,30 +482,30 @@ body {
|
||||
.approval-row { padding: 9px 12px; }
|
||||
.approval-row-line { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
||||
.approval-row-line .approval-btns { margin-top: 0; flex-wrap: nowrap; }
|
||||
.approval-peek { font: inherit; font-size: 11.5px; color: var(--accent); background: none; border: 0; padding: 0; cursor: pointer; flex: none; }
|
||||
.approval-peek { font: inherit; font-size: 12px; color: var(--accent); background: none; border: 0; padding: 0; cursor: pointer; flex: none; }
|
||||
/* owner call (UX-018): no solid fills — the primary action is a blue border */
|
||||
.approval .btn.approval-primary { border-color: var(--accent); color: var(--accent); font-weight: 600; }
|
||||
.approval .btn.approval-primary:hover { background: var(--accent-soft); }
|
||||
.approval .btn.quiet-deny { border: 0; background: none; color: var(--faint); }
|
||||
.approval .btn.quiet-deny:hover { color: var(--danger); }
|
||||
.approval-rest {
|
||||
margin-top: 7px; font-family: var(--mono); font-size: 11.5px; color: var(--faint);
|
||||
margin-top: 7px; font-family: var(--mono); font-size: 12px; color: var(--faint);
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.approval-reason { margin-top: 8px; font-size: 12.5px; color: var(--muted); }
|
||||
.approval-reason { margin-top: 8px; font-size: 13px; color: var(--muted); }
|
||||
/* Standing-approval consent lines (§25): reads = quiet disclosure, writes = the grants. */
|
||||
.approval-grants {
|
||||
margin-top: 11px; border: 1px solid var(--line); border-radius: 8px; background: var(--paper);
|
||||
padding: 7px 10px; display: flex; flex-direction: column; gap: 5px;
|
||||
}
|
||||
.approval-grant { display: flex; align-items: baseline; gap: 7px; font-size: 12.5px; color: var(--muted); }
|
||||
.approval-grant { display: flex; align-items: baseline; gap: 7px; font-size: 13px; color: var(--muted); }
|
||||
.approval-grant .grant-mark { color: var(--faint); width: 12px; text-align: center; flex: none; }
|
||||
.approval-grant .grant-mark.write { color: var(--teal-ink); font-weight: 600; }
|
||||
.approval-grant[data-access="write"] { color: var(--ink); }
|
||||
.approval-grant .grant-note { color: var(--faint); font-size: 11.5px; }
|
||||
.approval-grant .grant-note { color: var(--faint); font-size: 12px; }
|
||||
.approval-btns { display: flex; gap: 8px; margin-top: 13px; flex-wrap: wrap; align-items: center; }
|
||||
.approval-btns .spacer { flex: 1; }
|
||||
.approval .resolved { margin-top: 10px; font-size: 12.5px; color: var(--muted); }
|
||||
.approval .resolved { margin-top: 10px; font-size: 13px; color: var(--muted); }
|
||||
button.btn { font: inherit; font-size: 13px; padding: 7px 12px; border-radius: 8px; border: 1px solid var(--line-strong); background: var(--panel); cursor: pointer; }
|
||||
button.btn.icon-only { display: inline-flex; align-items: center; justify-content: center; padding: 7px 9px; flex: none; color: var(--muted); }
|
||||
button.btn.icon-only:hover { color: var(--ink); }
|
||||
@@ -455,7 +516,7 @@ button.btn.danger { color: var(--accent); }
|
||||
/* composer — the wrap/card/head/bar layout is supplied by Tailwind utilities on the elements
|
||||
(Composer.tsx); only the textarea reset + drag-ring remain in CSS. */
|
||||
.composer textarea {
|
||||
width: 100%; border: none; outline: none; resize: none; font: inherit; font-size: 14.5px; line-height: 1.45;
|
||||
width: 100%; border: none; outline: none; resize: none; font: inherit; font-size: 14px; line-height: 1.45;
|
||||
background: transparent; color: var(--ink); min-height: 24px; overflow: hidden;
|
||||
/* Height (incl. the cap) is owned by the autogrow effect in Composer.tsx — a
|
||||
CSS max-height here fought it and re-created the OPE-106 clipping. */
|
||||
@@ -478,7 +539,7 @@ button.btn.danger { color: var(--accent); }
|
||||
the chevron glued to the label (the old inline pill let it wrap onto its own line). */
|
||||
.pill {
|
||||
display: inline-flex; align-items: center; gap: 3px; max-width: 240px;
|
||||
font: inherit; font-size: 12.5px; color: var(--muted);
|
||||
font: inherit; font-size: 13px; color: var(--muted);
|
||||
border: none; border-radius: 7px; padding: 5px 8px; background: transparent; cursor: pointer;
|
||||
}
|
||||
.pill:hover { color: var(--ink); background: var(--paper); }
|
||||
@@ -506,7 +567,7 @@ button.btn.danger { color: var(--accent); }
|
||||
.dd-item { padding: 8px 10px; border-radius: 8px; cursor: pointer; }
|
||||
.dd-item:hover { background: var(--paper); }
|
||||
.dd-item.sel .dd-label { color: var(--accent); }
|
||||
.dd-label { font-size: 13.5px; color: var(--ink); display: flex; justify-content: space-between; align-items: center; }
|
||||
.dd-label { font-size: 13px; color: var(--ink); display: flex; justify-content: space-between; align-items: center; }
|
||||
.dd-item .chk { color: var(--accent); }
|
||||
.dd-desc { font-size: 12px; color: var(--faint); margin-top: 1px; }
|
||||
.send { width: 34px; height: 34px; border-radius: 50%; border: none; background: var(--accent); color: #fff; cursor: pointer; font-size: 16px; }
|
||||
@@ -519,7 +580,7 @@ button.btn.danger { color: var(--accent); }
|
||||
|
||||
.approval-inline {
|
||||
display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
|
||||
color: var(--muted); font-size: 12.5px; background: var(--panel);
|
||||
color: var(--muted); font-size: 13px; background: var(--panel);
|
||||
border: 1px solid var(--line); border-radius: 999px; padding: 6px 10px;
|
||||
}
|
||||
.approval-inline .status { color: var(--ok); }
|
||||
@@ -533,7 +594,7 @@ button.btn.danger { color: var(--accent); }
|
||||
bottom: 0;
|
||||
width: 332px;
|
||||
overflow-y: auto;
|
||||
background: var(--glass-soft);
|
||||
background: color-mix(in srgb, var(--chrome) 84%, transparent);
|
||||
border-left: 1px solid var(--line);
|
||||
padding: 18px 16px;
|
||||
backdrop-filter: blur(12px);
|
||||
@@ -545,7 +606,7 @@ button.btn.danger { color: var(--accent); }
|
||||
width: var(--artifact-rail-w);
|
||||
z-index: 8;
|
||||
padding: 0;
|
||||
background: var(--paper);
|
||||
background: var(--canvas);
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.main.rail-open:has(.right-rail.artifact-mode) .main-chat { margin-right: var(--artifact-rail-w); }
|
||||
@@ -556,7 +617,7 @@ button.btn.danger { color: var(--accent); }
|
||||
.rail-section-toggle { flex: 1; display: flex; gap: 8px; align-items: center; cursor: pointer; font: inherit; font-size: 13px; font-weight: 600; color: var(--ink); user-select: none; border: 0; background: transparent; padding: 0; text-align: left; }
|
||||
.rail-chev { color: var(--faint); flex: none; }
|
||||
.rail-mini-btn { width: 24px; height: 24px; display: grid; place-items: center; color: var(--muted); border: 0; border-radius: 7px; background: transparent; cursor: pointer; }
|
||||
.rail-mini-btn:hover { color: var(--ink); background: var(--paper); }
|
||||
.rail-mini-btn:hover { color: var(--ink); background: var(--chrome-hover); }
|
||||
.rail-section-body { padding-top: 10px; }
|
||||
/* Seventeenth pass: a section header's maximum signal — a small count chip, amber only
|
||||
when it carries attention states (blocked/review). */
|
||||
@@ -568,10 +629,10 @@ button.btn.danger { color: var(--accent); }
|
||||
/* The Team panel — member rows; click = the altitude-3 escape hatch into that worker's session. */
|
||||
.rail-team { display: flex; flex-direction: column; gap: 1px; }
|
||||
.rail-team-row { display: flex; gap: 9px; align-items: center; width: 100%; padding: 6px 6px; border: 0; border-radius: 8px; background: transparent; cursor: pointer; font: inherit; text-align: left; }
|
||||
.rail-team-row:hover { background: var(--paper); }
|
||||
.rail-team-row:hover { background: var(--chrome-hover); }
|
||||
.rail-team-row:hover .rail-team-open { opacity: 1; }
|
||||
.rail-team-name { font-size: 13px; font-weight: 550; color: var(--ink); flex: none; }
|
||||
.rail-team-item { font-size: 11.5px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
|
||||
.rail-team-item { font-size: 12px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
|
||||
.rail-team-open { font-size: 11px; color: var(--accent); opacity: 0; flex: none; }
|
||||
.rail-chat-row .rail-team-name { font-weight: 500; color: var(--muted); }
|
||||
.rail-muted { font-size: 12px; color: var(--faint); line-height: 1.45; overflow-wrap: anywhere; }
|
||||
@@ -579,7 +640,7 @@ button.btn.danger { color: var(--accent); }
|
||||
.rail-todo-list { display: flex; flex-direction: column; gap: 9px; }
|
||||
.rail-todo {
|
||||
display: grid; grid-template-columns: 16px minmax(0, 1fr); gap: 8px; align-items: flex-start;
|
||||
font-size: 12.5px; line-height: 1.35; color: var(--muted);
|
||||
font-size: 13px; line-height: 1.35; color: var(--muted);
|
||||
}
|
||||
.rail-todo-mark {
|
||||
width: 12px; height: 12px; margin-top: 2px; border: 1.6px solid var(--line-strong);
|
||||
@@ -610,9 +671,9 @@ button.btn.danger { color: var(--accent); }
|
||||
}
|
||||
.artifact-row:hover { border-color: var(--line); background: var(--panel); }
|
||||
.artifact-ico { display: inline-flex; align-items: center; justify-content: center; color: var(--muted); }
|
||||
.artifact-name { display: flex; flex-direction: column; gap: 1px; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.artifact-name { display: flex; flex-direction: column; gap: 1px; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.artifact-row-meta { font-size: 11px; color: var(--faint); font-weight: 400; }
|
||||
.artifact-open { font-size: 11.5px; color: var(--faint); }
|
||||
.artifact-open { font-size: 12px; color: var(--faint); }
|
||||
.artifact-viewer { display: flex; flex-direction: column; min-height: 0; height: 100%; }
|
||||
.artifact-head {
|
||||
flex: none; min-height: 58px; padding: 10px 18px; border-bottom: 1px solid var(--line);
|
||||
@@ -628,7 +689,7 @@ button.btn.danger { color: var(--accent); }
|
||||
.artifact-title { display: flex; align-items: center; gap: 7px; font-size: 14px; font-weight: 600; color: var(--ink); overflow: hidden; white-space: nowrap; }
|
||||
.artifact-title span:last-child { overflow: hidden; text-overflow: ellipsis; }
|
||||
.artifact-sep { color: var(--faint); font-weight: 400; }
|
||||
.artifact-path { font-size: 11.5px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
|
||||
.artifact-path { font-size: 12px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
|
||||
.artifact-preview { min-height: 0; flex: 1; overflow: auto; background: var(--panel); }
|
||||
.artifact-frame { width: 100%; height: 100%; min-height: calc(100vh - 86px); border: 0; background: #fff; }
|
||||
/* Checkerboard backdrop so white/transparent images still read as an image, not a blank pane. */
|
||||
@@ -637,7 +698,7 @@ button.btn.danger { color: var(--accent); }
|
||||
border: 1px solid var(--line);
|
||||
background: repeating-conic-gradient(var(--check-a) 0% 25%, var(--check-b) 0% 50%) 0 0 / 20px 20px;
|
||||
}
|
||||
.artifact-code { margin: 0; padding: 22px 26px; font-family: var(--mono); font-size: 12.5px; line-height: 1.55; white-space: pre-wrap; color: var(--ink); }
|
||||
.artifact-code { margin: 0; padding: 22px 26px; font-family: var(--mono); font-size: 13px; line-height: 1.55; white-space: pre-wrap; color: var(--ink); }
|
||||
/* Non-previewable binaries (Office docs): a centered prompt to open in the default app. */
|
||||
.artifact-open-prompt {
|
||||
height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
@@ -654,7 +715,7 @@ button.btn.danger { color: var(--accent); }
|
||||
|
||||
/* csv / spreadsheet preview */
|
||||
.artifact-tablewrap { padding: 14px 16px 40px; overflow: auto; }
|
||||
.artifact-table { border-collapse: collapse; font-size: 12.5px; white-space: nowrap; }
|
||||
.artifact-table { border-collapse: collapse; font-size: 13px; white-space: nowrap; }
|
||||
.artifact-table th, .artifact-table td { border: 1px solid var(--line); padding: 5px 10px; text-align: left; max-width: 340px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.artifact-table th { background: var(--paper); font-weight: 600; position: sticky; top: 0; }
|
||||
.artifact-table-note { padding: 10px 16px; }
|
||||
@@ -668,15 +729,15 @@ button.btn.danger { color: var(--accent); }
|
||||
}
|
||||
.sheet-tab.active { color: var(--ink); background: var(--panel); border-color: var(--line); }
|
||||
/* markdown artifacts reuse the shared .md renderer inside a readable padded column */
|
||||
.artifact-md { padding: 26px 28px 70px; font-size: 14.5px; line-height: 1.6; color: var(--ink); }
|
||||
.artifact-md { padding: 26px 28px 70px; font-size: 14px; line-height: 1.6; color: var(--ink); }
|
||||
.artifact-md .md h1 { font-size: 24px; }
|
||||
.artifact-md .md h2 { font-size: 19px; margin-top: 22px; }
|
||||
.artifact-md .md h2 { font-size: 20px; margin-top: 22px; }
|
||||
|
||||
/* working-directory bar */
|
||||
/* Workspace chip lives INSIDE the composer card's head row (top chip row, Tailwind-laid-out). */
|
||||
.wschip {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
font: inherit; font-size: 12.5px; color: var(--ink);
|
||||
font: inherit; font-size: 13px; color: var(--ink);
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 8px;
|
||||
padding: 4px 9px; cursor: pointer; max-width: 60%;
|
||||
}
|
||||
@@ -706,7 +767,7 @@ button.btn.danger { color: var(--accent); }
|
||||
.root-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
|
||||
.root-label { font-size: 13px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.root-path { font-size: 11px; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }
|
||||
.root-tag { font-size: 10.5px; color: var(--muted); background: var(--line); border-radius: 5px; padding: 1px 5px; flex: none; }
|
||||
.root-tag { font-size: 11px; color: var(--muted); background: var(--line); border-radius: 5px; padding: 1px 5px; flex: none; }
|
||||
.root-tag.warn { color: var(--warn-ink); background: var(--warn-soft); }
|
||||
.root-access {
|
||||
font: inherit; font-size: 11px; cursor: pointer; flex: none;
|
||||
@@ -714,7 +775,7 @@ button.btn.danger { color: var(--accent); }
|
||||
}
|
||||
.root-access.rw { color: var(--ok); border-color: var(--ok-line); background: var(--ok-soft); }
|
||||
.root-access:disabled { cursor: default; opacity: 0.7; }
|
||||
.root-x { font: inherit; font-size: 15px; line-height: 1; color: var(--faint); background: none; border: none; cursor: pointer; padding: 0 4px; flex: none; }
|
||||
.root-x { font: inherit; font-size: 14px; line-height: 1; color: var(--faint); background: none; border: none; cursor: pointer; padding: 0 4px; flex: none; }
|
||||
.root-x:hover { color: var(--ink); }
|
||||
.roots-add { padding: 8px 2px 2px; border-top: 1px solid var(--line); margin-top: 6px; }
|
||||
.roots-err { color: var(--danger); font-size: 12px; padding: 6px 6px 2px; }
|
||||
@@ -722,7 +783,7 @@ button.btn.danger { color: var(--accent); }
|
||||
/* add-folder form (shared: roots popover + session start panel) */
|
||||
.addfolder-trigger {
|
||||
display: inline-flex; align-items: center; gap: 7px; width: 100%; justify-content: center;
|
||||
font: inherit; font-size: 12.5px; cursor: pointer; color: var(--ink);
|
||||
font: inherit; font-size: 13px; cursor: pointer; color: var(--ink);
|
||||
background: var(--paper); border: 1px dashed var(--line-strong); border-radius: 8px; padding: 7px 10px;
|
||||
}
|
||||
.addfolder-trigger:hover { border-color: var(--accent); color: var(--accent); }
|
||||
@@ -768,11 +829,11 @@ button.btn.danger { color: var(--accent); }
|
||||
}
|
||||
.task-card:first-child { border-top: none; }
|
||||
.task-card-body { flex: 1; min-width: 0; }
|
||||
.task-card-title { display: block; font-size: 14.5px; transition: color 0.12s; }
|
||||
.task-card-title { display: block; font-size: 14px; transition: color 0.12s; }
|
||||
.task-card:hover .task-card-title { color: var(--accent); }
|
||||
.task-card-sub {
|
||||
display: flex; align-items: center; gap: 7px;
|
||||
font-size: 12.5px; color: var(--faint); margin-top: 3px;
|
||||
font-size: 13px; color: var(--faint); margin-top: 3px;
|
||||
}
|
||||
.task-dot {
|
||||
width: 18px; height: 18px; border-radius: 50%; flex: none;
|
||||
@@ -782,7 +843,7 @@ button.btn.danger { color: var(--accent); }
|
||||
.task-dot + .task-dot { margin-left: -4px; }
|
||||
.task-dot.off { filter: grayscale(1); opacity: 0.7; }
|
||||
.task-card-act {
|
||||
color: var(--faint); font-size: 12.5px; flex: none;
|
||||
color: var(--faint); font-size: 13px; flex: none;
|
||||
opacity: 0; transition: opacity 0.12s;
|
||||
}
|
||||
.task-card:hover .task-card-act { opacity: 1; color: var(--accent); }
|
||||
@@ -794,7 +855,7 @@ button.btn.danger { color: var(--accent); }
|
||||
padding: 5px 10px; margin-right: 6px;
|
||||
border: 1px solid var(--line-strong); border-radius: 8px;
|
||||
background: var(--panel); color: var(--muted); cursor: pointer;
|
||||
font: inherit; font-size: 12.5px;
|
||||
font: inherit; font-size: 13px;
|
||||
}
|
||||
.topbar-artifacts-btn:hover { color: var(--ink); border-color: var(--accent); }
|
||||
.topbar-artifacts-count {
|
||||
@@ -812,7 +873,7 @@ button.btn.danger { color: var(--accent); }
|
||||
broken (owner catch 2026-08-15 on the tool-request card). */
|
||||
max-width: 760px; margin: 0 auto 9px;
|
||||
}
|
||||
.dirreq-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13.5px; color: var(--ink); }
|
||||
.dirreq-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px; color: var(--ink); }
|
||||
.dirreq-head .ico { color: var(--accent); }
|
||||
.dirreq-reason { font-size: 13px; color: var(--muted); font-style: italic; margin: 6px 0 10px; }
|
||||
/* the coworker's justification gets an explicit label — the quote alone made readers
|
||||
@@ -821,11 +882,11 @@ button.btn.danger { color: var(--accent); }
|
||||
/* request_tool fact strip — the product's own metadata (version, publisher, checksum),
|
||||
deliberately NOT italic so it can't be misread as the coworker still talking */
|
||||
.toolreq-facts {
|
||||
font-size: 12.5px; color: var(--muted); font-style: normal;
|
||||
font-size: 13px; color: var(--muted); font-style: normal;
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 8px;
|
||||
padding: 7px 10px; margin: 2px 0 10px;
|
||||
}
|
||||
.toolreq-facts code { color: var(--ink); font-size: 12.5px; }
|
||||
.toolreq-facts code { color: var(--ink); font-size: 13px; }
|
||||
.toolreq-factrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
|
||||
.toolreq-factrow code + .toolreq-fact::before { content: "· "; color: var(--muted); }
|
||||
.toolreq-explain { margin-top: 4px; }
|
||||
@@ -839,12 +900,12 @@ button.btn.danger { color: var(--accent); }
|
||||
}
|
||||
.dirreq-path:focus { border-color: var(--accent); }
|
||||
.dirreq-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
|
||||
.dirreq-access { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--muted); cursor: pointer; }
|
||||
.dirreq-access { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); cursor: pointer; }
|
||||
.dirreq-actions .spacer { flex: 1; }
|
||||
|
||||
/* propose_plan prompt (plan-mode exit) */
|
||||
.plan-card .plan-body {
|
||||
max-height: 320px; overflow-y: auto; font-size: 13.5px;
|
||||
max-height: 320px; overflow-y: auto; font-size: 13px;
|
||||
margin: 8px 0 2px; padding: 8px 10px;
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 8px;
|
||||
}
|
||||
@@ -854,17 +915,17 @@ button.btn.danger { color: var(--accent); }
|
||||
.gate { width: 480px; max-width: 92vw; background: var(--paper); border: 1px solid var(--line-strong); border-radius: 16px; padding: 26px 26px 22px; box-shadow: 0 20px 60px rgba(0,0,0,0.18); }
|
||||
.gate-mark { color: var(--accent); font-size: 22px; }
|
||||
.gate h2 { font-family: var(--serif); font-size: 24px; margin: 6px 0 4px; }
|
||||
.gate-sub { color: var(--muted); font-size: 13.5px; margin: 0 0 16px; }
|
||||
.gate-sub { color: var(--muted); font-size: 13px; margin: 0 0 16px; }
|
||||
.gate-input { display: flex; gap: 8px; }
|
||||
.gate-input input { flex: 1; font: inherit; font-size: 14px; padding: 9px 12px; border: 1px solid var(--line-strong); border-radius: 9px; background: var(--panel); outline: none; }
|
||||
.gate-input input:focus { border-color: var(--accent); }
|
||||
.gate-error { color: var(--accent); font-size: 12.5px; margin-top: 8px; }
|
||||
.gate-error { color: var(--accent); font-size: 13px; margin-top: 8px; }
|
||||
.gate-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--faint); margin: 18px 0 6px; }
|
||||
.gate-recents { display: flex; flex-direction: column; max-height: 200px; overflow-y: auto; }
|
||||
.gate-recent { display: flex; flex-direction: column; padding: 8px 10px; border-radius: 8px; cursor: pointer; }
|
||||
.gate-recent:hover { background: var(--panel); }
|
||||
.gate-recent .folder { font-size: 14px; color: var(--ink); }
|
||||
.gate-recent .dim { font-size: 11.5px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gate-recent .dim { font-size: 12px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gate-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; }
|
||||
.gate-link { font-size: 13px; color: var(--accent); cursor: pointer; }
|
||||
.gate-link:hover { text-decoration: underline; }
|
||||
@@ -876,7 +937,7 @@ button.btn.danger { color: var(--accent); }
|
||||
|
||||
/* Small inline segmented control (Appearance: Light / Dark / Auto). */
|
||||
.seg { display: inline-flex; gap: 4px; background: var(--panel); border: 1px solid var(--line); padding: 3px; border-radius: 9px; }
|
||||
.seg button { font: inherit; font-size: 12.5px; padding: 5px 14px; border: none; border-radius: 7px; background: transparent; color: var(--muted); cursor: pointer; }
|
||||
.seg button { font: inherit; font-size: 13px; padding: 5px 14px; border: none; border-radius: 7px; background: transparent; color: var(--muted); cursor: pointer; }
|
||||
.seg button.active { background: var(--accent-soft); color: var(--ink); }
|
||||
|
||||
/* Model checklist (Onboarding + Configure Models): tick = in the composer picker. */
|
||||
@@ -885,15 +946,15 @@ button.btn.danger { color: var(--accent); }
|
||||
.mlist-row:hover { background: var(--panel); }
|
||||
.mlist-main { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; cursor: pointer; }
|
||||
.mlist-main input { accent-color: var(--accent); margin: 0; }
|
||||
.mlist-name { font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.mlist-name { font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.mlist-row.off .mlist-name { color: var(--muted); }
|
||||
.mlist-default {
|
||||
background: var(--ink); color: var(--panel); font-size: 10.5px; font-weight: 600;
|
||||
background: var(--ink); color: var(--panel); font-size: 11px; font-weight: 600;
|
||||
padding: 2px 9px; border-radius: 999px; letter-spacing: 0.03em; flex: none;
|
||||
}
|
||||
.mlist-make {
|
||||
visibility: hidden; border: 1px solid var(--line-strong); background: var(--panel);
|
||||
border-radius: 7px; padding: 3px 9px; font-size: 11.5px; color: var(--muted); cursor: pointer; flex: none;
|
||||
border-radius: 7px; padding: 3px 9px; font-size: 12px; color: var(--muted); cursor: pointer; flex: none;
|
||||
}
|
||||
.mlist-row:hover .mlist-make { visibility: visible; }
|
||||
.mlist-make:hover { color: var(--ink); border-color: var(--ink); }
|
||||
@@ -911,11 +972,11 @@ button.btn.danger { color: var(--accent); }
|
||||
|
||||
/* .mcp-error is used by ScheduledView; the MCP servers list itself is Tailwind-styled inline
|
||||
(ManageTabs McpTab/McpRow/AddForm). */
|
||||
.mcp-error { color: var(--accent); font-size: 12.5px; margin-top: 6px; }
|
||||
.mcp-error { color: var(--accent); font-size: 13px; margin-top: 6px; }
|
||||
|
||||
button.btn-primary { font: inherit; font-size: 13px; padding: 8px 16px; border-radius: 9px; border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer; align-self: flex-start; }
|
||||
button.btn-primary:hover { filter: brightness(1.05); }
|
||||
button.link { font: inherit; font-size: 12.5px; background: none; border: none; color: var(--muted); cursor: pointer; padding: 0; }
|
||||
button.link { font: inherit; font-size: 13px; background: none; border: none; color: var(--muted); cursor: pointer; padding: 0; }
|
||||
button.link:hover { color: var(--ink); }
|
||||
button.link.danger:hover { color: var(--accent); }
|
||||
button.link:disabled { color: var(--faint); cursor: default; }
|
||||
@@ -929,14 +990,14 @@ button.link:disabled { color: var(--faint); cursor: default; }
|
||||
|
||||
/* -- Connectors tab --------------------------------------------------------- */
|
||||
.conn-name { font-size: 14px; color: var(--ink); display: flex; align-items: center; gap: 8px; }
|
||||
.conn-meta { font-size: 11.5px; color: var(--faint); margin-top: 2px; display: flex; align-items: center; gap: 5px; }
|
||||
.conn-meta { font-size: 12px; color: var(--faint); margin-top: 2px; display: flex; align-items: center; gap: 5px; }
|
||||
.conn-meta.ok, .conn-meta .ok { color: var(--ok); }
|
||||
.conn-meta.danger, .conn-meta .danger { color: var(--danger); }
|
||||
button.btn-primary.sm { padding: 5px 13px; font-size: 12.5px; }
|
||||
button.btn.sm { padding: 5px 13px; font-size: 12.5px; }
|
||||
button.btn-primary.sm { padding: 5px 13px; font-size: 13px; }
|
||||
button.btn.sm { padding: 5px 13px; font-size: 13px; }
|
||||
|
||||
.conn-field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.conn-field-label { font-size: 12.5px; color: var(--ink); }
|
||||
.conn-field-label { font-size: 13px; color: var(--ink); }
|
||||
.conn-field-label em { color: var(--faint); font-style: normal; }
|
||||
.conn-field input { font: inherit; font-size: 13px; padding: 8px 11px; border: 1px solid var(--line-strong); border-radius: 9px; background: var(--paper); color: var(--ink); outline: none; }
|
||||
.conn-field input:focus { border-color: var(--accent); }
|
||||
@@ -951,7 +1012,7 @@ button.btn.sm { padding: 5px 13px; font-size: 12.5px; }
|
||||
background-repeat: no-repeat; background-position: right 11px center; background-size: 12px;
|
||||
}
|
||||
.conn-field select:focus { border-color: var(--accent); }
|
||||
.conn-field-help { font-size: 11.5px; color: var(--faint); }
|
||||
.conn-field-help { font-size: 12px; color: var(--faint); }
|
||||
|
||||
.sa-sub { font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); margin-top: 4px; }
|
||||
.dim { color: var(--faint); }
|
||||
@@ -960,7 +1021,7 @@ button.btn.sm { padding: 5px 13px; font-size: 12.5px; }
|
||||
.sched-detail { display: flex; flex-direction: column; gap: 10px; }
|
||||
.sched-detail-head { display: flex; align-items: center; justify-content: space-between; }
|
||||
.sched-actions { display: flex; align-items: center; gap: 12px; }
|
||||
.sched-instructions { font-size: 13.5px; color: var(--ink); white-space: pre-wrap; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px; line-height: 1.5; }
|
||||
.sched-instructions { font-size: 13px; color: var(--ink); white-space: pre-wrap; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px; line-height: 1.5; }
|
||||
/* "Allowed without asking" — the automation's standing-approval revoke list (§25). */
|
||||
.sched-grants { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
|
||||
.sched-grant { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 13px; border-bottom: 1px solid var(--line); font-size: 13px; }
|
||||
@@ -979,7 +1040,7 @@ button.btn.sm { padding: 5px 13px; font-size: 12.5px; }
|
||||
/* Detail "Delete" button — danger-tinted line button with the trash glyph. */
|
||||
button.btn.sm.danger-btn { display: inline-flex; align-items: center; gap: 5px; color: var(--danger); border-color: var(--line-strong); }
|
||||
button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
.sched-edit-title { font-size: 18px; font-weight: 600; max-width: 360px; }
|
||||
.sched-edit-title { font-size: var(--fs-title); font-weight: 600; max-width: 360px; }
|
||||
.sched-edit-sched { margin: 12px 0 4px; }
|
||||
.sched-edit-instr { min-height: 96px; }
|
||||
.sched-run { border-bottom: 1px solid var(--line); }
|
||||
@@ -988,14 +1049,14 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
/* UX-023: marks runs newer than the seen mark captured when the detail opened. */
|
||||
.run-new-pill {
|
||||
display: inline-block; margin-right: 8px; padding: 0 6px; border-radius: 999px;
|
||||
background: var(--accent); color: #fff; font-size: 10px; font-weight: 600; line-height: 16px;
|
||||
background: var(--accent); color: #fff; font-size: 11px; font-weight: 600; line-height: 16px;
|
||||
}
|
||||
/* Run row = a launcher into the run's conversation. */
|
||||
.sched-run.open { border: 1px solid var(--line); border-radius: 10px; margin-bottom: 8px; padding: 4px 12px 10px; cursor: pointer; background: var(--panel); }
|
||||
.sched-run.open:hover { border-color: var(--line-strong); background: var(--paper); }
|
||||
.sched-run.open .sched-run-row { cursor: pointer; }
|
||||
.sched-run-go { color: var(--accent); font-size: 12.5px; font-weight: 500; }
|
||||
.sched-run-peek { font-size: 12.5px; color: var(--muted); line-height: 1.45; max-height: 2.9em; overflow: hidden; margin-top: 2px; }
|
||||
.sched-run-go { color: var(--accent); font-size: 13px; font-weight: 500; }
|
||||
.sched-run-peek { font-size: 13px; color: var(--muted); line-height: 1.45; max-height: 2.9em; overflow: hidden; margin-top: 2px; }
|
||||
|
||||
/* Desktop boot splash — shown while the Tauri sidecar server comes up. */
|
||||
.boot-splash {
|
||||
@@ -1061,7 +1122,7 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
padding-left: 82px; /* clear the macOS traffic lights */
|
||||
user-select: none;
|
||||
}
|
||||
.titlebar-brand { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; color: var(--muted); user-select: none; }
|
||||
.titlebar-brand { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; font-weight: 600; color: var(--muted); user-select: none; }
|
||||
.titlebar-brand .mark { color: var(--accent); }
|
||||
/* The sidebar's brand row IS the desktop title bar (pin control + wordmark). On desktop the
|
||||
traffic lights float top-left, so shift the row RIGHT (not down) to clear them and keep it at
|
||||
@@ -1109,7 +1170,7 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
gap: 11px;
|
||||
padding: 9px 11px;
|
||||
border-radius: 9px;
|
||||
font-size: 13.5px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.ob-rail-item.active { color: var(--ink); background: var(--accent-soft); }
|
||||
@@ -1120,7 +1181,7 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 11.5px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background: var(--line);
|
||||
color: var(--muted);
|
||||
@@ -1129,7 +1190,7 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
.ob-rail-item.active .ob-dot { background: var(--accent); color: #fff; }
|
||||
.ob-rail-item.done .ob-dot { background: var(--ok); color: #fff; }
|
||||
.ob-body { grid-column: 2; padding: 40px 40px 16px; overflow-y: auto; }
|
||||
.ob-step h2 { font-family: var(--serif); margin: 0 0 8px; font-size: 25px; color: var(--ink); }
|
||||
.ob-step h2 { font-family: var(--serif); margin: 0 0 8px; font-size: 24px; color: var(--ink); }
|
||||
.ob-sub { color: var(--muted); font-size: 14px; line-height: 1.55; margin: 0 0 22px; }
|
||||
.ob-mark { color: var(--accent); font-size: 30px; margin-bottom: 12px; }
|
||||
.ob-label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--faint); margin: 20px 0 7px; }
|
||||
@@ -1149,7 +1210,7 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
.ob-row input:focus, .ob-select:focus, .ob-input:focus { border-color: var(--accent); }
|
||||
.ob-select, .ob-input { width: 100%; }
|
||||
.ob-input { box-sizing: border-box; display: block; }
|
||||
.ob-note { font-size: 12.5px; margin-top: 9px; color: var(--ink); }
|
||||
.ob-note { font-size: 13px; margin-top: 9px; color: var(--ink); }
|
||||
.ob-note.dim, .dim { color: var(--faint); }
|
||||
.ob-toggle {
|
||||
display: flex;
|
||||
@@ -1162,8 +1223,8 @@ button.btn.sm.danger-btn:hover { border-color: var(--danger); }
|
||||
.ob-toggle.disabled { opacity: 0.5; cursor: default; }
|
||||
.ob-toggle input { margin-top: 3px; accent-color: var(--accent); }
|
||||
.ob-toggle span { display: flex; flex-direction: column; gap: 3px; }
|
||||
.ob-toggle strong { font-weight: 500; font-size: 13.5px; color: var(--ink); }
|
||||
.ob-toggle small { color: var(--muted); font-size: 12.5px; }
|
||||
.ob-toggle strong { font-weight: 500; font-size: 13px; color: var(--ink); }
|
||||
.ob-toggle small { color: var(--muted); font-size: 13px; }
|
||||
.ob-check {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -1187,29 +1248,29 @@ button.btn.ghost:hover { color: var(--ink); }
|
||||
|
||||
/* Welcome value-prop bullets */
|
||||
.ob-valueprops { list-style: none; margin: 0 0 18px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.ob-valueprops li { position: relative; padding-left: 22px; font-size: 13.5px; color: var(--muted); line-height: 1.5; }
|
||||
.ob-valueprops li { position: relative; padding-left: 22px; font-size: 13px; color: var(--muted); line-height: 1.5; }
|
||||
.ob-valueprops li::before { content: "✓"; position: absolute; left: 0; top: 0; color: var(--ok); font-weight: 700; }
|
||||
.ob-valueprops strong { color: var(--ink); font-weight: 600; }
|
||||
|
||||
/* Model step: detect / verify status (one line) + key help — generous spacing so the stacked
|
||||
states don't feel cramped. */
|
||||
.ob-status { margin-top: 12px; font-size: 12.5px; line-height: 1.5; }
|
||||
.ob-status { margin-top: 12px; font-size: 13px; line-height: 1.5; }
|
||||
.ob-status.ob-ok { color: var(--ok); }
|
||||
.ob-status.ob-detected { color: var(--ok); }
|
||||
.ob-status.ob-err { color: var(--danger); }
|
||||
.ob-keyhelp { margin-top: 18px; }
|
||||
.ob-link {
|
||||
background: none; border: none; padding: 0; cursor: pointer;
|
||||
color: var(--accent); font: inherit; font-size: 12.5px;
|
||||
color: var(--accent); font: inherit; font-size: 13px;
|
||||
}
|
||||
.ob-link:hover { text-decoration: underline; }
|
||||
.ob-keyhelp-body {
|
||||
margin-top: 8px; padding: 10px 12px;
|
||||
background: var(--accent-soft); border: 1px solid var(--line);
|
||||
border-radius: 9px; font-size: 12.5px; color: var(--ink);
|
||||
border-radius: 9px; font-size: 13px; color: var(--ink);
|
||||
}
|
||||
.ob-url {
|
||||
flex: 1; font-size: 11.5px; color: var(--muted);
|
||||
flex: 1; font-size: 12px; color: var(--muted);
|
||||
overflow-wrap: anywhere; user-select: all;
|
||||
}
|
||||
|
||||
@@ -1217,7 +1278,7 @@ button.btn.ghost:hover { color: var(--ink); }
|
||||
.ob-skipwarn {
|
||||
grid-column: 2; margin: 0 40px; padding: 12px 14px;
|
||||
background: var(--warn-soft); border: 1px solid var(--warn-ink);
|
||||
border-radius: 10px; font-size: 12.5px; color: var(--warn-ink);
|
||||
border-radius: 10px; font-size: 13px; color: var(--warn-ink);
|
||||
display: flex; flex-direction: column; gap: 10px;
|
||||
}
|
||||
.ob-skipwarn-actions { display: flex; gap: 8px; }
|
||||
@@ -1234,7 +1295,7 @@ button.btn.ghost:hover { color: var(--ink); }
|
||||
.attach-chip.img img { width: 46px; height: 46px; object-fit: cover; border-radius: 9px; border: 1px solid var(--line-strong); display: block; }
|
||||
.attach-x {
|
||||
position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; border-radius: 50%;
|
||||
border: none; background: var(--solid); color: var(--on-solid); font-size: 10px; line-height: 1; cursor: pointer;
|
||||
border: none; background: var(--solid); color: var(--on-solid); font-size: 11px; line-height: 1; cursor: pointer;
|
||||
display: grid; place-items: center;
|
||||
}
|
||||
.icon-btn {
|
||||
@@ -1259,7 +1320,7 @@ button.btn.ghost:hover { color: var(--ink); }
|
||||
.tmpl-input::placeholder { color: var(--faint); }
|
||||
.tmpl-textarea { resize: vertical; min-height: 64px; line-height: 1.45; }
|
||||
.tmpl-sched { display: flex; gap: 14px; flex-wrap: wrap; }
|
||||
.tmpl-field { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--muted); }
|
||||
.tmpl-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
|
||||
.tmpl-time, .tmpl-select { padding: 6px 9px; }
|
||||
.tmpl-form-actions { display: flex; align-items: center; gap: 12px; margin-top: 2px; }
|
||||
|
||||
@@ -1270,7 +1331,7 @@ button.btn.ghost:hover { color: var(--ink); }
|
||||
display: inline-flex; align-items: center; gap: 7px; margin: 8px 0 2px;
|
||||
max-width: 100%; padding: 4px 8px 4px 5px; border: 1px solid var(--line);
|
||||
border-radius: 999px; background: var(--paper); color: var(--muted);
|
||||
font: inherit; font-size: 12.5px; cursor: pointer;
|
||||
font: inherit; font-size: 13px; cursor: pointer;
|
||||
}
|
||||
.inbox-session-chip:hover:not(:disabled) { color: var(--ink); border-color: var(--line-strong); }
|
||||
.inbox-session-chip:disabled { cursor: default; opacity: 0.6; }
|
||||
@@ -1398,9 +1459,9 @@ html[data-theme="dark"] :not(.connector-badge) > .connector-icon[data-dark-mark]
|
||||
background: var(--accent-soft); color: var(--accent); display: grid; place-items: center;
|
||||
}
|
||||
.art-chip-meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
|
||||
.art-chip-meta b { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.art-chip-meta b { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.art-chip-meta span { font-size: 11px; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.art-chip-open { margin-left: 8px; flex: none; font-size: 11.5px; color: var(--accent); font-weight: 500; }
|
||||
.art-chip-open { margin-left: 8px; flex: none; font-size: 12px; color: var(--accent); font-weight: 500; }
|
||||
|
||||
/* Seventeenth pass: [Board · N items](board:) — the lead's one-time board mention, an
|
||||
inline pill (smaller than the artifact chip: it opens a panel, not a document). */
|
||||
@@ -1659,7 +1720,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.artifact-folder-row {
|
||||
display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
|
||||
padding: 7px 10px; border: 0; border-radius: 8px; background: none; cursor: pointer;
|
||||
color: var(--ink); font-size: 12.5px;
|
||||
color: var(--ink); font-size: 13px;
|
||||
}
|
||||
.artifact-folder-row:hover { background: var(--paper); }
|
||||
.artifact-folder-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
@@ -1670,7 +1731,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.board-rail-quiet { font-size: 12px; color: var(--faint); padding: 2px 0 4px; }
|
||||
.board-finished-toggle {
|
||||
align-self: flex-start; border: none; background: none; padding: 4px 0 0; cursor: pointer;
|
||||
font-size: 11.5px; color: var(--faint);
|
||||
font-size: 12px; color: var(--faint);
|
||||
}
|
||||
.board-finished-toggle:hover { color: var(--muted); text-decoration: underline; }
|
||||
|
||||
@@ -1683,7 +1744,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.boardwake-head {
|
||||
display: flex; align-items: center; gap: 7px; width: 100%; text-align: left;
|
||||
border: none; background: none; cursor: pointer; padding: 7px 10px;
|
||||
font-size: 12.5px; color: var(--muted);
|
||||
font-size: 13px; color: var(--muted);
|
||||
}
|
||||
.boardwake-head:hover { background: var(--paper); }
|
||||
.boardwake-title { font-weight: 600; color: var(--ink); }
|
||||
@@ -1695,7 +1756,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.boardwake-row-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
||||
.boardwake-row-text { font-size: 12px; color: var(--ink); }
|
||||
.boardwake-note {
|
||||
font-size: 11.5px; color: var(--muted); white-space: pre-wrap; overflow-wrap: anywhere;
|
||||
font-size: 12px; color: var(--muted); white-space: pre-wrap; overflow-wrap: anywhere;
|
||||
border-left: 2px solid var(--line); padding-left: 8px;
|
||||
}
|
||||
.boardwake-note-toggle {
|
||||
@@ -1704,19 +1765,19 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
}
|
||||
.boardwake-note-toggle:hover { text-decoration: underline; }
|
||||
.board-group {
|
||||
font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase;
|
||||
font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
|
||||
color: var(--faint); font-weight: 700; margin: 8px 0 3px;
|
||||
}
|
||||
.board-rail > div:first-child .board-group { margin-top: 0; }
|
||||
.board-row {
|
||||
display: flex; align-items: flex-start; gap: 8px; width: 100%; text-align: left;
|
||||
padding: 5px 6px; border: 0; border-radius: 7px; background: transparent;
|
||||
cursor: pointer; color: var(--muted); font-size: 12.5px;
|
||||
cursor: pointer; color: var(--muted); font-size: 13px;
|
||||
}
|
||||
.board-row:hover { background: var(--paper); }
|
||||
.board-row-main { display: flex; flex-direction: column; min-width: 0; }
|
||||
.board-row-title { color: var(--ink); overflow: hidden; text-overflow: ellipsis; }
|
||||
.board-row-id { color: var(--faint); font-weight: 600; font-size: 11.5px; }
|
||||
.board-row-id { color: var(--faint); font-weight: 600; font-size: 12px; }
|
||||
.board-row-who { font-size: 11px; color: var(--faint); }
|
||||
.board-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; margin-top: 5px; background: var(--faint); }
|
||||
.board-dot.work { background: var(--ok-dot); }
|
||||
@@ -1740,7 +1801,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
display: flex; align-items: center; gap: 10px; padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.board-overlay-title { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13.5px; color: var(--ink); flex: 1; }
|
||||
.board-overlay-title { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px; color: var(--ink); flex: 1; }
|
||||
.board-overlay-space { color: var(--faint); font-weight: 400; font-size: 12px; }
|
||||
.board-overlay-body { flex: 1; display: flex; min-height: 0; }
|
||||
|
||||
@@ -1758,7 +1819,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.board-lrow:hover { background: var(--paper); }
|
||||
.board-lrow.sel { background: color-mix(in srgb, var(--accent) 8%, transparent); }
|
||||
.board-lrow .board-dot { margin-top: 0; flex: none; }
|
||||
.board-lrow-id { color: var(--faint); font-size: 11.5px; width: 26px; flex: none; }
|
||||
.board-lrow-id { color: var(--faint); font-size: 12px; width: 26px; flex: none; }
|
||||
.board-lrow-title {
|
||||
flex: 1; min-width: 0; font-size: 13px;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
@@ -1773,21 +1834,21 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
width: 400px; flex: none; border-left: 1px solid var(--line);
|
||||
padding: 20px 24px; overflow-y: auto; min-height: 0;
|
||||
}
|
||||
.board-detail-title { font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
|
||||
.board-detail-title { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
|
||||
.board-detail-id { color: var(--faint); font-weight: 400; font-size: 13px; margin-right: 2px; }
|
||||
.board-detail-meta { font-size: 12.5px; color: var(--muted); margin-top: 5px; }
|
||||
.board-detail-meta { font-size: 13px; color: var(--muted); margin-top: 5px; }
|
||||
.board-detail-st { font-weight: 500; }
|
||||
.st-review { color: var(--warn, #b45309); }
|
||||
.st-blocked { color: var(--danger, #dc2626); }
|
||||
.st-in_progress { color: var(--accent); }
|
||||
.board-detail-worker {
|
||||
border: none; background: none; padding: 0; font: inherit; font-size: 12.5px;
|
||||
border: none; background: none; padding: 0; font: inherit; font-size: 13px;
|
||||
color: var(--accent); cursor: pointer;
|
||||
}
|
||||
.board-detail-worker:hover { text-decoration: underline; }
|
||||
.board-detail-desc { margin-top: 12px; font-size: 12.5px; color: var(--muted); white-space: pre-wrap; }
|
||||
.board-detail-desc { margin-top: 12px; font-size: 13px; color: var(--muted); white-space: pre-wrap; }
|
||||
.board-detail-crit {
|
||||
margin-top: 12px; font-size: 12.5px; color: var(--ink);
|
||||
margin-top: 12px; font-size: 13px; color: var(--ink);
|
||||
max-height: 84px; overflow-y: auto; padding-right: 8px;
|
||||
}
|
||||
.board-detail-label { color: var(--faint); font-weight: 600; }
|
||||
@@ -1803,21 +1864,21 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.board-tl-ev.blocked::before { background: var(--danger, #dc2626); }
|
||||
.board-tl-ev.work::before { background: var(--accent); }
|
||||
.board-tl-line { font-size: 12px; color: var(--faint); }
|
||||
.board-tl-line b { color: var(--ink); font-weight: 600; font-size: 12.5px; }
|
||||
.board-tl-body { font-size: 12.5px; color: var(--muted); margin-top: 3px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.board-tl-line b { color: var(--ink); font-weight: 600; font-size: 13px; }
|
||||
.board-tl-body { font-size: 13px; color: var(--muted); margin-top: 3px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.board-shot { display: block; width: 170px; margin-top: 8px; border-radius: 10px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.14); text-decoration: none; }
|
||||
.board-shot img { display: block; width: 100%; max-height: 120px; object-fit: cover; background: var(--paper); }
|
||||
.board-shot-cap { display: block; font-size: 10.5px; color: var(--faint); padding: 4px 8px; background: var(--panel); }
|
||||
.board-shot-cap { display: block; font-size: 11px; color: var(--faint); padding: 4px 8px; background: var(--panel); }
|
||||
|
||||
.board-note-input {
|
||||
width: 100%; margin-top: 14px; font: inherit; font-size: 12.5px; color: var(--ink);
|
||||
width: 100%; margin-top: 14px; font: inherit; font-size: 13px; color: var(--ink);
|
||||
background: var(--paper); border: 1px solid var(--line); border-radius: 999px;
|
||||
padding: 7px 14px; outline: none;
|
||||
}
|
||||
.board-note-input::placeholder { color: var(--faint); }
|
||||
.board-note-input:focus { border-color: var(--accent); background: var(--panel); }
|
||||
.board-detail-actions { display: flex; gap: 14px; align-items: center; margin-top: 20px; }
|
||||
.board-btn { font-size: 12.5px; cursor: pointer; font-family: inherit; }
|
||||
.board-btn { font-size: 13px; cursor: pointer; font-family: inherit; }
|
||||
.board-btn.primary {
|
||||
color: #fff; background: var(--accent); border: none; border-radius: 999px; padding: 6px 16px;
|
||||
}
|
||||
@@ -1826,7 +1887,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.board-btn.ghost:hover { color: var(--ink); }
|
||||
.board-changes { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
||||
.board-changes textarea {
|
||||
width: 100%; min-height: 64px; resize: vertical; font: inherit; font-size: 12.5px;
|
||||
width: 100%; min-height: 64px; resize: vertical; font: inherit; font-size: 13px;
|
||||
color: var(--ink); background: var(--paper); border: 1px solid var(--line);
|
||||
border-radius: 10px; padding: 8px 10px; outline: none;
|
||||
}
|
||||
@@ -1835,7 +1896,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
|
||||
/* Journal rail section */
|
||||
.journal-list { display: flex; flex-direction: column; gap: 3px; }
|
||||
.journal-row { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--muted); padding: 3px 4px; }
|
||||
.journal-row { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); padding: 3px 4px; }
|
||||
.journal-case { color: var(--ink); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
||||
.journal-count { font-size: 11px; color: var(--faint); }
|
||||
|
||||
@@ -1846,12 +1907,12 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.teamreq-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
||||
.teamreq-title { font-weight: 600; font-size: 13px; color: var(--ink); }
|
||||
.teamreq-note { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
|
||||
.teamreq-row { display: flex; gap: 9px; padding: 7px 0; border-top: 1px solid var(--line); font-size: 12.5px; }
|
||||
.teamreq-row { display: flex; gap: 9px; padding: 7px 0; border-top: 1px solid var(--line); font-size: 13px; }
|
||||
.teamreq-diamond { color: var(--faint); font-size: 11px; padding-top: 1px; }
|
||||
.teamreq-body code { background: var(--paper); border-radius: 5px; padding: 1px 6px; font-size: 12px; }
|
||||
.teamreq-model { color: var(--muted); }
|
||||
.teamreq-reason { color: var(--faint); }
|
||||
.teamreq-grant { font-size: 11.5px; color: var(--faint); }
|
||||
.teamreq-grant { font-size: 12px; color: var(--faint); }
|
||||
|
||||
/* Sidebar: the expandable team entry (workers nest under their lead) */
|
||||
.team-child { margin-left: 18px; }
|
||||
@@ -1869,8 +1930,8 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.itemsreq-item { display: flex; gap: 9px; padding: 7px 0; border-top: 1px solid var(--line); }
|
||||
.itemsreq-num { color: var(--faint); font-size: 12px; padding-top: 1px; }
|
||||
.itemsreq-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||||
.itemsreq-item-title { font-size: 12.5px; color: var(--ink); }
|
||||
.itemsreq-ac { font-size: 11.5px; color: var(--muted); }
|
||||
.itemsreq-item-title { font-size: 13px; color: var(--ink); }
|
||||
.itemsreq-ac { font-size: 12px; color: var(--muted); }
|
||||
.itemsreq-ac b { font-weight: 600; }
|
||||
/* Essay-length criteria clamp to two lines; the expander reveals the rest. The
|
||||
list itself caps so a big proposal never occupies two screens of chat. */
|
||||
@@ -1884,25 +1945,25 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.itemsreq-ac-toggle:hover { text-decoration: underline; }
|
||||
.itemsreq-list { max-height: 320px; overflow-y: auto; }
|
||||
.itemsreq-more { display: flex; align-items: center; gap: 5px; border: 0; background: transparent; color: var(--accent); font-size: 12px; cursor: pointer; padding: 6px 0; }
|
||||
.itemsreq-grant { font-size: 11.5px; color: var(--faint); }
|
||||
.itemsreq-grant { font-size: 12px; color: var(--faint); }
|
||||
|
||||
/* # team chat */
|
||||
.teamreq-name { color: var(--ink); font-weight: 600; }
|
||||
.teamreq-chat { display: flex; align-items: center; gap: 8px; padding: 9px 0 2px; border-top: 1px solid var(--line); font-size: 12.5px; color: var(--ink); cursor: pointer; }
|
||||
.teamreq-chat { display: flex; align-items: center; gap: 8px; padding: 9px 0 2px; border-top: 1px solid var(--line); font-size: 13px; color: var(--ink); cursor: pointer; }
|
||||
.teamreq-info { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border: 1px solid var(--line-strong); border-radius: 50%; color: var(--faint); font-size: 9px; cursor: help; }
|
||||
.chat-panel { max-width: 860px; }
|
||||
.chat-hash { color: var(--faint); font-weight: 700; }
|
||||
.chat-scroll { flex: 1; overflow: auto; padding: 14px 18px; display: flex; flex-direction: column; }
|
||||
.chat-empty { color: var(--faint); font-size: 12.5px; margin: auto; max-width: 420px; text-align: center; }
|
||||
.chat-empty { color: var(--faint); font-size: 13px; margin: auto; max-width: 420px; text-align: center; }
|
||||
.chat-msg { margin-top: 12px; }
|
||||
.chat-msg.grouped { margin-top: 2px; padding-left: 34px; }
|
||||
.chat-who { display: flex; align-items: baseline; gap: 8px; }
|
||||
.chat-avatar { width: 26px; height: 26px; border-radius: 7px; background: var(--paper); border: 1px solid var(--line-strong); display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; align-self: center; }
|
||||
.chat-avatar.lead { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
|
||||
.chat-avatar.user { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok); }
|
||||
.chat-name { font-size: 12.5px; font-weight: 700; color: var(--ink); }
|
||||
.chat-name { font-size: 13px; font-weight: 700; color: var(--ink); }
|
||||
.chat-role { font-size: 11px; color: var(--faint); }
|
||||
.chat-ts { font-size: 10.5px; color: var(--faint); }
|
||||
.chat-ts { font-size: 11px; color: var(--faint); }
|
||||
.chat-text { font-size: 13px; color: var(--ink); margin-top: 1px; padding-left: 34px; }
|
||||
.chat-msg.grouped .chat-text { padding-left: 0; }
|
||||
.chat-mention { color: var(--accent); background: var(--accent-soft); border-radius: 4px; padding: 0 3px; }
|
||||
@@ -1912,7 +1973,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
|
||||
/* Sidebar chat row */
|
||||
.team-hash { color: var(--faint); font-weight: 700; font-size: 12px; width: 10px; text-align: center; }
|
||||
.team-chat-badge { margin-left: auto; background: var(--accent); color: #fff; font-size: 10px; font-weight: 700; border-radius: 8px; padding: 0 6px; }
|
||||
.team-chat-badge { margin-left: auto; background: var(--accent); color: #fff; font-size: 11px; font-weight: 700; border-radius: 8px; padding: 0 6px; }
|
||||
|
||||
/* # team chat as an in-place view (not a modal): fills .main, sidebar stays live. */
|
||||
.chat-view { position: absolute; inset: 0; z-index: 40; background: var(--paper); display: flex; flex-direction: column; }
|
||||
@@ -1923,7 +1984,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.sleep-strip {
|
||||
max-width: 760px; margin: 0 auto 8px; display: flex; align-items: center; gap: 9px;
|
||||
padding: 8px 12px; border: 1px solid var(--line); border-radius: 10px;
|
||||
background: var(--panel); font-size: 12.5px; color: var(--muted);
|
||||
background: var(--panel); font-size: 13px; color: var(--muted);
|
||||
}
|
||||
.sleep-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--warn-ink); opacity: 0.7; flex-shrink: 0; animation: sleep-pulse 2.4s ease-in-out infinite; }
|
||||
@keyframes sleep-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.85; } }
|
||||
@@ -1945,7 +2006,7 @@ html[data-platform="linux"] ::-webkit-scrollbar-thumb:hover { background-color:
|
||||
.artifact-menu-item {
|
||||
display: flex; width: 100%; align-items: center; gap: 9px; padding: 7px 9px;
|
||||
border: 0; background: none; border-radius: 7px; text-align: left;
|
||||
font-size: 12.5px; color: var(--ink); cursor: pointer;
|
||||
font-size: 13px; color: var(--ink); cursor: pointer;
|
||||
}
|
||||
.artifact-menu-item:hover { background: var(--paper); }
|
||||
.artifact-menu-item svg { color: var(--muted); flex: none; }
|
||||
|
||||
@@ -18,6 +18,9 @@ export default {
|
||||
colors: {
|
||||
paper: tok("--paper"),
|
||||
panel: tok("--panel"),
|
||||
canvas: tok("--canvas"),
|
||||
chrome: tok("--chrome"),
|
||||
chromeHover: tok("--chrome-hover"),
|
||||
ink: tok("--ink"),
|
||||
muted: tok("--muted"),
|
||||
faint: tok("--faint"),
|
||||
|
||||
Reference in New Issue
Block a user