mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
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:
1 parent
05ec2bfbf0
commit
d2f6171ddd
5 files changed
+21
-83
No files matched your search
@@ -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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Keep workspace write access opt-in. Conversations and groups stay local; do not add remote participants, cloud channels, or remote conversation storage.
|
||||||
|
|||||||
@@ -41,9 +41,6 @@
|
|||||||
<p class="agent-discovery-kicker">{{ t('agentDiscovery.kicker') }}</p>
|
<p class="agent-discovery-kicker">{{ t('agentDiscovery.kicker') }}</p>
|
||||||
<h1>{{ t('agentDiscovery.title') }}</h1>
|
<h1>{{ t('agentDiscovery.title') }}</h1>
|
||||||
<p>{{ t('agentDiscovery.body') }}</p>
|
<p>{{ t('agentDiscovery.body') }}</p>
|
||||||
<div class="agent-discovery-progress" aria-hidden="true">
|
|
||||||
<span />
|
|
||||||
</div>
|
|
||||||
<div class="agent-discovery-status">
|
<div class="agent-discovery-status">
|
||||||
<span class="agent-discovery-status-dot" aria-hidden="true" />
|
<span class="agent-discovery-status-dot" aria-hidden="true" />
|
||||||
<span>{{ t('agentDiscovery.status') }}</span>
|
<span>{{ t('agentDiscovery.status') }}</span>
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ describe('Meldwork workbench', () => {
|
|||||||
expect(wrapper.get('.agent-discovery-overlay').text()).toContain('Agent CLI')
|
expect(wrapper.get('.agent-discovery-overlay').text()).toContain('Agent CLI')
|
||||||
expect(wrapper.find('.pixel-blast-container').exists()).toBe(true)
|
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').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('.workspace-pane').attributes()).toHaveProperty('inert')
|
||||||
expect(wrapper.get('.sidebar').attributes()).toHaveProperty('inert')
|
expect(wrapper.get('.sidebar').attributes()).toHaveProperty('inert')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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">
|
<div class="wave-physics-track">
|
||||||
<span
|
<span
|
||||||
v-for="bar in bars"
|
v-for="bar in bars"
|
||||||
@@ -21,34 +21,23 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
theme: { type: String, default: 'light' },
|
theme: { type: String, default: 'light' },
|
||||||
})
|
})
|
||||||
|
|
||||||
const numBars = 15
|
const numBars = 15
|
||||||
const barWidth = 12
|
const barWidth = 8
|
||||||
const barGap = 8
|
const barGap = 5
|
||||||
const barTotalWidth = barWidth + barGap
|
const barTotalWidth = barWidth + barGap
|
||||||
const B = 4
|
const B = 4
|
||||||
const maxBounce = 60
|
const maxBounce = 24
|
||||||
const baseBarH = 16
|
const baseBarH = 8
|
||||||
const wavePeakH = 48
|
const wavePeakH = 24
|
||||||
const duration = 4000
|
const duration = 4000
|
||||||
|
|
||||||
const progress = ref(0.5)
|
const progress = ref(0.5)
|
||||||
let animationFrame = 0
|
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) {
|
function frameAt(t) {
|
||||||
const xFrac = t < 0.5 ? t / 0.5 : (1 - t) / 0.5
|
const xFrac = t < 0.5 ? t / 0.5 : (1 - t) / 0.5
|
||||||
const ballIndex = xFrac * (numBars - 1)
|
const ballIndex = xFrac * (numBars - 1)
|
||||||
@@ -57,7 +46,7 @@ function frameAt(t) {
|
|||||||
|
|
||||||
const bounceH = 4 * bounceF * (1 - bounceF)
|
const bounceH = 4 * bounceF * (1 - bounceF)
|
||||||
const heightFactor = Math.max(0, 1 - bounceH * 2)
|
const heightFactor = Math.max(0, 1 - bounceH * 2)
|
||||||
const ballIndent = heightFactor * 20
|
const ballIndent = heightFactor * 10
|
||||||
const ballY = (baseBarH + wavePeakH - ballIndent) + bounceH * maxBounce
|
const ballY = (baseBarH + wavePeakH - ballIndent) + bounceH * maxBounce
|
||||||
|
|
||||||
const bars = Array.from({ length: numBars }, (_unused, index) => {
|
const bars = Array.from({ length: numBars }, (_unused, index) => {
|
||||||
@@ -68,13 +57,13 @@ function frameAt(t) {
|
|||||||
let indent = 0
|
let indent = 0
|
||||||
if (dist < 1.5) {
|
if (dist < 1.5) {
|
||||||
const indentDist = Math.cos((dist / 1.5) * (Math.PI / 2))
|
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 {
|
return {
|
||||||
color: barColor(props.theme, waveValue),
|
color: `color-mix(in srgb, var(--accent) ${Math.round(waveValue * 100)}%, var(--accent-soft))`,
|
||||||
height: `${barHeight}px`,
|
height: `${barHeight}px`,
|
||||||
index,
|
index,
|
||||||
}
|
}
|
||||||
@@ -83,7 +72,7 @@ function frameAt(t) {
|
|||||||
return {
|
return {
|
||||||
bars,
|
bars,
|
||||||
ball: {
|
ball: {
|
||||||
color: props.theme === 'dark' ? 'rgb(244, 244, 245)' : 'rgb(24, 24, 27)',
|
color: 'var(--accent)',
|
||||||
scaleX: 1 + heightFactor * 0.25,
|
scaleX: 1 + heightFactor * 0.25,
|
||||||
scaleY: 1 - heightFactor * 0.3,
|
scaleY: 1 - heightFactor * 0.3,
|
||||||
x: `${ballIndex * barTotalWidth}px`,
|
x: `${ballIndex * barTotalWidth}px`,
|
||||||
@@ -126,16 +115,16 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
.wave-physics-track {
|
.wave-physics-track {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 292px;
|
width: 190px;
|
||||||
height: 192px;
|
height: 72px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 8px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wave-physics-bar {
|
.wave-physics-bar {
|
||||||
width: 12px;
|
width: 8px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
transform-origin: bottom center;
|
transform-origin: bottom center;
|
||||||
will-change: height, background-color;
|
will-change: height, background-color;
|
||||||
@@ -145,22 +134,14 @@ onBeforeUnmount(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 12px;
|
width: 8px;
|
||||||
height: 12px;
|
height: 8px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
transform-origin: bottom center;
|
transform-origin: bottom center;
|
||||||
box-shadow: 0 1px 3px color-mix(in srgb, currentColor 24%, transparent);
|
box-shadow: 0 1px 3px color-mix(in srgb, currentColor 24%, transparent);
|
||||||
will-change: transform, background-color;
|
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) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.wave-physics-bar,
|
.wave-physics-bar,
|
||||||
.wave-physics-ball {
|
.wave-physics-ball {
|
||||||
|
|||||||
@@ -67,25 +67,6 @@
|
|||||||
line-height: 1.55;
|
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 {
|
.agent-discovery-status {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -101,7 +82,6 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 12%, transparent);
|
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,
|
.agent-discovery-enter-active,
|
||||||
@@ -125,29 +105,6 @@
|
|||||||
transform: translateY(8px) scale(0.985);
|
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) {
|
@media (max-width: 480px) {
|
||||||
.agent-discovery-overlay { padding: 24px; }
|
.agent-discovery-overlay { padding: 24px; }
|
||||||
.agent-discovery-copy h1 { font-size: 26px; }
|
.agent-discovery-copy h1 { font-size: 26px; }
|
||||||
|
|||||||
Reference in new issue
Block a user