Align page typography to the unified scale
Sweep the remaining inline type across every page onto the system scale: oversized metric numbers (22/24px) down to the 18px title step, body text 13px → 14px, captions/meta 11px and 12.5px → 12px, and small control icons 13/11px → 16/14px. No structural or layout changes — the foundation already gridded spacing, radius, and color; this finishes the type so the whole app reads from one ramp. Full interactive e2e and the overflow guards stay green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f5aab15920
commit
fa01aa682f
31 files changed
+120
-120
No files matched your search
@@ -143,14 +143,14 @@ function ApprovalDetailDialog({
|
|||||||
<IconButton icon="x" label="Close" onClick={onClose} />
|
<IconButton icon="x" label="Close" onClick={onClose} />
|
||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ flexWrap: "wrap", gap: 6, marginBottom: 14 }}>
|
<div className="row" style={{ flexWrap: "wrap", gap: 6, marginBottom: 14 }}>
|
||||||
<span style={{ fontSize: 11, color: tone.color, fontWeight: 540, letterSpacing: "0.02em" }}>{tone.label}</span>
|
<span style={{ fontSize: 12, color: tone.color, fontWeight: 540, letterSpacing: "0.02em" }}>{tone.label}</span>
|
||||||
<span style={{ color: "var(--text-4)" }}>·</span>
|
<span style={{ color: "var(--text-4)" }}>·</span>
|
||||||
<Pill>{approval.kind}</Pill>
|
<Pill>{approval.kind}</Pill>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ margin: "0 0 16px", color: "var(--text-2)", fontSize: 13, lineHeight: 1.55, whiteSpace: "pre-wrap" }}>
|
<p style={{ margin: "0 0 16px", color: "var(--text-2)", fontSize: 14, lineHeight: 1.55, whiteSpace: "pre-wrap" }}>
|
||||||
{approval.details}
|
{approval.details}
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: "10px 14px", fontSize: 12.5 }}>
|
<div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: "10px 14px", fontSize: 12 }}>
|
||||||
<span style={{ color: "var(--text-3)" }}>Requested by</span>
|
<span style={{ color: "var(--text-3)" }}>Requested by</span>
|
||||||
<span>{requester}</span>
|
<span>{requester}</span>
|
||||||
{approval.sourceEntityType && (<><span style={{ color: "var(--text-3)" }}>Source</span><span>{humanizeKind(approval.sourceEntityType)}</span></>)}
|
{approval.sourceEntityType && (<><span style={{ color: "var(--text-3)" }}>Source</span><span>{humanizeKind(approval.sourceEntityType)}</span></>)}
|
||||||
@@ -199,7 +199,7 @@ function StatTile({ label, value, tone }: { label: string; value: string; tone?:
|
|||||||
<div className="card-label" style={{ marginBottom: 6 }}>{label}</div>
|
<div className="card-label" style={{ marginBottom: 6 }}>{label}</div>
|
||||||
<div
|
<div
|
||||||
className="num"
|
className="num"
|
||||||
style={{ fontSize: 22, fontWeight: 600, color: tone === "amber" ? "var(--amber)" : "var(--text-1)", letterSpacing: "-0.02em" }}
|
style={{ fontSize: 18, fontWeight: 600, color: tone === "amber" ? "var(--amber)" : "var(--text-1)", letterSpacing: "-0.02em" }}
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
@@ -241,12 +241,12 @@ function ApprovalRow({
|
|||||||
<div style={{ width: 3, alignSelf: "stretch", background: tone.color, borderRadius: 2 }} />
|
<div style={{ width: 3, alignSelf: "stretch", background: tone.color, borderRadius: 2 }} />
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div className="row" style={{ marginBottom: 4 }}>
|
<div className="row" style={{ marginBottom: 4 }}>
|
||||||
<span style={{ fontSize: 11, color: tone.color, fontWeight: 540, letterSpacing: "0.02em" }}>{tone.label}</span>
|
<span style={{ fontSize: 12, color: tone.color, fontWeight: 540, letterSpacing: "0.02em" }}>{tone.label}</span>
|
||||||
<span style={{ color: "var(--text-4)" }}>·</span>
|
<span style={{ color: "var(--text-4)" }}>·</span>
|
||||||
<Pill>{approval.kind}</Pill>
|
<Pill>{approval.kind}</Pill>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 15, fontWeight: 540, letterSpacing: "-0.01em", marginBottom: 4 }}>{approval.title}</div>
|
<div style={{ fontSize: 15, fontWeight: 540, letterSpacing: "-0.01em", marginBottom: 4 }}>{approval.title}</div>
|
||||||
<div style={{ fontSize: 13, color: "var(--text-2)", lineHeight: 1.5, marginBottom: 12, whiteSpace: "pre-wrap" }}>
|
<div style={{ fontSize: 14, color: "var(--text-2)", lineHeight: 1.5, marginBottom: 12, whiteSpace: "pre-wrap" }}>
|
||||||
{approval.details}
|
{approval.details}
|
||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ gap: 8 }}>
|
<div className="row" style={{ gap: 8 }}>
|
||||||
@@ -282,7 +282,7 @@ function DecidedRow({
|
|||||||
style={{
|
style={{
|
||||||
gap: 12,
|
gap: 12,
|
||||||
padding: "12px 18px",
|
padding: "12px 18px",
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
borderBottom: last ? "none" : "1px solid var(--border-soft)",
|
borderBottom: last ? "none" : "1px solid var(--border-soft)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function DocumentsPage({ snapshot, actions }: PageProps) {
|
|||||||
<option value="all">All types</option>
|
<option value="all">All types</option>
|
||||||
{DOC_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
|
{DOC_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> New document</Button>
|
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> New document</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Documents</h1>
|
<h1 className="page-h1">Documents</h1>
|
||||||
<p className="page-sub">{documents.length} {documents.length === 1 ? "document" : "documents"}</p>
|
<p className="page-sub">{documents.length} {documents.length === 1 ? "document" : "documents"}</p>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function FilesPage({ snapshot, actions }: PageProps) {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Button tone="primary" onClick={upload}>
|
<Button tone="primary" onClick={upload}>
|
||||||
<Icon name="upload" size={13} /> Upload
|
<Icon name="upload" size={16} /> Upload
|
||||||
</Button>
|
</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Files</h1>
|
<h1 className="page-h1">Files</h1>
|
||||||
@@ -104,7 +104,7 @@ export function FilesPage({ snapshot, actions }: PageProps) {
|
|||||||
|
|
||||||
<div className="row" style={{ marginBottom: 10 }}>
|
<div className="row" style={{ marginBottom: 10 }}>
|
||||||
<div className="section-label">{labelFor(section)}</div>
|
<div className="section-label">{labelFor(section)}</div>
|
||||||
<div style={{ marginLeft: "auto", fontSize: 11, color: "var(--text-3)" }}>Modified ↓</div>
|
<div style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-3)" }}>Modified ↓</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{view === "list" ? (
|
{view === "list" ? (
|
||||||
@@ -148,7 +148,7 @@ function FilesFilter({
|
|||||||
<Popover
|
<Popover
|
||||||
trigger={({ toggle }) => (
|
trigger={({ toggle }) => (
|
||||||
<button type="button" className="button ghost" aria-label="Filter files" onClick={toggle}>
|
<button type="button" className="button ghost" aria-label="Filter files" onClick={toggle}>
|
||||||
<Icon name="filter" size={13} color={scan !== "all" ? "var(--accent)" : "currentColor"} />
|
<Icon name="filter" size={16} color={scan !== "all" ? "var(--accent)" : "currentColor"} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -158,7 +158,7 @@ function FilesFilter({
|
|||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<MenuRow key={option.id} active={scan === option.id} onClick={() => onScan(option.id)}>
|
<MenuRow key={option.id} active={scan === option.id} onClick={() => onScan(option.id)}>
|
||||||
{option.label}
|
{option.label}
|
||||||
{scan === option.id && <span className="menu-check"><Icon name="check" size={13} /></span>}
|
{scan === option.id && <span className="menu-check"><Icon name="check" size={16} /></span>}
|
||||||
</MenuRow>
|
</MenuRow>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ export function InboxPage({ snapshot }: PageProps) {
|
|||||||
<div className="row" style={{ gap: 10 }}>
|
<div className="row" style={{ gap: 10 }}>
|
||||||
{item.agentName && <Avatar name={item.agentName} size="sm" bot />}
|
{item.agentName && <Avatar name={item.agentName} size="sm" bot />}
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.title}</div>
|
<div style={{ fontSize: 14, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.title}</div>
|
||||||
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{item.detail}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{item.detail}</div>
|
||||||
</div>
|
</div>
|
||||||
<Pill tone={item.tone}>{humanize(item.kind)}</Pill>
|
<Pill tone={item.tone}>{humanize(item.kind)}</Pill>
|
||||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)", flexShrink: 0 }}>{formatRelative(item.timestamp)}</span>
|
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)", flexShrink: 0 }}>{formatRelative(item.timestamp)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -155,7 +155,7 @@ function MetricCard({ label, value, sub }: { label: string; value: string; sub?:
|
|||||||
<div className="card" style={{ padding: 14 }}>
|
<div className="card" style={{ padding: 14 }}>
|
||||||
<div className="card-label" style={{ marginBottom: 6 }}>{label}</div>
|
<div className="card-label" style={{ marginBottom: 6 }}>{label}</div>
|
||||||
<div className="row" style={{ alignItems: "baseline", gap: 4 }}>
|
<div className="row" style={{ alignItems: "baseline", gap: 4 }}>
|
||||||
<span className="num" style={{ fontSize: 24, fontWeight: 600 }}>{value}</span>
|
<span className="num" style={{ fontSize: 18, fontWeight: 600 }}>{value}</span>
|
||||||
{sub && <span style={{ fontSize: 12, color: "var(--text-3)" }}>{sub}</span>}
|
{sub && <span style={{ fontSize: 12, color: "var(--text-3)" }}>{sub}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function KnowledgePage({ snapshot, actions }: PageProps) {
|
|||||||
<option value="all">All categories</option>
|
<option value="all">All categories</option>
|
||||||
{CATEGORIES.map((c) => <option key={c} value={c}>{c}</option>)}
|
{CATEGORIES.map((c) => <option key={c} value={c}>{c}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> Add entry</Button>
|
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> Add entry</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Knowledge Base</h1>
|
<h1 className="page-h1">Knowledge Base</h1>
|
||||||
<p className="page-sub">{entries.length} {entries.length === 1 ? "entry" : "entries"}</p>
|
<p className="page-sub">{entries.length} {entries.length === 1 ? "entry" : "entries"}</p>
|
||||||
@@ -61,10 +61,10 @@ export function KnowledgePage({ snapshot, actions }: PageProps) {
|
|||||||
<Pill>{entry.category}</Pill>
|
<Pill>{entry.category}</Pill>
|
||||||
<Pill tone={importanceTone(entry.importance)}>{entry.importance}</Pill>
|
<Pill tone={importanceTone(entry.importance)}>{entry.importance}</Pill>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 13, color: "var(--text-2)", lineHeight: 1.5, whiteSpace: "pre-wrap", maxHeight: 120, overflow: "hidden" }}>
|
<div style={{ fontSize: 14, color: "var(--text-2)", lineHeight: 1.5, whiteSpace: "pre-wrap", maxHeight: 120, overflow: "hidden" }}>
|
||||||
{entry.content}
|
{entry.content}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 8, fontSize: 11, color: "var(--text-3)" }}>{formatRelative(entry.updatedAt)}</div>
|
<div style={{ marginTop: 8, fontSize: 12, color: "var(--text-3)" }}>{formatRelative(entry.updatedAt)}</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function MeetingsPage({ snapshot, actions }: PageProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<TopbarActions>
|
<TopbarActions>
|
||||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> Schedule</Button>
|
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> Schedule</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Meetings</h1>
|
<h1 className="page-h1">Meetings</h1>
|
||||||
<p className="page-sub">{scheduled.length} upcoming · {completed.length} past</p>
|
<p className="page-sub">{scheduled.length} upcoming · {completed.length} past</p>
|
||||||
@@ -91,7 +91,7 @@ function MeetingCard({ meeting, onDelete }: { meeting: { id: string; title: stri
|
|||||||
<Pill>{meeting.durationMinutes}m</Pill>
|
<Pill>{meeting.durationMinutes}m</Pill>
|
||||||
</div>
|
</div>
|
||||||
{meeting.scheduledAt && <div style={{ fontSize: 12, color: "var(--text-3)" }}>{new Date(meeting.scheduledAt).toLocaleString()}</div>}
|
{meeting.scheduledAt && <div style={{ fontSize: 12, color: "var(--text-3)" }}>{new Date(meeting.scheduledAt).toLocaleString()}</div>}
|
||||||
{meeting.agenda && <div style={{ fontSize: 13, color: "var(--text-2)", marginTop: 6, whiteSpace: "pre-wrap", lineHeight: 1.5 }}>{meeting.agenda}</div>}
|
{meeting.agenda && <div style={{ fontSize: 14, color: "var(--text-2)", marginTop: 6, whiteSpace: "pre-wrap", lineHeight: 1.5 }}>{meeting.agenda}</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="split-main">
|
<div className="split-main">
|
||||||
<aside className="secondary-pane">
|
<aside className="secondary-pane">
|
||||||
<div style={{ fontSize: 13, fontWeight: 600, padding: "4px 8px 14px" }}>Settings</div>
|
<div style={{ fontSize: 14, fontWeight: 600, padding: "4px 8px 14px" }}>Settings</div>
|
||||||
<div className="pane-scroll">
|
<div className="pane-scroll">
|
||||||
<NavGroup items={TABS} active={tab} onChange={setTab} />
|
<NavGroup items={TABS} active={tab} onChange={setTab} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ export function SocialPage({ snapshot, actions }: PageProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="page" style={{ display: "flex", flexDirection: "column", minHeight: 0, height: "100%", padding: "28px 32px 0" }}>
|
<div className="page" style={{ display: "flex", flexDirection: "column", minHeight: 0, height: "100%", padding: "28px 32px 0" }}>
|
||||||
<TopbarActions>
|
<TopbarActions>
|
||||||
<Button onClick={() => setConnectOpen(true)}><Icon name="plus" size={13} /> Connect account</Button>
|
<Button onClick={() => setConnectOpen(true)}><Icon name="plus" size={16} /> Connect account</Button>
|
||||||
<Button tone="primary" onClick={() => setDraftOpen(true)} disabled={accounts.length === 0}>
|
<Button tone="primary" onClick={() => setDraftOpen(true)} disabled={accounts.length === 0}>
|
||||||
<Icon name="sparkle" size={13} /> Draft post
|
<Icon name="sparkle" size={16} /> Draft post
|
||||||
</Button>
|
</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Social</h1>
|
<h1 className="page-h1">Social</h1>
|
||||||
@@ -224,12 +224,12 @@ function AccountCard({
|
|||||||
<div className="row" style={{ gap: 10, minWidth: 0 }}>
|
<div className="row" style={{ gap: 10, minWidth: 0 }}>
|
||||||
<PlatformIcon platform={account.platform} />
|
<PlatformIcon platform={account.platform} />
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{account.handle}</div>
|
<div style={{ fontSize: 14, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{account.handle}</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{account.displayName}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{account.displayName}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{authorizedName && (
|
{authorizedName && (
|
||||||
<div className="row" style={{ fontSize: 11, color: "var(--text-3)", gap: 6 }}>
|
<div className="row" style={{ fontSize: 12, color: "var(--text-3)", gap: 6 }}>
|
||||||
<span style={{ width: 6, height: 6, borderRadius: 3, background: "var(--green)" }} /> Authorized to {authorizedName}
|
<span style={{ width: 6, height: 6, borderRadius: 3, background: "var(--green)" }} /> Authorized to {authorizedName}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -286,10 +286,10 @@ function PostRow({
|
|||||||
{post.state === "published" && <Pill tone="green" dot>Published</Pill>}
|
{post.state === "published" && <Pill tone="green" dot>Published</Pill>}
|
||||||
{post.state === "cancelled" && <Pill tone="red">Cancelled</Pill>}
|
{post.state === "cancelled" && <Pill tone="red">Cancelled</Pill>}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 13, color: "var(--text-1)", lineHeight: 1.5, marginBottom: 8, whiteSpace: "pre-wrap" }}>{post.bodyMd}</div>
|
<div style={{ fontSize: 14, color: "var(--text-1)", lineHeight: 1.5, marginBottom: 8, whiteSpace: "pre-wrap" }}>{post.bodyMd}</div>
|
||||||
<div className="row" style={{ gap: 6 }}>
|
<div className="row" style={{ gap: 6 }}>
|
||||||
<Avatar name={authorName} size="sm" bot={post.createdByEmployeeId === null} />
|
<Avatar name={authorName} size="sm" bot={post.createdByEmployeeId === null} />
|
||||||
<span style={{ fontSize: 11, color: "var(--text-3)" }}>By {authorName}</span>
|
<span style={{ fontSize: 12, color: "var(--text-3)" }}>By {authorName}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function SprintsPage({ snapshot, actions }: PageProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<TopbarActions>
|
<TopbarActions>
|
||||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> New sprint</Button>
|
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> New sprint</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Sprints</h1>
|
<h1 className="page-h1">Sprints</h1>
|
||||||
<p className="page-sub">{sprints.length} {sprints.length === 1 ? "sprint" : "sprints"}</p>
|
<p className="page-sub">{sprints.length} {sprints.length === 1 ? "sprint" : "sprints"}</p>
|
||||||
@@ -78,7 +78,7 @@ function SprintCard({ sprint, tasks, onDelete }: { sprint: SprintRecord; tasks:
|
|||||||
<Pill tone={sprintStatusTone(sprint.status)}>{sprint.status}</Pill>
|
<Pill tone={sprintStatusTone(sprint.status)}>{sprint.status}</Pill>
|
||||||
<Pill>{sprint.startDate} to {sprint.endDate}</Pill>
|
<Pill>{sprint.startDate} to {sprint.endDate}</Pill>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 13, color: "var(--text-2)", lineHeight: 1.5, marginBottom: 12 }}>{sprint.goal}</div>
|
<div style={{ fontSize: 14, color: "var(--text-2)", lineHeight: 1.5, marginBottom: 12 }}>{sprint.goal}</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
<ProgressBar label="Time" pct={progressPct} />
|
<ProgressBar label="Time" pct={progressPct} />
|
||||||
<ProgressBar label="Tasks" pct={taskPct} />
|
<ProgressBar label="Tasks" pct={taskPct} />
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ export function TeamPage({ snapshot, actions }: PageProps) {
|
|||||||
<div className="page">
|
<div className="page">
|
||||||
<TopbarActions>
|
<TopbarActions>
|
||||||
<Button tone="ghost" onClick={() => cycleFilter(filter, setFilter)} title="Filter">
|
<Button tone="ghost" onClick={() => cycleFilter(filter, setFilter)} title="Filter">
|
||||||
<Icon name="filter" size={13} /> {filterLabel(filter)}
|
<Icon name="filter" size={16} /> {filterLabel(filter)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button tone="primary" onClick={openHireDialog}><Icon name="plus" size={13} /> Hire</Button>
|
<Button tone="primary" onClick={openHireDialog}><Icon name="plus" size={16} /> Hire</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Team</h1>
|
<h1 className="page-h1">Team</h1>
|
||||||
<p className="page-sub">
|
<p className="page-sub">
|
||||||
@@ -266,7 +266,7 @@ function RosterRow({ row, last, onEdit }: { row: RosterEntry; last: boolean; onE
|
|||||||
<Avatar name={row.name} size="sm" bot={!row.human} />
|
<Avatar name={row.name} size="sm" bot={!row.human} />
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row.name}</div>
|
<div style={{ fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row.name}</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row.role}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row.role}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row.dept}</div>
|
<div style={{ color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row.dept}</div>
|
||||||
@@ -296,7 +296,7 @@ function RosterRow({ row, last, onEdit }: { row: RosterEntry; last: boolean; onE
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="num mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{money(row.budgetCents)}</span>
|
<span className="num mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{money(row.budgetCents)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ export function WorkPage({ snapshot, actions }: PageProps) {
|
|||||||
onPriority={setPriorityFilter}
|
onPriority={setPriorityFilter}
|
||||||
onAssignee={setAssigneeFilter}
|
onAssignee={setAssigneeFilter}
|
||||||
/>
|
/>
|
||||||
<Button tone="ghost" onClick={() => setNewGoalOpen(true)}><Icon name="plus" size={13} /> Goal</Button>
|
<Button tone="ghost" onClick={() => setNewGoalOpen(true)}><Icon name="plus" size={16} /> Goal</Button>
|
||||||
<Button tone="ghost" onClick={() => setNewProjectOpen(true)}><Icon name="plus" size={13} /> Project</Button>
|
<Button tone="ghost" onClick={() => setNewProjectOpen(true)}><Icon name="plus" size={16} /> Project</Button>
|
||||||
<Button tone="primary" onClick={() => setNewTaskOpen(true)}><Icon name="plus" size={13} /> New task</Button>
|
<Button tone="primary" onClick={() => setNewTaskOpen(true)}><Icon name="plus" size={16} /> New task</Button>
|
||||||
</TopbarActions>
|
</TopbarActions>
|
||||||
<h1 className="page-h1">Work</h1>
|
<h1 className="page-h1">Work</h1>
|
||||||
<p className="page-sub">
|
<p className="page-sub">
|
||||||
@@ -178,7 +178,7 @@ function WorkFilter({
|
|||||||
<Popover
|
<Popover
|
||||||
trigger={({ toggle }) => (
|
trigger={({ toggle }) => (
|
||||||
<button type="button" className="button ghost" aria-label="Filter tasks" onClick={toggle}>
|
<button type="button" className="button ghost" aria-label="Filter tasks" onClick={toggle}>
|
||||||
<Icon name="filter" size={13} color={active ? "var(--accent)" : "currentColor"} />
|
<Icon name="filter" size={16} color={active ? "var(--accent)" : "currentColor"} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -188,19 +188,19 @@ function WorkFilter({
|
|||||||
{priorities.map((option) => (
|
{priorities.map((option) => (
|
||||||
<MenuRow key={option.id} active={priority === option.id} onClick={() => onPriority(option.id)}>
|
<MenuRow key={option.id} active={priority === option.id} onClick={() => onPriority(option.id)}>
|
||||||
{option.label}
|
{option.label}
|
||||||
{priority === option.id && <span className="menu-check"><Icon name="check" size={13} /></span>}
|
{priority === option.id && <span className="menu-check"><Icon name="check" size={16} /></span>}
|
||||||
</MenuRow>
|
</MenuRow>
|
||||||
))}
|
))}
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuLabel>Assignee</MenuLabel>
|
<MenuLabel>Assignee</MenuLabel>
|
||||||
<MenuRow active={assignee === "all"} onClick={() => onAssignee("all")}>
|
<MenuRow active={assignee === "all"} onClick={() => onAssignee("all")}>
|
||||||
All assignees
|
All assignees
|
||||||
{assignee === "all" && <span className="menu-check"><Icon name="check" size={13} /></span>}
|
{assignee === "all" && <span className="menu-check"><Icon name="check" size={16} /></span>}
|
||||||
</MenuRow>
|
</MenuRow>
|
||||||
{agents.map((agent) => (
|
{agents.map((agent) => (
|
||||||
<MenuRow key={agent.id} active={assignee === agent.id} onClick={() => onAssignee(agent.id)}>
|
<MenuRow key={agent.id} active={assignee === agent.id} onClick={() => onAssignee(agent.id)}>
|
||||||
{agent.name}
|
{agent.name}
|
||||||
{assignee === agent.id && <span className="menu-check"><Icon name="check" size={13} /></span>}
|
{assignee === agent.id && <span className="menu-check"><Icon name="check" size={16} /></span>}
|
||||||
</MenuRow>
|
</MenuRow>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@@ -242,7 +242,7 @@ function BoardView({
|
|||||||
<div key={column.id} style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 0 }}>
|
<div key={column.id} style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 0 }}>
|
||||||
<div className="row" style={{ padding: "4px 6px 8px" }}>
|
<div className="row" style={{ padding: "4px 6px 8px" }}>
|
||||||
<span style={{ width: 8, height: 8, borderRadius: 2, background: column.tone, flexShrink: 0 }} />
|
<span style={{ width: 8, height: 8, borderRadius: 2, background: column.tone, flexShrink: 0 }} />
|
||||||
<span style={{ fontSize: 12.5, fontWeight: 540 }}>{column.label}</span>
|
<span style={{ fontSize: 12, fontWeight: 540 }}>{column.label}</span>
|
||||||
<span className="num" style={{ fontSize: 12, color: "var(--text-3)" }}>{columnTasks.length}</span>
|
<span className="num" style={{ fontSize: 12, color: "var(--text-3)" }}>{columnTasks.length}</span>
|
||||||
<IconButton icon="plus" label="Add task" onClick={onCreate} />
|
<IconButton icon="plus" label="Add task" onClick={onCreate} />
|
||||||
</div>
|
</div>
|
||||||
@@ -427,7 +427,7 @@ function TaskCard({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
fontWeight: 480,
|
fontWeight: 480,
|
||||||
lineHeight: 1.35,
|
lineHeight: 1.35,
|
||||||
letterSpacing: "-0.005em",
|
letterSpacing: "-0.005em",
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function ChatComposer({
|
|||||||
<IconButton icon="sparkle" label="Commands" onClick={() => setSlashOpen((open) => !open)} />
|
<IconButton icon="sparkle" label="Commands" onClick={() => setSlashOpen((open) => !open)} />
|
||||||
{attachment && (
|
{attachment && (
|
||||||
<span className="pill" style={{ maxWidth: 220 }}>
|
<span className="pill" style={{ maxWidth: 220 }}>
|
||||||
<Icon name="file" size={11} />
|
<Icon name="file" size={14} />
|
||||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{attachment.name}</span>
|
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{attachment.name}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -69,12 +69,12 @@ export function ChatComposer({
|
|||||||
onClick={() => onAttachmentChange("")}
|
onClick={() => onAttachmentChange("")}
|
||||||
style={{ display: "inline-flex", border: 0, background: "transparent", padding: 0, color: "var(--text-3)", cursor: "pointer" }}
|
style={{ display: "inline-flex", border: 0, background: "transparent", padding: 0, color: "var(--text-3)", cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<Icon name="x" size={11} />
|
<Icon name="x" size={14} />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6 }}>
|
<div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6 }}>
|
||||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>Ctrl+Enter</span>
|
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>Ctrl+Enter</span>
|
||||||
<Button tone="primary" size="small" type="submit" disabled={disabled}><Icon name="send" size={12} /> Send</Button>
|
<Button tone="primary" size="small" type="submit" disabled={disabled}><Icon name="send" size={12} /> Send</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,13 +58,13 @@ export function ChatMessageRow({
|
|||||||
<Avatar name={name} size={compact ? "sm" : ""} bot={bot} />
|
<Avatar name={name} size={compact ? "sm" : ""} bot={bot} />
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div className="row" style={{ alignItems: "baseline", gap: 8, marginBottom: 2 }}>
|
<div className="row" style={{ alignItems: "baseline", gap: 8, marginBottom: 2 }}>
|
||||||
<span style={{ fontSize: 13, fontWeight: 540 }}>{name}</span>
|
<span style={{ fontSize: 14, fontWeight: 540 }}>{name}</span>
|
||||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{formatTime(message.createdAt)}</span>
|
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{formatTime(message.createdAt)}</span>
|
||||||
{message.editedAt && !deleted && <span style={{ fontSize: 11, color: "var(--text-3)" }}>(edited)</span>}
|
{message.editedAt && !deleted && <span style={{ fontSize: 12, color: "var(--text-3)" }}>(edited)</span>}
|
||||||
{canModify && !editing && (
|
{canModify && !editing && (
|
||||||
<span style={{ marginLeft: "auto", display: "flex", gap: 2 }}>
|
<span style={{ marginLeft: "auto", display: "flex", gap: 2 }}>
|
||||||
<IconButton icon="edit" label="Edit message" size={13} onClick={() => setEditing(true)} />
|
<IconButton icon="edit" label="Edit message" size={16} onClick={() => setEditing(true)} />
|
||||||
<IconButton icon="x" label="Delete message" size={13} onClick={onDelete} />
|
<IconButton icon="x" label="Delete message" size={16} onClick={onDelete} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@ export function ChatMessageRow({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{deleted ? (
|
{deleted ? (
|
||||||
<div style={{ fontSize: 13, color: "var(--text-3)", fontStyle: "italic" }}>Message deleted</div>
|
<div style={{ fontSize: 14, color: "var(--text-3)", fontStyle: "italic" }}>Message deleted</div>
|
||||||
) : editing ? (
|
) : editing ? (
|
||||||
<form onSubmit={submitEdit}>
|
<form onSubmit={submitEdit}>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -91,7 +91,7 @@ export function ChatMessageRow({
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ fontSize: 13, color: "var(--text-1)", lineHeight: 1.5, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
|
<div style={{ fontSize: 14, color: "var(--text-1)", lineHeight: 1.5, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
|
||||||
{renderBody(message.bodyMd, messageMentions, context)}
|
{renderBody(message.bodyMd, messageMentions, context)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -123,7 +123,7 @@ export function ChatMessageRow({
|
|||||||
border: 0,
|
border: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="chat" size={11} /> {replyCount > 0 ? `${replyCount} ${replyCount === 1 ? "reply" : "replies"}` : "Thread"} →
|
<Icon name="chat" size={14} /> {replyCount > 0 ? `${replyCount} ${replyCount === 1 ? "reply" : "replies"}` : "Thread"} →
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -205,14 +205,14 @@ function AttachmentCard({ file, handoff = false, onOpen }: { file: FileObjectRec
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div className="row" style={{ gap: 6, minWidth: 0 }}>
|
<div className="row" style={{ gap: 6, minWidth: 0 }}>
|
||||||
<span style={{ fontSize: 12.5, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{file.name}</span>
|
<span style={{ fontSize: 12, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{file.name}</span>
|
||||||
{handoff && (
|
{handoff && (
|
||||||
<span className="pill violet" style={{ flexShrink: 0 }}>
|
<span className="pill violet" style={{ flexShrink: 0 }}>
|
||||||
<Icon name="bot" size={10} /> AI → AI
|
<Icon name="bot" size={10} /> AI → AI
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{file.kind.toUpperCase()} · {file.sha256.slice(0, 8)}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{file.kind.toUpperCase()} · {file.sha256.slice(0, 8)}</div>
|
||||||
</div>
|
</div>
|
||||||
{canOpen && <IconButton icon="arrowUpRight" label={`Open ${file.name}`} onClick={onOpen ?? undefined} />}
|
{canOpen && <IconButton icon="arrowUpRight" label={`Open ${file.name}`} onClick={onOpen ?? undefined} />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function ChatSidebar({
|
|||||||
return (
|
return (
|
||||||
<aside className="secondary-pane">
|
<aside className="secondary-pane">
|
||||||
<div className="row" style={{ padding: "4px 8px 12px" }}>
|
<div className="row" style={{ padding: "4px 8px 12px" }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 600 }}>Messages</div>
|
<div style={{ fontSize: 14, fontWeight: 600 }}>Messages</div>
|
||||||
<IconButton icon="plus" label="New space" onClick={onCreateSpace} />
|
<IconButton icon="plus" label="New space" onClick={onCreateSpace} />
|
||||||
</div>
|
</div>
|
||||||
<div className="pane-scroll">
|
<div className="pane-scroll">
|
||||||
@@ -81,7 +81,7 @@ export function ChatSidebar({
|
|||||||
function NavGroup({ label, children }: { label: string; children: ReactNode }) {
|
function NavGroup({ label, children }: { label: string; children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 10 }}>
|
<div style={{ marginTop: 10 }}>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)", padding: "6px 8px", fontWeight: 540, letterSpacing: "0.02em" }}>{label}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)", padding: "6px 8px", fontWeight: 540, letterSpacing: "0.02em" }}>{label}</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 1 }}>{children}</div>
|
<div style={{ display: "flex", flexDirection: "column", gap: 1 }}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -104,7 +104,7 @@ function ChannelItem({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button type="button" className={`nav-item ${active ? "active" : ""}`.trim()} onClick={onClick}>
|
<button type="button" className={`nav-item ${active ? "active" : ""}`.trim()} onClick={onClick}>
|
||||||
<Icon name={space.visibility === "restricted" ? "lock" : "hash"} size={13} color={active ? "var(--text-1)" : "var(--text-3)"} />
|
<Icon name={space.visibility === "restricted" ? "lock" : "hash"} size={16} color={active ? "var(--text-1)" : "var(--text-3)"} />
|
||||||
<span>{space.name}</span>
|
<span>{space.name}</span>
|
||||||
{count > 0 && <span className="nav-badge">{count}</span>}
|
{count > 0 && <span className="nav-badge">{count}</span>}
|
||||||
</button>
|
</button>
|
||||||
@@ -136,7 +136,7 @@ function DirectItem({
|
|||||||
{dotColor && <span className="presence-dot" style={{ background: dotColor }} />}
|
{dotColor && <span className="presence-dot" style={{ background: dotColor }} />}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Icon name="user" size={13} />
|
<Icon name="user" size={16} />
|
||||||
)}
|
)}
|
||||||
<span>{space.name}</span>
|
<span>{space.name}</span>
|
||||||
{count > 0 && <span className="nav-badge">{count}</span>}
|
{count > 0 && <span className="nav-badge">{count}</span>}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function ChatThreadPanel({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)", flexShrink: 0 }}>
|
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)", flexShrink: 0 }}>
|
||||||
<div style={{ fontSize: 12.5, fontWeight: 600 }}>Thread</div>
|
<div style={{ fontSize: 12, fontWeight: 600 }}>Thread</div>
|
||||||
<Pill tone={boardStateTone(thread.boardState)}>{boardStateLabel(thread.boardState)}</Pill>
|
<Pill tone={boardStateTone(thread.boardState)}>{boardStateLabel(thread.boardState)}</Pill>
|
||||||
<div style={{ marginLeft: "auto" }}>
|
<div style={{ marginLeft: "auto" }}>
|
||||||
<IconButton icon="x" label="Close thread" onClick={onClose} />
|
<IconButton icon="x" label="Close thread" onClick={onClose} />
|
||||||
@@ -134,7 +134,7 @@ export function ChatThreadPanel({
|
|||||||
<IconButton icon="attach" label="Attach file" onClick={onAttach} />
|
<IconButton icon="attach" label="Attach file" onClick={onAttach} />
|
||||||
{attachment && (
|
{attachment && (
|
||||||
<span className="pill" style={{ maxWidth: 150 }}>
|
<span className="pill" style={{ maxWidth: 150 }}>
|
||||||
<Icon name="file" size={11} />
|
<Icon name="file" size={14} />
|
||||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{attachment.name}</span>
|
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{attachment.name}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -142,7 +142,7 @@ export function ChatThreadPanel({
|
|||||||
onClick={() => onAttachmentChange("")}
|
onClick={() => onAttachmentChange("")}
|
||||||
style={{ display: "inline-flex", border: 0, background: "transparent", padding: 0, color: "var(--text-3)", cursor: "pointer" }}
|
style={{ display: "inline-flex", border: 0, background: "transparent", padding: 0, color: "var(--text-3)", cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<Icon name="x" size={11} />
|
<Icon name="x" size={14} />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -215,7 +215,7 @@ function BoardControl({
|
|||||||
{interventions.map((intervention) => (
|
{interventions.map((intervention) => (
|
||||||
<div key={intervention.id} style={{ borderTop: "1px solid var(--border-soft)", paddingTop: 7 }}>
|
<div key={intervention.id} style={{ borderTop: "1px solid var(--border-soft)", paddingTop: 7 }}>
|
||||||
<div className="row" style={{ gap: 6, marginBottom: 3 }}>
|
<div className="row" style={{ gap: 6, marginBottom: 3 }}>
|
||||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{formatTime(intervention.createdAt)}</span>
|
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{formatTime(intervention.createdAt)}</span>
|
||||||
<Pill tone={intervention.mode === "takeover" ? "violet" : intervention.action === "freeze" ? "red" : undefined}>
|
<Pill tone={intervention.mode === "takeover" ? "violet" : intervention.action === "freeze" ? "red" : undefined}>
|
||||||
{formatBoardAction(intervention.action)}
|
{formatBoardAction(intervention.action)}
|
||||||
</Pill>
|
</Pill>
|
||||||
@@ -264,7 +264,7 @@ function AuditReplay({ events }: { events: AuditEventRecord[] }) {
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)", flexShrink: 0 }}>{formatTime(event.createdAt)}</span>
|
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)", flexShrink: 0 }}>{formatTime(event.createdAt)}</span>
|
||||||
<span style={{ fontSize: 12, color: "var(--text-1)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<span style={{ fontSize: 12, color: "var(--text-1)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
{auditEventLabel(event.entityType, event.action)}
|
{auditEventLabel(event.entityType, event.action)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ function DayDivider({ label }: { label: string }) {
|
|||||||
return (
|
return (
|
||||||
<div className="row" style={{ gap: 12, margin: "8px 0 16px" }}>
|
<div className="row" style={{ gap: 12, margin: "8px 0 16px" }}>
|
||||||
<div style={{ flex: 1, height: 1, background: "var(--border-soft)" }} />
|
<div style={{ flex: 1, height: 1, background: "var(--border-soft)" }} />
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)", fontWeight: 540 }}>{label}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)", fontWeight: 540 }}>{label}</div>
|
||||||
<div style={{ flex: 1, height: 1, background: "var(--border-soft)" }} />
|
<div style={{ flex: 1, height: 1, background: "var(--border-soft)" }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -126,7 +126,7 @@ function ApprovalInline({
|
|||||||
<Pill tone={approval.risk === "high" ? "red" : approval.risk === "medium" ? "amber" : undefined}>{approval.risk}</Pill>
|
<Pill tone={approval.risk === "high" ? "red" : approval.risk === "medium" ? "amber" : undefined}>{approval.risk}</Pill>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 14, fontWeight: 540, marginBottom: 4 }}>{approval.title}</div>
|
<div style={{ fontSize: 14, fontWeight: 540, marginBottom: 4 }}>{approval.title}</div>
|
||||||
<div style={{ fontSize: 12.5, color: "var(--text-3)", marginBottom: 12, lineHeight: 1.5 }}>{approval.details}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)", marginBottom: 12, lineHeight: 1.5 }}>{approval.details}</div>
|
||||||
<div className="row" style={{ gap: 6 }}>
|
<div className="row" style={{ gap: 6 }}>
|
||||||
<Button tone="primary" size="small" onClick={onApprove}>Approve</Button>
|
<Button tone="primary" size="small" onClick={onApprove}>Approve</Button>
|
||||||
<Button size="small" onClick={onDecline}>Decline</Button>
|
<Button size="small" onClick={onDecline}>Decline</Button>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export function MentionTextarea({
|
|||||||
border: 0,
|
border: 0,
|
||||||
background: i === index ? "var(--accent-soft)" : "transparent",
|
background: i === index ? "var(--accent-soft)" : "transparent",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
color: "var(--text-1)",
|
color: "var(--text-1)",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
}}
|
}}
|
||||||
@@ -155,7 +155,7 @@ export function MentionTextarea({
|
|||||||
border: 0,
|
border: 0,
|
||||||
resize: "none",
|
resize: "none",
|
||||||
outline: "none",
|
outline: "none",
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
color: "var(--text-1)",
|
color: "var(--text-1)",
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function PinnedCard({
|
|||||||
<Icon name="pin" size={14} />
|
<Icon name="pin" size={14} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 540, letterSpacing: "-0.005em", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<div style={{ fontSize: 14, fontWeight: 540, letterSpacing: "-0.005em", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
{file.name}
|
{file.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="mono" style={{ color: "var(--text-3)", fontSize: 10, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<div className="mono" style={{ color: "var(--text-3)", fontSize: 10, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
@@ -92,12 +92,12 @@ export function PinnedCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ gap: 6 }}>
|
<div className="row" style={{ gap: 6 }}>
|
||||||
<Pill tone={file.scanState === "approved" ? "green" : "red"}>{file.scanState === "approved" ? "Clean" : "Blocked"}</Pill>
|
<Pill tone={file.scanState === "approved" ? "green" : "red"}>{file.scanState === "approved" ? "Clean" : "Blocked"}</Pill>
|
||||||
<span className="mono" style={{ color: "var(--text-3)", fontSize: 11 }}>{grantCount}</span>
|
<span className="mono" style={{ color: "var(--text-3)", fontSize: 12 }}>{grantCount}</span>
|
||||||
</div>
|
</div>
|
||||||
<ScanLabelStrip file={file} />
|
<ScanLabelStrip file={file} />
|
||||||
<div className="row" style={{ gap: 8, marginTop: "auto", minWidth: 0 }}>
|
<div className="row" style={{ gap: 8, marginTop: "auto", minWidth: 0 }}>
|
||||||
<Avatar name={ownerName} size="sm" bot={isAgentOwner(file)} />
|
<Avatar name={ownerName} size="sm" bot={isAgentOwner(file)} />
|
||||||
<span style={{ fontSize: 11, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<span style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
{formatRelative(file.updatedAt ?? file.createdAt)} · {ownerName.split(" ")[0]}
|
{formatRelative(file.updatedAt ?? file.createdAt)} · {ownerName.split(" ")[0]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ function FileRow({
|
|||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ gap: 6, minWidth: 0 }}>
|
<div className="row" style={{ gap: 6, minWidth: 0 }}>
|
||||||
<Pill tone={canOpen ? "green" : "red"}>{canOpen ? "Clean" : "Blocked"}</Pill>
|
<Pill tone={canOpen ? "green" : "red"}>{canOpen ? "Clean" : "Blocked"}</Pill>
|
||||||
<span className="mono" style={{ color: "var(--text-3)", fontSize: 11 }}>{grantCount}</span>
|
<span className="mono" style={{ color: "var(--text-3)", fontSize: 12 }}>{grantCount}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: "var(--text-3)", fontSize: 12 }}>{formatRelative(file.updatedAt ?? file.createdAt)}</div>
|
<div style={{ color: "var(--text-3)", fontSize: 12 }}>{formatRelative(file.updatedAt ?? file.createdAt)}</div>
|
||||||
<div style={{ textAlign: "right" }} onClick={(event) => event.stopPropagation()}>
|
<div style={{ textAlign: "right" }} onClick={(event) => event.stopPropagation()}>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function BillingPanel({
|
|||||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 20 }}>
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 20 }}>
|
||||||
<div className="card" style={{ padding: 14 }}>
|
<div className="card" style={{ padding: 14 }}>
|
||||||
<div className="card-label" style={{ marginBottom: 8 }}>Token usage (30d)</div>
|
<div className="card-label" style={{ marginBottom: 8 }}>Token usage (30d)</div>
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: "6px 14px", fontSize: 12.5 }}>
|
<div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: "6px 14px", fontSize: 12 }}>
|
||||||
<span style={{ color: "var(--text-3)" }}>Input chars</span><span className="num">{totalInputChars.toLocaleString()}</span>
|
<span style={{ color: "var(--text-3)" }}>Input chars</span><span className="num">{totalInputChars.toLocaleString()}</span>
|
||||||
<span style={{ color: "var(--text-3)" }}>Output chars</span><span className="num">{totalOutputChars.toLocaleString()}</span>
|
<span style={{ color: "var(--text-3)" }}>Output chars</span><span className="num">{totalOutputChars.toLocaleString()}</span>
|
||||||
<span style={{ color: "var(--text-3)" }}>Total</span><span className="num">{(totalInputChars + totalOutputChars).toLocaleString()}</span>
|
<span style={{ color: "var(--text-3)" }}>Total</span><span className="num">{(totalInputChars + totalOutputChars).toLocaleString()}</span>
|
||||||
@@ -66,8 +66,8 @@ export function BillingPanel({
|
|||||||
</div>
|
</div>
|
||||||
<div className="num mono">{money(row.agent.budgetCents)}</div>
|
<div className="num mono">{money(row.agent.budgetCents)}</div>
|
||||||
<div className="num mono">{row.requestCount}</div>
|
<div className="num mono">{row.requestCount}</div>
|
||||||
<div className="num mono" style={{ fontSize: 11 }}>{row.inputChars.toLocaleString()}</div>
|
<div className="num mono" style={{ fontSize: 12 }}>{row.inputChars.toLocaleString()}</div>
|
||||||
<div className="num mono" style={{ fontSize: 11 }}>{row.outputChars.toLocaleString()}</div>
|
<div className="num mono" style={{ fontSize: 12 }}>{row.outputChars.toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -85,7 +85,7 @@ export function BillingPanel({
|
|||||||
<div key={provider.id} className="table-row" style={{ gridTemplateColumns: "minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr)", borderBottom: index === providers.length - 1 ? "none" : "1px solid var(--border-soft)" }}>
|
<div key={provider.id} className="table-row" style={{ gridTemplateColumns: "minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr)", borderBottom: index === providers.length - 1 ? "none" : "1px solid var(--border-soft)" }}>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 540 }}>{provider.label}</div>
|
<div style={{ fontWeight: 540 }}>{provider.label}</div>
|
||||||
{provider.model && <div style={{ fontSize: 11, color: "var(--text-3)" }}>{provider.model}</div>}
|
{provider.model && <div style={{ fontSize: 12, color: "var(--text-3)" }}>{provider.model}</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className="num mono">{requests.length}</div>
|
<div className="num mono">{requests.length}</div>
|
||||||
<div>{errors > 0 ? <Pill tone="amber">{errors}</Pill> : <span className="num mono" style={{ color: "var(--text-3)" }}>0</span>}</div>
|
<div>{errors > 0 ? <Pill tone="amber">{errors}</Pill> : <span className="num mono" style={{ color: "var(--text-3)" }}>0</span>}</div>
|
||||||
@@ -102,7 +102,7 @@ function StatTile({ label, value, tone }: { label: string; value: string; tone?:
|
|||||||
return (
|
return (
|
||||||
<div className="card" style={{ padding: 14 }}>
|
<div className="card" style={{ padding: 14 }}>
|
||||||
<div className="card-label" style={{ marginBottom: 6 }}>{label}</div>
|
<div className="card-label" style={{ marginBottom: 6 }}>{label}</div>
|
||||||
<div className="num" style={{ fontSize: 22, fontWeight: 600, color: tone === "red" ? "var(--red)" : "var(--text-1)" }}>{value}</div>
|
<div className="num" style={{ fontSize: 18, fontWeight: 600, color: tone === "red" ? "var(--red)" : "var(--text-1)" }}>{value}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function MigrationPanel({
|
|||||||
<p className="page-sub" style={{ marginBottom: 0 }}>{reports.length} migration {reports.length === 1 ? "report" : "reports"}.</p>
|
<p className="page-sub" style={{ marginBottom: 0 }}>{reports.length} migration {reports.length === 1 ? "report" : "reports"}.</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginLeft: "auto" }}>
|
<div style={{ marginLeft: "auto" }}>
|
||||||
<Button tone="primary" onClick={() => setOpen(true)}><Icon name="plus" size={13} /> Report</Button>
|
<Button tone="primary" onClick={() => setOpen(true)}><Icon name="plus" size={16} /> Report</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{reports.length > 0 && (
|
{reports.length > 0 && (
|
||||||
@@ -67,7 +67,7 @@ export function MigrationPanel({
|
|||||||
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
|
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
|
||||||
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
|
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
|
||||||
<Icon name="refresh" size={14} color="var(--text-3)" />
|
<Icon name="refresh" size={14} color="var(--text-3)" />
|
||||||
<div style={{ fontSize: 13, fontWeight: 600 }}>Rollback snapshots</div>
|
<div style={{ fontSize: 14, fontWeight: 600 }}>Rollback snapshots</div>
|
||||||
</div>
|
</div>
|
||||||
{rollbackSnapshots.length > 0 ? rollbackSnapshots.map((snapshot, index) => (
|
{rollbackSnapshots.length > 0 ? rollbackSnapshots.map((snapshot, index) => (
|
||||||
<RollbackRow
|
<RollbackRow
|
||||||
@@ -83,7 +83,7 @@ export function MigrationPanel({
|
|||||||
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
|
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
|
||||||
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
|
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
|
||||||
<Icon name="list" size={14} color="var(--text-3)" />
|
<Icon name="list" size={14} color="var(--text-3)" />
|
||||||
<div style={{ fontSize: 13, fontWeight: 600 }}>Reconciliation</div>
|
<div style={{ fontSize: 14, fontWeight: 600 }}>Reconciliation</div>
|
||||||
</div>
|
</div>
|
||||||
{reconciliationItems.length > 0 ? reconciliationItems.map((item, index) => (
|
{reconciliationItems.length > 0 ? reconciliationItems.map((item, index) => (
|
||||||
<ReconciliationRow
|
<ReconciliationRow
|
||||||
@@ -125,7 +125,7 @@ function ReportRow({
|
|||||||
>
|
>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={report.sourceRef}>{report.sourceRef}</div>
|
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={report.sourceRef}>{report.sourceRef}</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{formatRelative(report.createdAt)}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(report.createdAt)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ flexWrap: "wrap", gap: 4 }}>
|
<div className="row" style={{ flexWrap: "wrap", gap: 4 }}>
|
||||||
{Object.entries(body.tableCounts).map(([table, count]) => (
|
{Object.entries(body.tableCounts).map(([table, count]) => (
|
||||||
@@ -169,7 +169,7 @@ function RollbackRow({
|
|||||||
>
|
>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={snapshot.sourceDatabasePath}>{snapshot.sourceDatabasePath}</div>
|
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={snapshot.sourceDatabasePath}>{snapshot.sourceDatabasePath}</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{formatRelative(snapshot.createdAt)}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(snapshot.createdAt)}</div>
|
||||||
</div>
|
</div>
|
||||||
<Pill>{snapshot.sourceDatabaseSha256.slice(0, 12)}</Pill>
|
<Pill>{snapshot.sourceDatabaseSha256.slice(0, 12)}</Pill>
|
||||||
<Pill>{manifest.workspaces.length} workspaces</Pill>
|
<Pill>{manifest.workspaces.length} workspaces</Pill>
|
||||||
@@ -206,7 +206,7 @@ function ReconciliationRow({
|
|||||||
>
|
>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.targetType} · {item.targetId}</div>
|
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.targetType} · {item.targetId}</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{formatRelative(item.createdAt)}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(item.createdAt)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 12, color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.reason}</div>
|
<div style={{ fontSize: 12, color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.reason}</div>
|
||||||
<Pill tone={resolved ? "green" : "amber"}>{item.state}</Pill>
|
<Pill tone={resolved ? "green" : "amber"}>{item.state}</Pill>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function ProviderConnectFields({
|
|||||||
if (entry.authType === "api_key") {
|
if (entry.authType === "api_key") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
<p style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||||
<Field label="API key"><input className="input" name="secret" type="password" required autoFocus /></Field>
|
<Field label="API key"><input className="input" name="secret" type="password" required autoFocus /></Field>
|
||||||
<ModelPicker entry={entry} provider={provider} />
|
<ModelPicker entry={entry} provider={provider} />
|
||||||
</>
|
</>
|
||||||
@@ -50,7 +50,7 @@ export function ProviderOAuthFields({
|
|||||||
const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
|
const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
<p style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||||
<Field label="Default model">
|
<Field label="Default model">
|
||||||
{entry.models.length ? (
|
{entry.models.length ? (
|
||||||
<select className="input" value={model} onChange={(event) => setModel(event.target.value)}>
|
<select className="input" value={model} onChange={(event) => setModel(event.target.value)}>
|
||||||
@@ -77,7 +77,7 @@ function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; pr
|
|||||||
const [baseUrl, setBaseUrl] = useState("");
|
const [baseUrl, setBaseUrl] = useState("");
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
<p style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||||
<Field label="Base URL">
|
<Field label="Base URL">
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input"
|
||||||
@@ -139,7 +139,7 @@ function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; pro
|
|||||||
const update = (patch: Partial<CloudFields>) => setState((current) => ({ ...current, ...patch }));
|
const update = (patch: Partial<CloudFields>) => setState((current) => ({ ...current, ...patch }));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
<p style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||||
<Field label="Base URL">
|
<Field label="Base URL">
|
||||||
<input className="input" name="cloudBaseUrl" value={state.baseUrl} onChange={(event) => update({ baseUrl: event.target.value })} />
|
<input className="input" name="cloudBaseUrl" value={state.baseUrl} onChange={(event) => update({ baseUrl: event.target.value })} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function ProviderRequestPanel({
|
|||||||
</div>
|
</div>
|
||||||
<Button type="submit" tone="primary">
|
<Button type="submit" tone="primary">
|
||||||
<span className="row" style={{ gap: 6 }}>
|
<span className="row" style={{ gap: 6 }}>
|
||||||
<Icon name="send" size={13} />
|
<Icon name="send" size={16} />
|
||||||
Run
|
Run
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -169,7 +169,7 @@ function ProviderRequestRow({
|
|||||||
>
|
>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{provider.label}</div>
|
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{provider.label}</div>
|
||||||
<div className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{request.model}</div>
|
<div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{request.model}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mono" style={{ color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace?.name ?? "Organization"}</div>
|
<div className="mono" style={{ color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace?.name ?? "Organization"}</div>
|
||||||
<div className="mono" style={{ color: "var(--text-2)" }}>{request.executionMode}</div>
|
<div className="mono" style={{ color: "var(--text-2)" }}>{request.executionMode}</div>
|
||||||
|
|||||||
@@ -302,8 +302,8 @@ function ProviderRow({
|
|||||||
</div>
|
</div>
|
||||||
<ProviderHealth provider={provider} />
|
<ProviderHealth provider={provider} />
|
||||||
<div style={{ textAlign: "right", minWidth: 120 }}>
|
<div style={{ textAlign: "right", minWidth: 120 }}>
|
||||||
<div className="num mono" style={{ fontSize: 13, fontWeight: 540 }}>{boundCount} {boundCount === 1 ? "agent" : "agents"}</div>
|
<div className="num mono" style={{ fontSize: 14, fontWeight: 540 }}>{boundCount} {boundCount === 1 ? "agent" : "agents"}</div>
|
||||||
<div className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>bound</div>
|
<div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>bound</div>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={onCheck}>Check</Button>
|
<Button onClick={onCheck}>Check</Button>
|
||||||
<Button onClick={onManage}>Manage</Button>
|
<Button onClick={onManage}>Manage</Button>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function SkillsPanel({ orgId, skills, revisions, bindings, tests, agents,
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginLeft: "auto" }}>
|
<div style={{ marginLeft: "auto" }}>
|
||||||
<Button tone="primary" onClick={() => setImportOpen(true)}><Icon name="plus" size={13} /> Import</Button>
|
<Button tone="primary" onClick={() => setImportOpen(true)}><Icon name="plus" size={16} /> Import</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export function SkillsPanel({ orgId, skills, revisions, bindings, tests, agents,
|
|||||||
>
|
>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{skill.name}</div>
|
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{skill.name}</div>
|
||||||
<div style={{ color: "var(--text-3)", fontSize: 11 }}>{skill.slug}</div>
|
<div style={{ color: "var(--text-3)", fontSize: 12 }}>{skill.slug}</div>
|
||||||
</div>
|
</div>
|
||||||
<Pill>{latest ? latest.versionSemver : "—"}</Pill>
|
<Pill>{latest ? latest.versionSemver : "—"}</Pill>
|
||||||
<Pill>{testSummary(revisionTests)}</Pill>
|
<Pill>{testSummary(revisionTests)}</Pill>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function WorkspacePanel({
|
|||||||
<div className="card" style={{ padding: 18 }}>
|
<div className="card" style={{ padding: 18 }}>
|
||||||
<div className="row" style={{ marginBottom: 12 }}>
|
<div className="row" style={{ marginBottom: 12 }}>
|
||||||
<div className="section-label">Workspaces</div>
|
<div className="section-label">Workspaces</div>
|
||||||
<span style={{ marginLeft: "auto" }}><Button tone="ghost" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> Add</Button></span>
|
<span style={{ marginLeft: "auto" }}><Button tone="ghost" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> Add</Button></span>
|
||||||
</div>
|
</div>
|
||||||
{workspaces.map((workspace) => (
|
{workspaces.map((workspace) => (
|
||||||
<div key={workspace.id} className="row" style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
|
<div key={workspace.id} className="row" style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
|
||||||
@@ -84,7 +84,7 @@ export function WorkspacePanel({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{workspaces.length === 0 && <div style={{ fontSize: 13, color: "var(--text-3)" }}>No workspaces configured</div>}
|
{workspaces.length === 0 && <div style={{ fontSize: 14, color: "var(--text-3)" }}>No workspaces configured</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog title="Add workspace" open={createOpen} submitLabel="Create" onClose={() => setCreateOpen(false)} onSubmit={submit}>
|
<Dialog title="Add workspace" open={createOpen} submitLabel="Create" onClose={() => setCreateOpen(false)} onSubmit={submit}>
|
||||||
@@ -96,7 +96,7 @@ export function WorkspacePanel({
|
|||||||
const form = document.querySelector<HTMLFormElement>("form:has([name='localPath'])");
|
const form = document.querySelector<HTMLFormElement>("form:has([name='localPath'])");
|
||||||
if (form) void chooseDirectory(form);
|
if (form) void chooseDirectory(form);
|
||||||
}}>
|
}}>
|
||||||
<Icon name="folder" size={13} /> Browse
|
<Icon name="folder" size={16} /> Browse
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function SocialActivity({
|
|||||||
</span>
|
</span>
|
||||||
<span style={{ color: "var(--text-3)", fontSize: 12 }}>{accountHandle(grant.socialAccountId)}</span>
|
<span style={{ color: "var(--text-3)", fontSize: 12 }}>{accountHandle(grant.socialAccountId)}</span>
|
||||||
<Pill tone="violet">{humanize(grant.role)}</Pill>
|
<Pill tone="violet">{humanize(grant.role)}</Pill>
|
||||||
<span style={{ marginLeft: "auto", fontSize: 11, color: "var(--text-3)" }}>{scopeSummary(grant.scopeJson)}</span>
|
<span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-3)" }}>{scopeSummary(grant.scopeJson)}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -95,10 +95,10 @@ export function SocialActivity({
|
|||||||
className="row"
|
className="row"
|
||||||
style={{ gap: 10, padding: "10px 16px", borderBottom: index === entries.length - 1 ? "none" : "1px solid var(--border-soft)" }}
|
style={{ gap: 10, padding: "10px 16px", borderBottom: index === entries.length - 1 ? "none" : "1px solid var(--border-soft)" }}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 12.5, fontWeight: 500 }}>{entry.label}</span>
|
<span style={{ fontSize: 12, fontWeight: 500 }}>{entry.label}</span>
|
||||||
<span style={{ fontSize: 12, color: "var(--text-3)" }}>{accountHandle(entry.accountId)}</span>
|
<span style={{ fontSize: 12, color: "var(--text-3)" }}>{accountHandle(entry.accountId)}</span>
|
||||||
<Pill tone={policyTone(entry.policyDecision)}>{humanize(entry.policyDecision)}</Pill>
|
<Pill tone={policyTone(entry.policyDecision)}>{humanize(entry.policyDecision)}</Pill>
|
||||||
<span style={{ marginLeft: "auto", fontSize: 11, color: "var(--text-3)" }}>
|
<span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-3)" }}>
|
||||||
{entry.actorType === "agent" ? "agent" : employeeName(entry.actorId)} · {formatRelative(entry.createdAt)}
|
{entry.actorType === "agent" ? "agent" : employeeName(entry.actorId)} · {formatRelative(entry.createdAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function SocialCalendar({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: 3,
|
gap: 3,
|
||||||
fontSize: 11,
|
fontSize: 12,
|
||||||
color: cell.inMonth ? "var(--text-2)" : "var(--text-4)",
|
color: cell.inMonth ? "var(--text-2)" : "var(--text-4)",
|
||||||
fontWeight: isToday ? 600 : 480,
|
fontWeight: isToday ? 600 : 480,
|
||||||
background: isToday ? "var(--text-1)" : "transparent",
|
background: isToday ? "var(--text-1)" : "transparent",
|
||||||
@@ -82,7 +82,7 @@ export function SocialCalendar({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ marginTop: 16, fontSize: 11, color: "var(--text-3)", gap: 10 }}>
|
<div className="row" style={{ marginTop: 16, fontSize: 12, color: "var(--text-3)", gap: 10 }}>
|
||||||
<Legend label="X" color="var(--accent)" />
|
<Legend label="X" color="var(--accent)" />
|
||||||
<Legend label="LinkedIn" color="var(--green)" />
|
<Legend label="LinkedIn" color="var(--green)" />
|
||||||
<Legend label="YouTube" color="var(--amber)" />
|
<Legend label="YouTube" color="var(--amber)" />
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ export function HiringPipeline({ agents, approvals }: { agents: AgentRecord[]; a
|
|||||||
<div className="row" style={{ gap: 8 }}>
|
<div className="row" style={{ gap: 8 }}>
|
||||||
<Avatar name={entry.agent.name} size="sm" bot />
|
<Avatar name={entry.agent.name} size="sm" bot />
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ fontSize: 12.5, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{entry.agent.name}</div>
|
<div style={{ fontSize: 12, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{entry.agent.name}</div>
|
||||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{entry.agent.role} · {entry.agent.department}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{entry.agent.role} · {entry.agent.department}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{entry.approval && (
|
{entry.approval && (
|
||||||
<div style={{ marginTop: 6, fontSize: 11, color: "var(--text-3)" }}>
|
<div style={{ marginTop: 6, fontSize: 12, color: "var(--text-3)" }}>
|
||||||
{formatRelative(entry.approval.createdAt)}
|
{formatRelative(entry.approval.createdAt)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ export function TaskDetailDialog({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="modal-actions">
|
<div className="modal-actions">
|
||||||
<Button tone="ghost" onClick={deleteTask}><Icon name="x" size={13} /> Delete</Button>
|
<Button tone="ghost" onClick={deleteTask}><Icon name="x" size={16} /> Delete</Button>
|
||||||
{assignee && <Button onClick={() => onRun(task)} disabled={!canRun(provider)}><Icon name="send" size={13} /> Run</Button>}
|
{assignee && <Button onClick={() => onRun(task)} disabled={!canRun(provider)}><Icon name="send" size={16} /> Run</Button>}
|
||||||
{advanceTo && <Button tone="primary" onClick={() => onAdvance(task)}><Icon name="check" size={13} /> {prettyStatus(advanceTo)}</Button>}
|
{advanceTo && <Button tone="primary" onClick={() => onAdvance(task)}><Icon name="check" size={16} /> {prettyStatus(advanceTo)}</Button>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{editing && <EditOverlay task={task} agents={agents} allTasks={allTasks} onSubmit={submitEdit} onCancel={() => setEditing(false)} />}
|
{editing && <EditOverlay task={task} agents={agents} allTasks={allTasks} onSubmit={submitEdit} onCancel={() => setEditing(false)} />}
|
||||||
@@ -140,9 +140,9 @@ function OverviewTab({ task, assignee, project, goal, workspace, provider, runs
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{task.description && (
|
{task.description && (
|
||||||
<p style={{ margin: "0 0 14px", color: "var(--text-2)", fontSize: 13, lineHeight: 1.55, whiteSpace: "pre-wrap" }}>{task.description}</p>
|
<p style={{ margin: "0 0 14px", color: "var(--text-2)", fontSize: 14, lineHeight: 1.55, whiteSpace: "pre-wrap" }}>{task.description}</p>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: "8px 14px", fontSize: 12.5 }}>
|
<div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: "8px 14px", fontSize: 12 }}>
|
||||||
<MetaLabel>Assignee</MetaLabel>
|
<MetaLabel>Assignee</MetaLabel>
|
||||||
<div className="row" style={{ gap: 8 }}>
|
<div className="row" style={{ gap: 8 }}>
|
||||||
{assignee ? (<><Avatar name={assignee.name} size="sm" bot /><span>{assignee.name}</span>{provider && <span style={{ color: "var(--text-3)" }}>({provider.label})</span>}</>) : <span style={{ color: "var(--text-4)" }}>Unassigned</span>}
|
{assignee ? (<><Avatar name={assignee.name} size="sm" bot /><span>{assignee.name}</span>{provider && <span style={{ color: "var(--text-3)" }}>({provider.label})</span>}</>) : <span style={{ color: "var(--text-4)" }}>Unassigned</span>}
|
||||||
@@ -177,13 +177,13 @@ function OverviewTab({ task, assignee, project, goal, workspace, provider, runs
|
|||||||
function SubtasksTab({ subtasks, agents }: { subtasks: TaskRecord[]; agents: AgentRecord[] }) {
|
function SubtasksTab({ subtasks, agents }: { subtasks: TaskRecord[]; agents: AgentRecord[] }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: 60 }}>
|
<div style={{ minHeight: 60 }}>
|
||||||
{subtasks.length === 0 && <div style={{ fontSize: 13, color: "var(--text-3)" }}>No subtasks</div>}
|
{subtasks.length === 0 && <div style={{ fontSize: 14, color: "var(--text-3)" }}>No subtasks</div>}
|
||||||
{subtasks.map((st) => {
|
{subtasks.map((st) => {
|
||||||
const owner = agents.find((a) => a.id === st.assigneeAgentId);
|
const owner = agents.find((a) => a.id === st.assigneeAgentId);
|
||||||
return (
|
return (
|
||||||
<div key={st.id} className="row" style={{ gap: 10, padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
|
<div key={st.id} className="row" style={{ gap: 10, padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
|
||||||
<Icon name={st.status === TASK_STATUS.DONE ? "check" : "work"} size={13} color={st.status === TASK_STATUS.DONE ? "var(--green)" : "var(--text-3)"} />
|
<Icon name={st.status === TASK_STATUS.DONE ? "check" : "work"} size={16} color={st.status === TASK_STATUS.DONE ? "var(--green)" : "var(--text-3)"} />
|
||||||
<span style={{ flex: 1, fontSize: 13, fontWeight: 480, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{st.title}</span>
|
<span style={{ flex: 1, fontSize: 14, fontWeight: 480, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{st.title}</span>
|
||||||
<Pill>{prettyStatus(st.status)}</Pill>
|
<Pill>{prettyStatus(st.status)}</Pill>
|
||||||
{owner && <Avatar name={owner.name} size="sm" bot />}
|
{owner && <Avatar name={owner.name} size="sm" bot />}
|
||||||
</div>
|
</div>
|
||||||
@@ -202,18 +202,18 @@ function ActivityTab({ audit, approvals }: { audit: AuditEventRecord[]; approval
|
|||||||
{approvals.map((a) => (
|
{approvals.map((a) => (
|
||||||
<div key={a.id} className="row" style={{ gap: 8, padding: "6px 0" }}>
|
<div key={a.id} className="row" style={{ gap: 8, padding: "6px 0" }}>
|
||||||
<Pill tone={a.state === "approved" ? "green" : a.state === "pending" ? "amber" : "red"}>{a.state}</Pill>
|
<Pill tone={a.state === "approved" ? "green" : a.state === "pending" ? "amber" : "red"}>{a.state}</Pill>
|
||||||
<span style={{ fontSize: 12.5 }}>{a.title}</span>
|
<span style={{ fontSize: 12 }}>{a.title}</span>
|
||||||
<span className="mono" style={{ marginLeft: "auto", fontSize: 11, color: "var(--text-3)" }}>{formatRelative(a.createdAt)}</span>
|
<span className="mono" style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-3)" }}>{formatRelative(a.createdAt)}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="section-label">Events</div>
|
<div className="section-label">Events</div>
|
||||||
{audit.length === 0 && <div style={{ fontSize: 13, color: "var(--text-3)" }}>No events</div>}
|
{audit.length === 0 && <div style={{ fontSize: 14, color: "var(--text-3)" }}>No events</div>}
|
||||||
{audit.map((e) => (
|
{audit.map((e) => (
|
||||||
<div key={e.id} className="row" style={{ gap: 8, padding: "5px 0" }}>
|
<div key={e.id} className="row" style={{ gap: 8, padding: "5px 0" }}>
|
||||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)", flexShrink: 0 }}>{formatRelative(e.createdAt)}</span>
|
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)", flexShrink: 0 }}>{formatRelative(e.createdAt)}</span>
|
||||||
<span style={{ fontSize: 12.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{auditEventLabel(e.entityType, e.action)}</span>
|
<span style={{ fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{auditEventLabel(e.entityType, e.action)}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,10 +116,10 @@ export function SearchDialog({
|
|||||||
>
|
>
|
||||||
<Pill>{LABELS[result.entityType]}</Pill>
|
<Pill>{LABELS[result.entityType]}</Pill>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{result.title}</div>
|
<div style={{ fontSize: 14, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{result.title}</div>
|
||||||
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{result.preview}</div>
|
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{result.preview}</div>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: 11, color: "var(--text-3)" }}>{result.facets.type}</span>
|
<span style={{ fontSize: 12, color: "var(--text-3)" }}>{result.facets.type}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user