mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 13:06:20 +02:00
* feat(agent-status): add workspace live-tasks endpoint and TaskFailureReason type Lays the API + type contract for the front-end agent presence cache: - New `GET /api/active-tasks` returns active (queued/dispatched/running) tasks plus failed tasks within the last 2 minutes for the current workspace. The 2-minute window powers a UI-side auto-clearing "Failed" agent state without back-end pollers. - `agent_task_queue` has no workspace_id column, so the query JOINs agent; `SELECT atq.*` keeps `failure_reason` (migration 055) on the wire. - Adds `TaskFailureReason` to `AgentTask` so the UI can map the 5 backend classifiers (agent_error / timeout / runtime_offline / runtime_recovery / manual) to copy without parsing free-text errors. - New `api.getActiveTasksForWorkspace()` client method; workspace is resolved server-side from the X-Workspace-Slug header (no path param, matching /api/agents and /api/runtimes conventions). Includes the joint engineering plan and designer brief that scope the broader Agent / Runtime status redesign — Phase 0 is this contract plus the front-end derivation layer landing in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent-status): derive presence/health states with WS sync and desktop IPC bridge Adds the front-end derivation layer that turns raw server data into the user-facing 5-state agent / 4-state runtime enums. UI files are deliberately untouched in this commit — derivation lives behind hooks (useAgentPresence, useRuntimeHealth) that any component can call with zero additional network traffic. Architecture: - Derivation is pure functions in packages/core/{agents,runtimes}; the back-end stays free of UI translation. Agents algorithm: runtime offline > recent failed (2-min window) > running > queued > available. Runtimes algorithm: status + last_seen_at -> online / recently_lost / offline / about_to_gc. - A single workspace-wide active-tasks query backs all per-agent presence reads, eliminating N+1 across hover cards, list rows, and pickers. 30-second tick re-renders the hooks so the failed window expires even when no underlying data changes. - WS task lifecycle events (dispatch / completed / failed / cancelled) invalidate active-tasks via the prefix dispatcher. completed/failed were removed from specificEvents so they go through both the prefix invalidate and the existing chat ws.on() handlers. Reconnect refetch picks up active-tasks too. - Desktop bridges window.daemonAPI.onStatusChange directly into the runtimes cache via setQueryData, giving the local daemon sub-second feedback (vs. 75s server sweep). Bridge is wsId-bound so workspace switches automatically rebind the subscription; daemon_id matching covers the same-daemon-multiple-providers case. 24 derivation unit tests cover all branches plus null/empty/boundary inputs (FAILED_WINDOW_MS edges, null last_seen_at, missing completed_at). Full core suite: 112 tests passing. Typecheck green across all 8 workspace packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent-status): redesign agent runtime status as two orthogonal dimensions Splits the conflated 5-state agent presence into two independent axes: - AgentAvailability (3-state): online / unstable / offline — drives the dot indicator everywhere a dot appears. Pure runtime reachability; never sticky-red because of a past task outcome. - LastTaskState (5-state): running / completed / failed / cancelled / idle — surfaced as text + icon on focused surfaces (hover card, agent detail page, agents list, runtime detail). Never colours the dot. Major changes: * Domain layer: AgentPresence union → AgentAvailability + LastTaskState. derive-presence split into deriveAgentAvailability + deriveLastTaskState + deriveAgentPresenceDetail orchestrator. Tests reorganised into three groups (availability invariants, last-task invariants, composition). * Visual config: presenceConfig (5 entries) → availabilityConfig (3) + taskStateConfig (5). availabilityOrder + lastTaskOrder for filter chips. * Workspace-level presence prefetch: new useWorkspacePresencePrefetch hook + WorkspacePresencePrefetch mount component, wired into DashboardLayout (web) and WorkspaceRouteLayout (desktop). Hover cards render synchronously with no skeleton flash on first hover. * ActorAvatar hover: flipped default — disableHoverCard removed, enableHoverCard added (default false). Opt-in at ~14 decision-moment surfaces; pickers / decoration sub-chips stay plain. Status dot decoupled (showStatusDot prop) so picker rows can show presence without nesting popovers. * Hover cards: AgentProfileCard simplified — availability dot only, Detail link top-right (logs live on the detail page). New MemberProfileCard mirrors the structure: name + role + email + top-2 owned agents (sorted by 30d run count) with click-through to agent detail. * Agents list: split Status into two columns — availability (3-color dot + label) and Last run (task icon + label, optional running counts). Two independent filter chip groups (Status + Last run); combination acts as intersection ("online + failed" finds broken- but-alive agents). * Other UI surfaces (issue list/board/detail, comments, autopilots, projects, runtimes, mention autocomplete, subscribers picker) updated to the new dot semantics; status dot now strictly 3-color. Server changes accompany the client redesign — workspace-wide agent-task-snapshot endpoint, runtime usage queries, etc. — to feed the derive layer with the data it needs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent-detail): drop last-task chip from detail header + inspector The Recent work section on the agent detail page already shows the same data (with task titles, timestamps, error context) — surfacing "Completed" / "Failed" / etc. up in the header was redundant chrome. Detail surfaces now show only the 3-state availability dot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tables): handle narrow viewports across agents / skills / runtimes Three table layouts were squeezing content into adjacent cells at intermediate widths. Each fix is small and targeted: * runtime-list: the Runtime cell's base name had `shrink-0`, so it refused to truncate when its grid column was narrowed under width pressure — the name visually overflowed into the Health column ("ClaudeOnline" etc). Removed shrink-0, added truncate. The Health column was also a fixed 9.5rem reservation for the worst-case "Recently lost · 2m 14s ago" copy; switched to minmax(0,1fr) so it competes fairly with Runtime. * skills-page: had a single grid template with no responsive breakpoints — all 6 columns were rendered at any width and got visually jammed below md. Added a <md template that drops Source + Updated; the row markup hides those cells via `hidden md:block` / `md:contents`. * agent-list-item: the new Last run column was reserved at minmax(8rem, max-content); on narrow md viewports the 8rem floor pushed the row past available width. Changed to minmax(0,max-content) so the cell shrinks under pressure (its content already truncates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent-card): hover-only Detail + add Runtime row + breathing room Three small polish tweaks to the agent hover card: - Detail link gets `mr-1` + fades in only on card hover (group-hover). It was visually flush against the popover edge and competing for attention; now it stays out of the way during a quick glance and surfaces only when the user is dwelling on the card. - Runtime row is back, in the meta block (cloud/local icon + runtime name). The earlier removal was over-aggressive — knowing where an agent runs is part of "who is this agent". The wifi badge stays dropped because the availability dot in the header already conveys reachability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(runtime): wifi-style health icon (4-state) for runtime list + agent card Replaces the 6px coloured dot with a wifi-shape icon that carries both state (Wifi vs WifiOff) and severity (success/warning/muted/destructive). Mapping: - online → Wifi (success) - recently_lost → WifiHigh (warning) — transient hiccup, fewer bars - offline → WifiOff (muted) — long unreachable - about_to_gc → WifiOff (destructive) — sweeper coming soon Used in two places: - Runtime list: replaces HealthDot in the dedicated leading-icon column. Bumped the column from 0.5rem (dot-sized) to 0.875rem (icon-sized). - Agent profile card RuntimeRow: derives runtime health from runtime + clock (matching the 4-state semantics) and renders HealthIcon next to the runtime name. Cloud runtimes always read as online. The duplicate signal with the header availability dot is intentional — it confirms WHICH runtime is the one currently in the dot's state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
533 lines
13 KiB
CSS
533 lines
13 KiB
CSS
/*
|
|
* ContentEditor typography — ProseMirror styles using shadcn design tokens.
|
|
*
|
|
* Design tier: "Compact" (same tier as Linear, Slack). Optimized for short-form
|
|
* content (issue descriptions, comments) that users scan, not long-form reading.
|
|
*
|
|
* Typography values benchmarked against (April 2026):
|
|
* - github-markdown-css (GitHub's markdown renderer)
|
|
* - @tailwindcss/typography prose-sm preset
|
|
* - Linear's editor (Tiptap-based, 14px body)
|
|
*
|
|
* Key decisions:
|
|
* Body: 14px (text-sm), line-height 1.625 (between GitHub 1.5 and Tailwind 1.714)
|
|
* Headings: h1=22px (1.57x), h2=18px (1.29x), h3=15px (1.07x) — compact but
|
|
* with clear hierarchy. Previous h3 was 14px (same as body = no differentiation).
|
|
* Paragraph spacing: 10px (was 8px; GitHub uses 10px, Tailwind prose-sm uses 16px)
|
|
* List indent: 20px for ul (was 16px; standard is 22-32px)
|
|
* Code block margin: 12px (was 8px; gives breathing room between code and prose)
|
|
* Blockquote border: 3px (was 2px; GitHub/Tailwind both use 4px)
|
|
* Links: var(--brand) blue with 40% opacity underline (was var(--primary) near-black)
|
|
*
|
|
* Inline elements (mention cards, inline code) that exceed line-height:
|
|
* The browser auto-expands the line box for lines containing taller inline
|
|
* elements. Controlled via vertical-align on [data-node-view-wrapper] and
|
|
* box-decoration-break: clone on inline code.
|
|
*/
|
|
|
|
.rich-text-editor.ProseMirror {
|
|
color: var(--foreground);
|
|
caret-color: var(--foreground);
|
|
min-height: 100%;
|
|
}
|
|
|
|
.rich-text-editor.ProseMirror:focus {
|
|
outline: none;
|
|
}
|
|
|
|
/* Placeholder */
|
|
.rich-text-editor .is-editor-empty:first-child::before {
|
|
content: attr(data-placeholder);
|
|
float: left;
|
|
color: var(--muted-foreground);
|
|
pointer-events: none;
|
|
height: 0;
|
|
}
|
|
|
|
/* Headings — compact but with clear visual hierarchy */
|
|
.rich-text-editor h1 {
|
|
font-size: 1.375rem;
|
|
font-weight: 700;
|
|
margin-top: 1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
line-height: 1.3;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.rich-text-editor h2 {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
margin-top: 1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.rich-text-editor h3 {
|
|
font-size: 0.9375rem;
|
|
font-weight: 600;
|
|
margin-top: 1rem;
|
|
margin-bottom: 0.5rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Paragraphs */
|
|
.rich-text-editor p {
|
|
margin-top: 0.625rem;
|
|
margin-bottom: 0.625rem;
|
|
line-height: 1.625;
|
|
}
|
|
|
|
/* First child should not have top margin */
|
|
.rich-text-editor > *:first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
/* Last child should not have bottom margin */
|
|
.rich-text-editor > *:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* Lists */
|
|
.rich-text-editor ul {
|
|
list-style-type: disc;
|
|
padding-inline-start: 1.25rem;
|
|
padding-inline-end: 0.5rem;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.rich-text-editor ol {
|
|
list-style-type: decimal;
|
|
padding-inline-start: 1.5rem;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.rich-text-editor li {
|
|
margin: 0.25rem 0;
|
|
line-height: 1.625;
|
|
}
|
|
|
|
.rich-text-editor li + li {
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.rich-text-editor li::marker {
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
/* Remove paragraph margins inside list items (Tiptap wraps li content in <p>) */
|
|
.rich-text-editor li > p {
|
|
margin: 0;
|
|
}
|
|
|
|
.rich-text-editor li > p + p {
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.rich-text-editor .math-node {
|
|
display: inline-flex;
|
|
max-width: 100%;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.rich-text-editor .math-node.inline {
|
|
align-items: center;
|
|
}
|
|
|
|
.rich-text-editor .math-node.block {
|
|
display: block;
|
|
margin: 0.75rem 0;
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
}
|
|
|
|
.rich-text-editor .math-node.block .katex-display {
|
|
margin: 0;
|
|
}
|
|
|
|
.rich-text-editor .math-node .katex {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Nested lists — bullet style progression and tighter spacing */
|
|
.rich-text-editor ul ul {
|
|
list-style-type: circle;
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.rich-text-editor ul ul ul {
|
|
list-style-type: square;
|
|
}
|
|
|
|
.rich-text-editor ol ol {
|
|
list-style-type: lower-alpha;
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.rich-text-editor ol ol ol {
|
|
list-style-type: lower-roman;
|
|
}
|
|
|
|
/* Inline code */
|
|
.rich-text-editor code {
|
|
font-family: var(--font-mono, ui-monospace, monospace);
|
|
font-size: 0.875rem;
|
|
background: color-mix(in srgb, var(--foreground) 3%, transparent);
|
|
border: 1px solid color-mix(in srgb, var(--foreground) 5%, transparent);
|
|
color: color-mix(in srgb, var(--foreground) 75%, transparent);
|
|
padding: 0.125rem 0.375rem;
|
|
border-radius: var(--radius-sm);
|
|
box-decoration-break: clone;
|
|
-webkit-box-decoration-break: clone;
|
|
line-height: 2;
|
|
}
|
|
|
|
/* Code blocks */
|
|
.rich-text-editor pre {
|
|
font-family: var(--font-mono, ui-monospace, monospace);
|
|
background: var(--muted);
|
|
border-radius: var(--radius);
|
|
padding: 0.75rem 1rem;
|
|
margin: 0.75rem 0;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.rich-text-editor pre code {
|
|
background: none;
|
|
border: none;
|
|
color: var(--foreground);
|
|
padding: 0;
|
|
font-size: 0.8125rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Syntax highlighting — lowlight (hljs) */
|
|
.rich-text-editor .hljs-keyword,
|
|
.rich-text-editor .hljs-selector-tag,
|
|
.rich-text-editor .hljs-built_in { color: oklch(0.55 0.16 255); }
|
|
|
|
.rich-text-editor .hljs-string,
|
|
.rich-text-editor .hljs-addition { color: oklch(0.55 0.14 155); }
|
|
|
|
.rich-text-editor .hljs-comment,
|
|
.rich-text-editor .hljs-quote { color: var(--muted-foreground); font-style: italic; }
|
|
|
|
.rich-text-editor .hljs-number,
|
|
.rich-text-editor .hljs-literal { color: oklch(0.58 0.16 30); }
|
|
|
|
.rich-text-editor .hljs-title,
|
|
.rich-text-editor .hljs-section,
|
|
.rich-text-editor .hljs-title\.function_ { color: oklch(0.55 0.14 280); }
|
|
|
|
.rich-text-editor .hljs-attr,
|
|
.rich-text-editor .hljs-attribute { color: oklch(0.58 0.12 60); }
|
|
|
|
.rich-text-editor .hljs-variable,
|
|
.rich-text-editor .hljs-template-variable { color: oklch(0.58 0.14 20); }
|
|
|
|
.rich-text-editor .hljs-type,
|
|
.rich-text-editor .hljs-title\.class_ { color: oklch(0.55 0.14 200); }
|
|
|
|
.rich-text-editor .hljs-deletion { color: oklch(0.55 0.2 25); }
|
|
|
|
.rich-text-editor .hljs-meta { color: var(--muted-foreground); }
|
|
|
|
/* Dark mode overrides */
|
|
.dark .rich-text-editor .hljs-keyword,
|
|
.dark .rich-text-editor .hljs-selector-tag,
|
|
.dark .rich-text-editor .hljs-built_in { color: oklch(0.7 0.14 255); }
|
|
|
|
.dark .rich-text-editor .hljs-string,
|
|
.dark .rich-text-editor .hljs-addition { color: oklch(0.7 0.14 155); }
|
|
|
|
.dark .rich-text-editor .hljs-number,
|
|
.dark .rich-text-editor .hljs-literal { color: oklch(0.72 0.14 30); }
|
|
|
|
.dark .rich-text-editor .hljs-title,
|
|
.dark .rich-text-editor .hljs-section,
|
|
.dark .rich-text-editor .hljs-title\.function_ { color: oklch(0.72 0.12 280); }
|
|
|
|
.dark .rich-text-editor .hljs-attr,
|
|
.dark .rich-text-editor .hljs-attribute { color: oklch(0.72 0.1 60); }
|
|
|
|
.dark .rich-text-editor .hljs-variable,
|
|
.dark .rich-text-editor .hljs-template-variable { color: oklch(0.72 0.12 20); }
|
|
|
|
.dark .rich-text-editor .hljs-type,
|
|
.dark .rich-text-editor .hljs-title\.class_ { color: oklch(0.72 0.12 200); }
|
|
|
|
.dark .rich-text-editor .hljs-deletion { color: oklch(0.7 0.18 25); }
|
|
|
|
/* Tables */
|
|
.rich-text-editor .tableWrapper {
|
|
overflow-x: auto;
|
|
margin: 1rem 0;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.rich-text-editor table {
|
|
min-width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.rich-text-editor colgroup {
|
|
display: none;
|
|
}
|
|
|
|
.rich-text-editor thead {
|
|
background: color-mix(in srgb, var(--muted) 50%, transparent);
|
|
}
|
|
|
|
.rich-text-editor tbody tr {
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.rich-text-editor tr:hover td {
|
|
background: color-mix(in srgb, var(--muted) 30%, transparent);
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.rich-text-editor th,
|
|
.rich-text-editor td {
|
|
text-align: left;
|
|
padding: 0.625rem 1rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.rich-text-editor th {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Remove paragraph margin inside table cells */
|
|
.rich-text-editor th p,
|
|
.rich-text-editor td p {
|
|
margin: 0;
|
|
}
|
|
|
|
/* Blockquotes */
|
|
.rich-text-editor blockquote {
|
|
border-left: 3px solid color-mix(in srgb, var(--muted-foreground) 30%, transparent);
|
|
padding-left: 0.75rem;
|
|
margin: 0.625rem 0;
|
|
color: var(--muted-foreground);
|
|
font-style: italic;
|
|
}
|
|
|
|
.rich-text-editor blockquote p {
|
|
margin-top: 0.25rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.rich-text-editor blockquote > *:first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.rich-text-editor blockquote > *:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.rich-text-editor blockquote blockquote {
|
|
margin-top: 0.25rem;
|
|
margin-bottom: 0.25rem;
|
|
border-left-color: color-mix(in srgb, var(--muted-foreground) 15%, transparent);
|
|
}
|
|
|
|
/* Horizontal rules */
|
|
.rich-text-editor hr {
|
|
border: none;
|
|
border-top: 1px solid var(--border);
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
/* Links */
|
|
.rich-text-editor a {
|
|
color: var(--brand);
|
|
text-decoration: underline;
|
|
text-decoration-color: color-mix(in srgb, var(--brand) 40%, transparent);
|
|
text-underline-offset: 2px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.rich-text-editor a:hover {
|
|
text-decoration-color: var(--brand);
|
|
}
|
|
|
|
/* Issue mention cards — inline cards that sit within text flow */
|
|
.rich-text-editor a.issue-mention {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.rich-text-editor a.issue-mention:hover {
|
|
text-decoration: none;
|
|
}
|
|
|
|
/* Mentions */
|
|
.rich-text-editor .mention {
|
|
color: var(--primary);
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
margin: 0 0.125rem;
|
|
}
|
|
|
|
/* Strong / emphasis */
|
|
.rich-text-editor strong {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.rich-text-editor em {
|
|
font-style: italic;
|
|
}
|
|
|
|
.rich-text-editor s,
|
|
.rich-text-editor del {
|
|
text-decoration: line-through;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
/* Readonly mode overrides */
|
|
.rich-text-editor.readonly.ProseMirror {
|
|
caret-color: transparent;
|
|
cursor: default;
|
|
}
|
|
|
|
/* Mention NodeView inline layout fix */
|
|
.rich-text-editor [data-node-view-wrapper] {
|
|
display: inline;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
/* Block-level NodeViews (fileCard) need to override the inline default above */
|
|
.rich-text-editor .file-card-node {
|
|
display: block !important;
|
|
}
|
|
|
|
/* Images — generic fallback (non-NodeView contexts) */
|
|
.rich-text-editor img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: var(--radius);
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
/* Image NodeView — centered block with max-width cap */
|
|
.rich-text-editor .image-node {
|
|
display: block !important;
|
|
text-align: center;
|
|
}
|
|
|
|
.rich-text-editor .image-figure {
|
|
position: relative;
|
|
display: inline-block;
|
|
max-width: min(100%, 640px);
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.rich-text-editor .image-figure.image-selected .image-content {
|
|
outline: 2px solid var(--brand);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.rich-text-editor .image-content {
|
|
display: block;
|
|
width: 100%;
|
|
height: auto;
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.rich-text-editor .image-uploading {
|
|
opacity: 0.5;
|
|
animation: rte-upload-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
}
|
|
|
|
@keyframes rte-upload-pulse {
|
|
0%, 100% { opacity: 0.5; }
|
|
50% { opacity: 0.3; }
|
|
}
|
|
|
|
/* Readonly — zoom cursor on clickable images */
|
|
.rich-text-editor.readonly .image-figure {
|
|
cursor: zoom-in;
|
|
}
|
|
|
|
/* Image toolbar — dark pill, top-right corner, appears on hover */
|
|
.rich-text-editor .image-toolbar {
|
|
position: absolute;
|
|
top: 0.5rem;
|
|
right: 0.5rem;
|
|
display: flex;
|
|
gap: 1px;
|
|
padding: 0.25rem;
|
|
background: color-mix(in srgb, black 75%, transparent);
|
|
backdrop-filter: blur(8px);
|
|
border-radius: var(--radius);
|
|
opacity: 0;
|
|
transition: opacity 0.15s;
|
|
z-index: 1;
|
|
}
|
|
|
|
.image-figure:hover .image-toolbar {
|
|
opacity: 1;
|
|
}
|
|
|
|
.rich-text-editor .image-toolbar button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 1.75rem;
|
|
height: 1.75rem;
|
|
border-radius: calc(var(--radius) - 2px);
|
|
color: white;
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.rich-text-editor .image-toolbar button:hover {
|
|
background: color-mix(in srgb, white 15%, transparent);
|
|
}
|
|
|
|
/* Bubble menu — floating toolbar pill */
|
|
.bubble-menu {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1px;
|
|
padding: 0.25rem;
|
|
background: var(--popover);
|
|
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
|
border-radius: var(--radius);
|
|
box-shadow:
|
|
0 4px 12px color-mix(in srgb, black 12%, transparent),
|
|
0 0 0 1px color-mix(in srgb, black 4%, transparent);
|
|
}
|
|
|
|
/* Link edit mode — inline URL input */
|
|
.bubble-menu-link-edit {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.25rem;
|
|
background: var(--popover);
|
|
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
|
border-radius: var(--radius);
|
|
box-shadow:
|
|
0 4px 12px color-mix(in srgb, black 12%, transparent),
|
|
0 0 0 1px color-mix(in srgb, black 4%, transparent);
|
|
min-width: 300px;
|
|
}
|
|
|
|
/* Link hover card — shows URL + actions on link hover */
|
|
.link-hover-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
|
|
background: var(--popover);
|
|
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
|
border-radius: var(--radius);
|
|
box-shadow:
|
|
0 4px 12px color-mix(in srgb, black 12%, transparent),
|
|
0 0 0 1px color-mix(in srgb, black 4%, transparent);
|
|
max-width: min(360px, calc(100vw - 2rem));
|
|
white-space: nowrap;
|
|
}
|
|
|