fix: show one compact theme-aware discovery loader

Remove the duplicate progress bar and pulsing status dot. Reduce wave geometry to 190 by 72 and inherit existing light/dark accent colors. Record the provider-agnostic native CLI boundary in AGENTS.md.

Checks: frontend 375 tests; web and desktop builds; desktop pack; Electron first-run and returning-user checks with animated loader, light/dark colors and a 560px window.
This commit is contained in:
Ryder Sun committed 2026-10-30 11:01:49 +08:00
1 parent 05ec2bfbf0
commit d2f6171ddd
5 files changed
+21 -83

No files matched your search

+1
View File
@@ -11,6 +11,7 @@
- Meldwork is a local-first desktop application. Do not introduce a required server, container, or cluster dependency unless the task explicitly calls for it.
- Keep Agent execution in the Electron main process. Renderer access must go through narrow, validated preload APIs.
- Respect native Agent configuration and keep CLI integration provider-agnostic. Do not add gateway-specific adapters, provider overrides, or bundled patches for external tools such as Magpie.
- Never expose executable paths, tokens, credentials, or unrestricted shell execution to renderer code.
- Store secrets only through operating-system-backed secure storage. Do not add plaintext fallbacks.
- Keep workspace write access opt-in. Conversations and groups stay local; do not add remote participants, cloud channels, or remote conversation storage.
-3
View File
@@ -41,9 +41,6 @@
<p class="agent-discovery-kicker">{{ t('agentDiscovery.kicker') }}</p>
<h1>{{ t('agentDiscovery.title') }}</h1>
<p>{{ t('agentDiscovery.body') }}</p>
<div class="agent-discovery-progress" aria-hidden="true">
<span />
</div>
<div class="agent-discovery-status">
<span class="agent-discovery-status-dot" aria-hidden="true" />
<span>{{ t('agentDiscovery.status') }}</span>
@@ -59,6 +59,8 @@ describe('Meldwork workbench', () => {
expect(wrapper.get('.agent-discovery-overlay').text()).toContain('Agent CLI')
expect(wrapper.find('.pixel-blast-container').exists()).toBe(true)
expect(wrapper.get('.agent-discovery-overlay').find('.wave-physics-loader').exists()).toBe(true)
expect(wrapper.get('.agent-discovery-overlay').findAll('.wave-physics-loader')).toHaveLength(1)
expect(wrapper.find('.agent-discovery-progress').exists()).toBe(false)
expect(wrapper.get('.workspace-pane').attributes()).toHaveProperty('inert')
expect(wrapper.get('.sidebar').attributes()).toHaveProperty('inert')
+18 -37
View File
@@ -1,5 +1,5 @@
<template>
<div class="wave-physics-loader" aria-hidden="true">
<div class="wave-physics-loader" :data-theme="theme" aria-hidden="true">
<div class="wave-physics-track">
<span
v-for="bar in bars"
@@ -21,34 +21,23 @@
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
const props = defineProps({
defineProps({
theme: { type: String, default: 'light' },
})
const numBars = 15
const barWidth = 12
const barGap = 8
const barWidth = 8
const barGap = 5
const barTotalWidth = barWidth + barGap
const B = 4
const maxBounce = 60
const baseBarH = 16
const wavePeakH = 48
const maxBounce = 24
const baseBarH = 8
const wavePeakH = 24
const duration = 4000
const progress = ref(0.5)
let animationFrame = 0
function interpolate(start, end, value) {
return Math.round(start + (end - start) * value)
}
function barColor(theme, waveValue) {
if (theme === 'dark') {
return `rgb(${interpolate(39, 228, waveValue)} ${interpolate(39, 228, waveValue)} ${interpolate(42, 231, waveValue)})`
}
return `rgb(${interpolate(228, 39, waveValue)} ${interpolate(228, 39, waveValue)} ${interpolate(231, 42, waveValue)})`
}
function frameAt(t) {
const xFrac = t < 0.5 ? t / 0.5 : (1 - t) / 0.5
const ballIndex = xFrac * (numBars - 1)
@@ -57,7 +46,7 @@ function frameAt(t) {
const bounceH = 4 * bounceF * (1 - bounceF)
const heightFactor = Math.max(0, 1 - bounceH * 2)
const ballIndent = heightFactor * 20
const ballIndent = heightFactor * 10
const ballY = (baseBarH + wavePeakH - ballIndent) + bounceH * maxBounce
const bars = Array.from({ length: numBars }, (_unused, index) => {
@@ -68,13 +57,13 @@ function frameAt(t) {
let indent = 0
if (dist < 1.5) {
const indentDist = Math.cos((dist / 1.5) * (Math.PI / 2))
indent = indentDist * heightFactor * 20
indent = indentDist * heightFactor * 10
}
const barHeight = Math.max(4, baseBarH + waveValue * wavePeakH - indent)
const barHeight = Math.max(3, baseBarH + waveValue * wavePeakH - indent)
return {
color: barColor(props.theme, waveValue),
color: `color-mix(in srgb, var(--accent) ${Math.round(waveValue * 100)}%, var(--accent-soft))`,
height: `${barHeight}px`,
index,
}
@@ -83,7 +72,7 @@ function frameAt(t) {
return {
bars,
ball: {
color: props.theme === 'dark' ? 'rgb(244, 244, 245)' : 'rgb(24, 24, 27)',
color: 'var(--accent)',
scaleX: 1 + heightFactor * 0.25,
scaleY: 1 - heightFactor * 0.3,
x: `${ballIndex * barTotalWidth}px`,
@@ -126,16 +115,16 @@ onBeforeUnmount(() => {
.wave-physics-track {
position: relative;
width: 292px;
height: 192px;
width: 190px;
height: 72px;
display: flex;
align-items: flex-end;
justify-content: flex-start;
gap: 8px;
gap: 5px;
}
.wave-physics-bar {
width: 12px;
width: 8px;
border-radius: 999px;
transform-origin: bottom center;
will-change: height, background-color;
@@ -145,22 +134,14 @@ onBeforeUnmount(() => {
position: absolute;
left: 0;
bottom: 0;
width: 12px;
height: 12px;
width: 8px;
height: 8px;
border-radius: 999px;
transform-origin: bottom center;
box-shadow: 0 1px 3px color-mix(in srgb, currentColor 24%, transparent);
will-change: transform, background-color;
}
@media (max-width: 640px) {
.wave-physics-track {
width: 260px;
transform: scale(0.9);
transform-origin: center;
}
}
@media (prefers-reduced-motion: reduce) {
.wave-physics-bar,
.wave-physics-ball {
-43
View File
@@ -67,25 +67,6 @@
line-height: 1.55;
}
.agent-discovery-progress {
width: min(260px, 100%);
height: 3px;
margin-top: 26px;
overflow: hidden;
border-radius: 999px;
background: color-mix(in srgb, var(--border) 68%, transparent);
}
.agent-discovery-progress span {
display: block;
width: 46%;
height: 100%;
border-radius: inherit;
background: var(--onboarding-frame-gradient);
box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 34%, transparent);
animation: agent-discovery-progress 1.35s ease-in-out infinite;
}
.agent-discovery-status {
display: inline-flex;
align-items: center;
@@ -101,7 +82,6 @@
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 12%, transparent);
animation: agent-discovery-dot 1.25s ease-in-out infinite;
}
.agent-discovery-enter-active,
@@ -125,29 +105,6 @@
transform: translateY(8px) scale(0.985);
}
@keyframes agent-discovery-progress {
0% { transform: translateX(-125%); opacity: 0.4; }
55% { opacity: 1; }
100% { transform: translateX(300%); opacity: 0.4; }
}
@keyframes agent-discovery-dot {
0%, 100% { opacity: 0.42; transform: scale(0.86); }
50% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.agent-discovery-progress span,
.agent-discovery-status-dot {
animation: none;
}
.agent-discovery-progress span {
width: 58%;
transform: translateX(36%);
}
}
@media (max-width: 480px) {
.agent-discovery-overlay { padding: 24px; }
.agent-discovery-copy h1 { font-size: 26px; }