Releases: abloom25/Meliora
Releases · abloom25/Meliora
Release list
v0.2.0-rc7
新增
- 管理 API 安全链路:新增 HMAC CSRF 请求头校验、同源写入检查、可信平台客户端 IP 传递、实例级高成本密码工作并发保护及统一安全错误响应。
- 部署与资源回归测试:新增 CSRF、限流、部署路由、音乐 API 载荷/响应上限、错误脱敏、媒体查询与管理消息定时器测试,全量测试提升至 406 项。
- 播放器组件拆分:从播放器控制区提取 ProgressControl 与 TransportButtons,补充共享媒体查询工具和管理认证卡片组件。
调整
- 配置校验策略统一:生产配置默认要求公网 HTTPS,仅显式开发模式允许私有地址;歌单、本地歌曲和外部测试响应增加数量与体积上限。
- 管理后台结构收口:管理 API 请求、认证卡片、限时消息、上传限制与密码规则改为共享实现,登录页可展示真实后端错误并完善表单可访问性。
- 播放器状态与性能:优化音频切换清理、预加载失败回退、歌词小窗生命周期、主题取色、封面缓存、歌曲索引和曲库交互逻辑。
- 更新工作流:临时更新分支仅保留在本地验证,全部测试通过后再合并并推送目标分支。
修复
- CSRF 合法令牌被拒绝:修复使用仅具备 verify 权限的 Web Crypto 密钥调用 sign 导致所有受保护写请求失败的问题,并移除不安全的 Cookie 令牌回退。
- 播放器 ID 冲突:拒绝重复歌单和重复本地歌曲 ID,避免队列与当前歌曲索引指向不同项目。
- 错误与反馈一致性:5xx 响应不再暴露内部异常文本;修复旧 Toast 定时器提前清除新消息,以及限流/服务繁忙被误报为密码错误的问题。
- PWA 与部署路由:动态 API、稳定上传资源及音视频 Range 请求不再被 Service Worker 错误缓存,Netlify API 重写优先于 SPA 回退。
完整变更记录:v0.2.0-rc6...v0.2.0-rc7
v0.2.0-rc6
Added
- 404 页面兜底:新增
NotFoundView与全局/:pathMatch(.*)*路由,未知路径会展示当前缺失路径并提供「返回播放器」入口;新增router.test.ts覆盖播放器、管理后台和未知路径解析
Changed
- 歌词切歌动画稳定性:
LyricsPanel移除进度条 hover 对主歌词面板的临时驱动,改为只跟随真实播放时间;切歌加载新歌词前统一取消旧歌词请求、滚动 RAF、行级 Web Animations 和用户滚动状态,避免快速切歌时旧歌词行带着位移动画叠到封面区域 - 歌词重对齐节流:歌词行 FLIP 滚动动画运行期间会延后相邻行的下一次重对齐请求,大跨度跳转则立即取消旧动画并切到新目标,减少快速 seek/切歌时的抖动和卡顿
- 进度条歌词预览职责收窄:
PlayerControls不再通过onSeekPreview驱动主歌词面板,只保留进度条浮层预览;浮层内容切换根据指针移动方向使用 forward/backward 动画,触摸拖拽仍保持隐藏 - 移动端抽屉空间优化:播放器曲库/设置抽屉和管理后台移动导航增加半屏偏移计算、底部延展区与 safe-area padding,提高小屏幕下拖拽、滚动和底部内容可达性;曲库、设置面板和管理导航同步加大移动端横向内边距与底部渐隐空间
- 滑杆轨道结构统一:
SettingRange和播放进度条改为显式 track + fill 结构,让填充层裁切更稳定,避免圆角轨道在不同浏览器中出现视觉溢出 - 移动端预览入口调整:便携设备上隐藏「进度条歌词预览」设置项,移动共享控制区不再启用歌词预览浮层,避免触屏拖拽时遮挡主操作区
Fixed
- 有歌词歌曲快速切换重叠:修复歌词已加载后快速切歌时,旧歌词动画未及时取消导致歌词短暂重叠到封面上再消失的问题
- 切歌加载态闪烁:当前歌曲变更时若新歌存在歌词来源,歌词可用状态先进入
loading而不是短暂unavailable,避免歌词视图在有歌词歌曲之间切换时错误回到封面视图 - 移动抽屉关闭误伤:播放器曲库抽屉和设置抽屉的 dismiss 回调拆分为各自关闭当前面板,避免拖拽关闭一个抽屉时顺带关闭其他面板状态
v0.2.0-rc5
Added
- 进度条歌词预览:新增
progressLyricPreview播放器设置项(默认关闭),设置面板新增「进度条歌词预览」开关;桌面进度条悬停/拖拽时可在浮层中显示对应时间点的歌词原文与翻译,并通过LyricsPanel的previewTime/previewActive临时驱动主歌词面板预览目标行;预览采样根据指针移动速度自适应节流,触摸输入默认不显示浮层也不驱动主歌词预览,避免移动端拖拽时遮挡 - 自定义滑杆控件:
SettingRange从原生 range 输入升级为自定义role="slider"控件,统一支持 pointer 拖拽、window pointerup fallback、键盘方向键/PageUp/PageDown/Home/End、aria-valuetext和粗指针触控高度;SleepTimerControl、EqualizerPanel、设置面板的音量/歌词字号/背景/节奏亮度滑杆均复用该控件 - 歌词小窗加载状态:
LyricStatus新增loading,歌词小窗和歌词面板同步显示「正在载入歌词」状态,不再把加载中和空歌词混在一起;useLyricsWindow增强 Safari/弹窗关闭检测,使用固定窗口名复用歌词小窗,并在窗口关闭、切换、卸载时清理轮询、ready 检测和事件监听 - 歌词解析增强:
parseLyrics支持清除逐字歌词<mm:ss.xx>增强时间戳;splitLyricTranslation支持全角中文括号;相同时间点的双行歌词会合并为原文 + 翻译,减少重复行展示
Changed
- 播放器进度条改为自定义 slider:
PlayerControls的播放进度从<input type="range">改为自定义 slider,补齐aria-valuemin/aria-valuemax/aria-valuenow/aria-valuetext,支持键盘 seek 和全局 pointerup 提交,并保持页面版、底部 dock 进度条和移动共享控制区布局一致 - 歌词 Provider 缓存升级:
loadTrackLyrics现在按 providercacheKey对曲目级歌词做 LRU 缓存并去重 in-flight 请求;共享请求保留多消费者语义,单个调用方 abort 只取消自身等待,当所有活跃调用方都 abort 时才中止底层 provider 请求,避免快速切歌时旧歌词请求无意义跑到超时 - 单曲循环播放行为:
useAudioPlayer在playMode === 'single'且音频结束时直接重播当前曲目,重置automaticCrossfadeStarted和 pending seek 状态,不再走下一首预测流程 - 设置与全屏可用性:
SettingsPanel仅在当前平台支持全屏且非便携设备时展示「全屏模式」开关;useFullscreen补充 resize/visualViewport/fullscreenchange 状态同步,让退出全屏或浏览器 UI 尺寸变化后的状态更可靠 - 歌词面板预览和动画细节:
LyricsPanel的活跃歌词时间可在预览模式下切换到进度条 hover 时间;歌词状态快照改为携带LyricStatus;翻译开关、字号变化、面板激活状态和预览时间变化都会触发更精确的 snapshot 与重对齐
Fixed
- 多指拖拽误提交:
SettingRange和PlayerControls进度条现在校验当前pointerId;拖拽期间来自其他手指/鼠标指针的pointermove、pointerup、pointercancel会被忽略,避免触屏场景中音量、均衡器、定时关闭或播放进度跳到错误坐标 - 自定义 slider 可访问值不一致:
SettingRange的视觉进度和aria-valuenow统一使用归一化后的步进值,外部恢复出越界值或非法小数时不会出现视觉/读屏值不一致 - 歌词预览状态泄漏:切歌、关闭 chrome、关闭歌词预览开关、取消拖拽、组件卸载时都会清理进度条歌词浮层和主歌词预览 driver,避免旧预览时间残留到下一首歌
- 播放器测试覆盖缺口:新增
player-controls-lyric-preview.test.ts与use-lyrics-window.test.ts,并扩展settings-controls-a11y、track-lyrics-service、lyrics-panel、lyrics、use-audio-player等测试,覆盖歌词预览、provider 缓存、单曲循环、滑杆键盘/触摸行为和歌词小窗状态同步
v0.2.0-rc4
Added
- 管理后台配置迁移页:新增
src/admin/views/ConfigTransferView.vue,并在AdminSidebar中加入「迁移」导航项(FileJson图标);页面提供「导出未加密配置」和「导入未加密配置」两块操作面板,导出前调用validateMusicConfig(props.config)对当前表单配置做完整 schema 校验,通过后生成meliora-config-plain-<ISO 时间>.json;导出确认弹窗明确提示明文 JSON 会包含站点配置、音乐 API Token、GitHub 代理、预发布开关等敏感字段,同时说明管理员登录密码不会被反向导出;导入流程通过隐藏<input type="file" accept="application/json,.json">读取 JSON,解析失败提示「JSON 格式无效」,schema 校验失败时拼接具体错误,校验通过后先进入「覆盖当前配置?」确认弹窗,确认后仅更新后台表单并提示「确认无误后请保存」,不会直接提交到仓库 - 桌面侧边栏折叠模式:
AdminSidebar新增desktopCollapsed状态与meliora:admin-sidebar-collapsedlocalStorage 持久化键,桌面端可通过PanelLeftClose/PanelLeftOpen图标按钮在 200px 完整导航和 72px 图标导航间切换;折叠后隐藏品牌文案和 tab 文案,保留图标、tooltip、保存/退出/返回按钮的可访问标题;移动端强制恢复完整 sticky 顶栏布局,避免折叠状态污染手机导航 - 音乐来源适配器层:新增
src/services/music-adapters/types.ts定义MusicProviderAdapter<TSource>与MusicProviderContext;新增local.ts/meting.ts两个 adapter,分别处理本地曲目映射和 Meting 歌单拉取;loadConfiguredTracks支持通过LoadConfiguredTracksOptions.adapters注入测试/自定义 adapter,并把每个来源统一封装为ConfiguredSource.load(config)任务,后续新增其他音乐来源时不再需要把逻辑塞进src/services/music.ts - 曲目级歌词 Provider 注册表:
src/services/lyrics.ts新增TrackLyricsProvider、registerTrackLyrics、loadTrackLyrics、hasTrackLyricsSource、hasCachedTrackLyrics、transferTrackLyricsProvider、mergeTrackLyricsProvider;歌词来源从Track.lyricsUrl字段迁移到 WeakMap 注册表,本地曲目 provider 优先级为 20,Meting provider 优先级为 10,去重时高优先级 provider 会保留到最终曲目;loadLrcLyrics统一负责loadLyricsText+parseLyrics+ 空歌词过滤 - 分享别名与标题版本兼容:
Track类型新增titleVersions?和shareAliases?,并将kind扩展为'meting' | 'remote' | 'local';mapMetingTrack/mapLocalTrack通过splitDisplayTitle把标题中的版本信息拆入titleVersions,formatTrackDisplayTitle用于显示和分享文案;trackIdentity将主标题和所有版本标题一起参与归一化,trackMatchesShareId同时匹配当前哈希和历史shareAliases,保证旧版基于原始标题生成的?share=链接在标题拆分后仍能定位歌曲 - 更新 workflow 防护测试:新增
server/tests/update-workflow.test.ts,静态校验.github/workflows/update-from-upstream.yml必须包含 GitHub proxy 输入校验、curl --max-redirs 0、以及合并目标分支后重新执行pnpm install --frozen-lockfile、pnpm test、pnpm type-check、pnpm lint、pnpm format:check、pnpm build、pnpm test:bundle - 音乐 API 重定向测试:新增
server/tests/music-api-tester.test.ts,覆盖后台音乐 API 测试遇到 302 重定向时不会跟随到内网/元数据地址,并返回error: '重定向已拒绝' - 配置迁移、管理导航和播放器测试补充:新增
src/tests/admin-config-transfer.test.ts覆盖迁移入口、桌面侧边栏折叠恢复、明文配置导出风险确认、合法 JSON 导入覆盖确认、非法 JSON 校验失败;新增/扩展about-update-status、admin-about-navigation、dom-utils、settings-controls-a11y、track-lyrics-service、use-drawer-sheet、use-keyboard-shortcuts等测试,覆盖本次 UI 与数据流改动
Changed
- 管理后台页面壳层重组:
DashboardView.vue新增.dashboard-shell包裹AdminSidebar和dashboard-content,桌面端保持横向布局,760px 以下改为纵向布局;tab 切换新增transfer分支,将ConfigTransferView接入主后台并复用现有showMessage通知;确认弹窗和 Toast 移入 shell 内部,确保迁移页、关于页和设置页的遮罩层级一致 - 关于页更新轮询更精细:
AboutView.vue调用checkUpdate时传入{ markUnauthenticated: openModalOnUpdate },避免静默轮询状态时误把认证弹窗打断到前台;refreshUpdateStatus开始读取服务端返回的retryAfterSeconds,当 workflow 状态为locating/queued/running时优先使用服务端建议的轮询间隔,否则回退到 3s/5s;模板外层增加.about-root,让关于页在后台 shell 中布局更稳定 - 管理后台表单可访问性补强:
AdvancedSettingsEditor的预发布开关、AnalyticsSettingsEditor的 Umami/Google Analytics 开关、PlaylistEditor的歌单启用开关均补充明确aria-label,让图标/开关控件在读屏器中不再只有泛化状态 - 播放器歌词加载链路改为按曲目加载:
LyricsPanel.vue不再监听currentTrack.lyricsUrl,改为监听[currentTrack.id, currentTrackVersion]并调用loadTrackLyrics(currentTrack);歌词缓存判断改为hasCachedTrackLyrics(track),空歌词/无 provider 统一进入empty;usePreloadPool和useAudioPlayer的歌词预加载也改为传入完整Track,使本地曲目、Meting 曲目和未来 adapter 曲目的歌词来源都走同一套 provider 机制 - 当前曲目对象保活更新:
src/stores/player.ts新增currentTrackVersion;setTracks在刷新曲库时若当前歌曲仍存在,保留原activeTrack引用并把新字段Object.assign回去,同时调用transferTrackLyricsProvider(track, activeTrack)迁移歌词 provider 并递增currentTrackVersion;这样可以避免音频播放中的当前曲目对象被替换导致播放状态、歌词和预加载出现短暂脱节 - 曲目去重保留元数据:
deduplicateTracks从Set改为Map<string, Track>,新增mergeDuplicate回调;loadConfiguredTracks在远程/本地来源去重时会合并歌词 provider 和分享别名,避免同一首歌在多个来源出现时丢失歌词来源或旧分享链接兼容信息 - 搜索和展示支持标题版本:
filterTracks会同时匹配track.titleVersions;TrackList不再对track.title现场调用splitDisplayTitle,而是直接消费track.title与track.titleVersions,避免列表渲染时重复拆分标题;播放器顶部标题同样改为从currentTrack.titleVersions读取版本信息 - 分享文案统一使用展示标题:
useTrackShare从splitDisplayTitle(track.title)改为formatTrackDisplayTitle(track),分享标题包含主标题和版本信息,分享 URL 继续使用?share=<createTrackShareId(track)>,但打开链接时PlayerView通过trackMatchesShareId匹配当前哈希或历史别名 - 设置面板空曲库禁用:
PlayerView.vue新增settingsAvailable = tracks.length > 0;没有歌曲时设置按钮禁用,aria-label/title显示「暂无歌曲,设置不可用」,如果曲库变空时设置面板已打开会自动关闭,避免空曲库下进入无意义设置抽屉 - 顶栏点击关闭面板更稳:
PlayerView.onTopbarClick改用isInteractiveElement(e.target)判断交互元素,替代手写closest('button, a, [role="button"], input, label'),让 label、输入框、按钮及后续扩展的交互控件不误触发关闭面板 - 开发生成流程统一:
package.json新增generate:dev,将predev、pretype-check、pretest、pretest:watch、prelint统一改为pnpm generate:dev;test:bundle显式加入脚本,本地与 CI 都能用同一命令检查构建产物泄漏 - CI 质量门禁加严:
main-validation.yml与pr-validation.yml在pnpm build后新增pnpm test:bundle;update-from-upstream.yml在验证阶段和合并目标分支后都加入 bundle 泄漏检查,并在最终合并前补跑pnpm install --frozen-lockfile、lint、format:check,减少临时分支落后目标分支时绕过质量门禁的可能 - GitHub proxy 输入和请求策略收紧:
update-from-upstream.yml新增Validate GitHub proxy input步骤,拒绝非 http(s)、无 hostname、localhost、.localhost、.local、私有/回环/link-local/组播/reserved/unspecified IP;workflow 中经代理访问 GitHub API 的curl改为--max-redirs 0,避免代理或恶意响应把请求重定向到非预期地址 - bundle 泄漏测试显式执行时必须有 dist:
server/tests/bundle-leakage.test.ts增加explicitBundleCheck,只有普通全量测试且dist不存在时才 skip;当用户显式运行pnpm test:bundle时如果dist缺失会直接失败,避免误以为检查已执行
Fixed
- 歌词请求取消误伤共享缓存:
loadLyricsText调整 Abort 语义:缓存层内部AbortController只负责 8 秒超时,调用方传入的signal只取消当前等待(withAbortSignal),不再把外部 abort 转发到共享 fetch;修复快速切歌、预加载和面板重载同时请求同一个歌词 URL 时,一个消费者取消会把其他消费者一起取消的问题 - 更新检查拒绝 GitHub 重定向:
server/core/update-handler.ts对 latest release、tags、workflow runs、workflow dispatch 请求均设置redirect: 'manual';当 GitHub 或代理返回 3xx 时,检查更新映射为400并提示「GitHub 代理重定向已拒绝,请使用直连的公网代理地址」,防止代理配置把服务端请求带到非预期目标 - 音乐 API 测试拒绝重定向:
server/core/music-api-tester.ts的fetchWithTimeout增加redirect: 'manual',歌单测试遇到 3xx 时返回失败结果、保留状态码并显示「重定向已拒绝」,避免后台测试接口跟随重定向访问内网地址 - 当前曲目刷新后歌词不更新:曲库重新加载时如果当前曲目 ID 不变但歌词 provider、标题版本或分享别名发生变化,现在通过
currentTrackVersion触发LyricsPanel重新加载歌词,同时保留当前播放对象引用,修复配置更新/远程歌单刷新后歌词仍使用旧来源的问题 - 旧分享链接在标题拆分后失效:
mapMetingTrack/mapLocalTrack会根据拆分前原始标题生成 legacy share ID,当它与新 share ID 不一致时写入shareAliases;PlayerView打开?share=时同时匹配别名,修复标题从「歌名 (版本)」拆为title + titleVersions后旧链接找不到歌曲的问题 - 空曲库仍可打开设置:设置按钮在
tracks.length === 0时禁用并自动关闭已打开的设置面板,避免没有任何歌曲时打开设置抽屉造成误导 - 明文配置导入/导出类型构建问题:
admin-config-transfer.test.ts改为显式保存导出的 Blob holder,并使用find(...).exists()做 Vue Test Utils 断言,修复vue-tsc -b在项目引用构建下对可选链和get().exists()的类型误判 - 音乐 adapter 联合泛型构建问题:
src/services/music.ts将ConfiguredMusicSource<T>联合改为闭包式ConfiguredSource.load(config),消除ConfiguredMusicSource<LocalTrackConfig>被错误传给ConfiguredMusicSource<MetingPlaylistConfig>的 TypeScript 构建错误
v0.2.0-rc3
Added
- 歌词翻译开关:新增
lyricTranslation设置项(默认开启),关闭时翻译行通过<Transition name="translation-toggle">以max-height: 2.2em → 0+translate: 0 → -0.18em+opacity: 0.76 → 0三重过渡动画收起;PlayerSettings类型与migrateSettings均新增该字段,默认值true保证向后兼容;displayedLinescomputed 在关闭时逐行剥离translation字段,同时emitSnapshot改发displayedLines而非原始lines,歌词小窗同步响应翻译开关 - 管理后台移动端导航面板:
AdminSidebar< 760px 时折叠为 sticky 顶栏(站点名 + 面包屑 + 汉堡按钮),新增<Teleport to="body">底部抽屉式mobile-nav-panel,复用useDrawerSheet实现全屏/半屏/关闭三段吸附拖拽,isMobileViewport()动态启用;面板内双列grid布局容纳 7 个导航标签,底部sidebar-footer包含「保存全部」(跨列占满)、「退出登录」、「返回播放器」三按钮;关闭时选中标签/保存/退出均自动触发dismissMobileNavAnimated(),遮挡层.mobile-nav-backdrop半屏时扩大 inset 覆盖全屏并加深背景;面板样式统一玻璃拟态(backdrop-filter: blur(52px) saturate(180%))+ 圆角顶部 + 底部 safe-area-inset 适配,420px 以下进一步紧凑间距 useFocusTrapautoFocus选项:新增可选的options.autoFocus参数,类型为MaybeRefOrGetter<boolean>,默认true;当toValue(options?.autoFocus) === false时跳过自动聚焦但仍设置pendingActivation = false避免状态卡死,移动端 sheet 抽屉(libraryDrawerRef)传入autoFocus: () => !isMobileSheet.value以跳过移动端自动对焦PlayerControls竖排布局variant="vertical":新增长 54px 的纵向排列模式,播放/上一首/下一首按钮按列堆叠(flex-direction: column),播放按钮 46×46px 带box-shadow强调,不渲染进度条行(variant !== 'bar' && variant !== 'vertical'),用于歌词小窗等窄空间场景- 开发模式本地配置同步:
vite.config.ts新增melioraLocalConfigPlugin,在 Vite dev server 注册POST /__meliora-dev/config中间件;管理后台saveConfig在import.meta.env.DEV下自动将清洗后的配置POST到该端点,插件经validateMusicConfig校验后写入.meliora/config.local.json并调用generatePublicConfig重新生成src/generated/public-config.ts,最后通过server.ws.send('full-reload')触发热更新,开发环境改配置后无需手动重启;SaveResult新增warning?字段,同步失败时ok: true但携带 warning 提示 - 本地开发配置优先读取:
generatePublicConfig在MELIORA_LOAD_DEV_VARS=true且.meliora/config.local.json存在时,优先以它作为配置源生成本地公开配置,代替public/config.json;pnpm generate:public-config:dev复用此逻辑 createTrackShareId确定性分享标识:src/utils/tracks.ts新增基于 FNV-1a 哈希(hashShareIdentity)的确定性 share ID,输入为trackIdentity(标题+艺术家归一化),输出为 36 进制短字符串(不含内部 URL 敏感信息);分享 URL 从?track=<track.id>改为?share=<hash>,useTrackShare分享文案从「正在 Meliora 收听」改为「Meliora: <标题 - 艺术家>」,剪贴板内容同步简化;loadTracks改为按share参数匹配加载后自动移除参数避免分享链接残留shouldUseIOSBackgroundSafeAudio浏览器能力检测:src/utils/browser.ts新增该函数,返回isIOSDevice() && isSafariBrowser()的合取,统一标识 iOS Safari 后台播放受限场景useDeviceDetectioncomposable:新增src/composables/useDeviceDetection.ts,抽离原本散布在App.vue中的compactViewport、portableDevice、phoneDevice、lyricsWindowSupported和isMobileSheet、viewportMode(枚举wide/narrow/phone)等设备检测逻辑为独立 composable
Changed
- 播放器架构拆分:原 2500 行
App.vue拆为两文件——App.vue缩至 ~25 行,仅保留<router-view>+<Transition mode="out-in">作为路由壳(接管原AppShell.vue职责);播放器全部业务逻辑迁移至src/views/PlayerView.vue;路由从component: App改为component: PlayerView;main.ts直接挂载App而非AppShell;AppShell.vue已删除;agent.md分层图同步更新为src/main.ts → src/App.vue → src/views/PlayerView.vue SettingsPanel去 props 化:组件不再通过 props 接收settings: PlayerSettings,改为内部直接storeToRefs(usePlayerStore())获取响应式settings;PlayerView.vue移除:settings="settings"传递;消除了 15 处vue/no-mutating-propsESLint 违规;PlayerSettings类型导入从 SettingsPanel 移除,不再依赖src/types/musicLyricsPanel滚动对齐全面重构:新增syncActiveLyric/scheduleRealign统一入口、ScrollToIndexOptions动画控制、programmaticScroll标记防误触发、键盘滚动检测、ResizeObserver监听布局变化、.distance-0~.distance-5CSS class 代替 inline style、--lyric-size上移面板根节点;行间距改为动态clamp(22px, calc(var(--lyric-size) * 1.28), 42px);浏览模式 hover 行加::before伪元素高亮;新增activeprop 控制对齐策略- 翻译切换动画:翻译行包裹
<Transition name="translation-toggle">,CSS 定义max-height(2.2em ↔ 0)、margin-top、opacity(0.76 ↔ 0)、translate(0 ↔ -0.18em) 四属性 360ms cubic-out 过渡;prefers-reduced-motion下过渡时长清零 - iOS Safari 后台播放适配:
useAudioPlayer检测到 iOS Safari 时:(a)所有<audio>设置playsinline属性;(b)活跃音频挂载到 DOM 节点维持后台播放;(c)绕过 Web Audio 节拍分析;(d)关闭 crossfade;(e)初始化/切歌/错误恢复路径均调用挂载;(f)卸载时清理 DOM host useBeatAnalyser音频资源释放:停止节拍分析时挂起共享AudioContext释放音频硬件,后续可 resume 恢复- 分享链接去内部 ID 化:分享 URL 从
?track=<track.id>改为?share=<FNV-1a hash>,文案简化为「Meliora: <标题 - 艺术家>」 TrackList搜索输入简化:debouncedQuery直接v-model绑定,handleSearchInput由 watcher 驱动- 管理后台全页面移动端响应式增强:
AdminApp、DashboardView、SiteSettingsEditor、SecurityEditor、AdvancedSettingsEditor、AnalyticsSettingsEditor均补充 720px/420px 断点适配 useDrawerSheet事件去重:handle 发起的拖拽不再被 container 的pointermove重复触发useLyricsWindow歌词小窗跨 toggle 复用:window.open使用固定窗口名称'meliora-lyrics',跨 toggle 复用- 版本脚本路径参数化:
generatePublicConfig路径改为函数,DEV 模式支持.meliora/config.local.json优先 - 测试增强:
use-audio-player.test.ts新增 3 个 iOS 适配测试;tracks.test.ts新增createTrackShareId测试;player-store.test.ts新增lyricTranslation迁移测试;use-preload-pool.test.ts补默认 settings - README 补充:新增本地配置同步章节、路线图链接、署名补充
agent.md规范更新:分层图路径、依赖方向、路由过渡参考随架构拆分同步修正
Fixed
useFocusTrap未使用变量:移除声明后从未引用的局部autoFocus变量PlayerView.vue未使用导入:移除PlayerViewportMode类型导入与viewportMode解构变量SettingsPanel.vue15 处 props 直接修改:组件改为使用 Pinia store 后彻底消除vue/no-mutating-props违规- 4 处
vue-tsc -b严格类型错误:AdminSidebaremit 联合类型重载、HTMLAudioElement.playsInline非标准属性、测试文件类型断言缺失、测试playsInline断言 - 5 处 Prettier 格式化违规:
useFocusTrap.tsimport 语句、PlayerView.vue缩进/解构/多行属性、TrackList.vuev-model 换行
Tests
- 歌词面板测试:新增
lyrics-panel.test.ts,覆盖滚动对齐、字号变更重对齐、ResizeObserver 集成、用户浏览模式计时器暂停、active tab 切换时状态同步
v0.2.0-rc2
Added
- 播放器均衡器组件:新增
EqualizerPanel,承载均衡器开关、预设选择、自定义频段增益调节,预设切换时触发触感反馈 - 定时关闭组件:新增
SleepTimerControl,将定时关闭滑块、刻度、剩余时间展示从主App.vue中拆出 - 触感反馈 composable:新增
useHaptic,统一封装light、medium、heavy、selection、success、warning、error等振动模式,并在非手机设备或prefers-reduced-motion下自动禁用 - 播放队列预测 API:播放器 store 新增
peekNext()/peekPrevious(),可在不改变当前曲目、进度和队列版本的情况下预测上一首/下一首 - 跨域音频降级播放:
useAudioPlayer新增 Web Audio CORS 失败降级路径,分析链路无法接入时会重建无crossOrigin的 audio 元素,保留播放能力并降级节拍分析 - 主封面 CORS 回退:主封面以
crossorigin="anonymous"首次加载失败时会移除crossorigin重建图片,优先保证封面显示,取色失败则回退默认主题 - 共享配置 schema:新增
shared/config-schema.ts,服务端保存配置和构建期公开配置生成共用同一套字段清洗与校验;覆盖receivePrereleaseUpdates、githubProxy、统计、站点验证、自定义 CSS/JS 等字段 - 共享环境 schema:新增
shared/env-schema.ts,统一DEVELOPMENT真值解析、GH_REPO格式校验与CONFIG_ENCRYPTION_KEY强度校验 - 共享版本工具:新增
shared/version.ts,支持严格 SemVer 解析、比较、预发布判断、v前缀 /refs/tags/规范化和最新版本选择 - 构建期公开配置生成:新增
scripts/generate-public-config.mjs与scripts/generate-public-config.d.mts,从缺失、明文或加密的public/config.json生成src/generated/public-config.ts与src/generated/admin-env.ts - 管理后台构建状态:公开配置生成时同步输出
idle、disabled、env-not-ready状态,前端可展示管理后台禁用或环境变量未就绪原因 - Pages 本地开发准备脚本:新增
scripts/prepare-pages-dev.mjs,为 Wrangler Pages Functions 本地开发创建.wrangler/pages-dev-static,让 Vite 提供页面、Wrangler 只模拟/api/* - Cloudflare Pages 静态规则:新增
public/_headers与public/_redirects,统一安全响应头、Service Worker 缓存、manifest 类型、静态资源 immutable 缓存与 SPA fallback - 一键更新状态跟踪:后台触发更新后返回
triggeredAt与triggerId,关于页持续轮询 GitHub Actions 状态并展示日志链接、失败详情和成功提交链接 - 更新状态接口:新增
GET /api/update/status?since=...&triggerId=...,归一化返回locating、queued、running、success、failed、cancelled、timed_out,并带retryAfterSeconds、Actions URL、commit URL 与失败消息 - 预发布版本开关:高级设置新增“接收预发布版本”,稳定版默认只接收稳定更新;开启后可接收 rc / beta / alpha,当前版本本身为 prerelease 时继续自动接收 prerelease
- 主分支验证 workflow:新增
.github/workflows/main-validation.yml,在mainpush 与手动触发时执行安装、测试、类型检查、Lint、格式检查、构建、自动 tag;稳定版自动创建 GitHub Release,预发布版仅打 tag - 管理后台上传常量:管理 API 新增
MAX_UPLOAD_BYTES、MAX_UPLOAD_BASE64_LENGTH、MAX_UPLOAD_SIZE_LABEL,统一前端上传限制 - 本地曲目编辑提示:本地曲目编辑器新增未补全提示,缺少 ID、标题、艺术家或音频时提示保存会被拦截
- 本地曲目删除失败展示:删除确认框新增逐项失败展示,便于看到具体失败文件
Changed
- 版本升级:
package.json版本从0.2.0-rc1升级到0.2.0-rc2 - 运行环境收紧:
package.jsonengines 从 Node>=20/ pnpm>=8调整为 Node>=22/ pnpm>=11.5.3,并固定packageManager为pnpm@11.5.3 - 生成流程拆分:
generate拆为generate:app-version与generate:public-config;predev使用开发变量生成公开配置,prebuild、pretest、pretype-check、prelint统一先生成构建产物 - 前端配置加载改为构建期公开配置:播放器从异步
/api/runtime-config改为同步读取loadMusicConfig(),并按公开apiEndpoint注入preconnect - 类型拆分公开配置和完整配置:新增
PublicMusicConfig,前端公开配置不再包含apiToken、githubProxy、receivePrereleaseUpdates等管理员/服务端字段 - Meting 请求去 token 化:前端歌单请求不再向 Meting API URL 拼接
token,只发送server、type、id - 播放器主文件瘦身:
App.vue移除内联均衡器、定时关闭、触感反馈逻辑,改为组合EqualizerPanel、SleepTimerControl、useHaptic - 空曲库体验调整:无曲目时提示改为“暂无可播放歌曲,请在管理后台添加音乐”,且不再把空曲库视为加载失败
- 切歌状态机重构:
useAudioPlayer改为明确的idle/switching状态机,使用AbortController取消过期切歌请求 - 音量淡入淡出重构:fade 动画从全局计数改为按 audio 元素独立管理,避免新旧音频淡入淡出互相取消
- 预加载目标预测统一:预加载池复用 store 的
peekNext()/peekPrevious(),保证顺序、循环、单曲、随机模式下预加载目标与实际切歌一致 - 预加载失败文案调整:后台预加载失败提示改为“预加载歌曲暂时无法播放,当前播放不受影响”,避免误报“已跳过”
- 虚拟曲库列表布局调整:
TrackList使用完整高度 spacer 包裹绝对定位条目,空列表时不渲染大高度 spacer - 封面缓存上限:
useCoverCache对 loaded / failed 封面缓存增加 512 条上限,按最近使用淘汰旧 trackId - Reduced motion 行为调整:
useBeatAnalyser在 reduced motion 下仍构建 Web Audio / EQ 图,但跳过视觉 RAF 循环和可见性监听 - Media Session 清理:无当前曲目时清空 metadata 并设置 playbackState 为
none - 开发模式显式化:服务端从“根据
GH_TOKEN是否为空/占位符推断本地模式”改为显式DEVELOPMENT=true/1/yes/on;Vercel、Cloudflare Pages、Netlify API 入口均透传DEVELOPMENT - 环境就绪校验调整:生产环境重点校验
GH_REPO格式与CONFIG_ENCRYPTION_KEY强度;GH_TOKEN不再作为公开状态页硬性校验项,需要写 GitHub 的接口仍会单独检查 token - 配置读取 fail-closed:
getConfig在 GitHub 读取失败时返回502;生产明文配置返回409;解密失败返回409并提示检查CONFIG_ENCRYPTION_KEY;保存时写入 shared schema 清洗后的配置 - 状态页改为 JSON 状态:
renderEnvNotReadyPage与renderDisabledPage不再返回完整 HTML,状态探针返回{ status, detail },由前端渲染管理后台禁用/环境未就绪状态 - 更新检查改为 POST JSON:
/api/check-update从 GET query 改为带认证、同源校验、JSON Content-Type 的 POST body,携带current、githubProxy、receivePrereleaseUpdates - 更新检查逻辑增强:同时读取 latest release 和 tags;release 不可用时 fallback 到 tag;Tags fallback 使用
per_page=100;版本选择改用严格 SemVer 排序 - 预发布策略明确:稳定版默认忽略 prerelease;当前版本为 prerelease 或显式开启“接收预发布版本”时才接收 prerelease
- GitHub 请求错误映射:更新检查统一 8 秒超时,并将 401/403/429/5xx/超时映射为可操作错误
- GitHub 代理语义收敛:检查更新可走代理且不附带
GH_TOKEN;workflow dispatch 与 Actions 状态查询始终直连 GitHub API,代理仅作为 workflow input 传给上游拉取步骤 - 更新目标精确锁定:检查更新返回
targetTag,触发 workflow 时传入target_tag、allow_prerelease、trigger_id,workflow 优先同步后台选定 tag - 更新轮询限流调整:
/api/update/status调整为 240 次 / 15 分钟,覆盖最长 10 分钟 running 轮询窗口 - 上传路径白名单收紧:
/api/upload从允许整个public/改为只允许public/music/下白名单媒体/歌词/图片扩展名,以及public/icon.png|jpg|jpeg|webp|ico - 上传大小统一:本地曲目和站点图标上传限制统一为 25MiB,base64 长度限制精确匹配 25MiB 文件边界
- 删除文件返回结构化:删除 API 从简单布尔返回改为
DeleteFilesResult,前端校验返回数量并展示逐项失败;远端文件不存在时视为已删除成功 - GitHub Contents API 编码增强:仓库、分支和文件路径按段编码,避免
?/#/&/空格等字符扭曲 URL 或注入查询参数 - 认证与密码安全增强:管理员密码 PBKDF2 迭代次数从 100k 提升到 600k;
tokenVersion缓存 TTL 从 30 秒缩短到 5 秒;开发模式 Cookie 签名密钥包含GH_REPO;限流键移除可伪造的 User-Agent - API 写请求同源校验:所有
/api/下 POST/PUT/DELETE 检查Origin或Referer;跨站返回403;开发模式允许 localhost Vite/Wrangler 代理写请求 - 本地后端开发命令调整:
dev:admin先执行prepare-pages-dev.mjs,再运行.wrangler/pages-dev-static,不再依赖旧dist - 格式化命令兼容性:
format与format:check增加--ignore-unknown - Vite base 固定:
vite.config.ts的base从process.env.BASE_PATH || '/'改为固定/ - 部署平台收敛:移除 GitHub Pages 部署假设,平台口径收敛为 Vercel、Netlify、Cloudflare Pages
- Cloudflare Pages 配置瘦身:
wrangler.toml只保留核心 Pages 配置,headers/redirects 迁移到public/_headers/public/_redirects - Netlify 构建环境固定:
PNPM_VERSION从11固定为11.5.3,并移除重复 inline build environment - TypeScript 覆盖范围扩展:
tsconfig.app.json与tsconfig.server.json纳入shared/**/*.ts;服务端 tsconfig 增加 Node types - 版本脚本 fail-fast:
generate-app-version.mjs与inject-sw-cache-name.mjs校验package.jsonversion 必须是合法 SemVer,不再静默回退0.0.0,并用JSON.stringify输出版本字符串 - Service Worker cache 注入 fail-fast:
inject-sw-cache-name.mjs在dist/sw.js缺少__SW_CACHE_NAME__占位符时直接失败 - 用户数据保留规则调整:上游同步继续保留
.dev.vars、.env、.env.local、.env.production、public/admin.json、public/config.json、public/music/、构建产物和本地目录;模板文件可随上游更新 - 站点图标上传流程调整:上传成功等待 FileReader 和网络请求均完成后再提示,默认使用
png后缀,上传类型移除 SVG - 管理员密码前端限制增强:修改密码最小长度从 6 位提升到 8 位
Fixed
- 快速切歌竞态:修复旧切歌请求继续提交状态、旧音频叠放播放、切歌异常后长期卡在过渡状态的问题
- 旧音频静音失败:修复手动切歌时旧音频淡出被新音频淡入取消,导致旧音频未及时静音的问题
- 曲库刷新同步:修复当前曲目从刷新结果中被删除后仍保留旧曲目、进度、时长或播放状态的问题;队列现在会同步删除不存在歌曲并追加新增歌曲
- pending seek 回弹:修复无 metadata 时 seek 导致进度条回弹的问题,现在先记录 pending seek 并更新 UI,待 duration 可用后再写入 audio
- 播放失败反馈:修复
skipOnError关闭或队列只有一首时错误消息被清空的问题 - 卸载后自动跳过:修复自动跳过计时器在组件卸载后仍可能触发的问题
- 跨域音频播放:修复跨域音频无法接入 Web Audio API 时播放被节拍分析拖垮的问题,现在降级分析但继续播放
- 跨域封面显示:修复跨域封面缺少 CORS 响应头时直接失败、不显示封面的问题
- 歌词缓存 abort:修复歌词缓存命中时调用方 abort signal 无法独立生效、已 abort signal 没有立即 reject 的问题
- 持久化设置清洗:修复
NaN、Infinity、越界数值、非法播放模式、非法均衡器预设等脏数据泄漏到运行时的问题 - 虚拟列表高度:修复曲库虚拟列表滚动高度不完整、空状态前出现异常大 spacer 的问题
- Media Session 兼容:修复 Safari/旧浏览器 Media Session 赋值或 handler 注册失败时可能影响播放流程的问题
- 管理后台状态识别:修复管理后台禁用或环境未就绪时可能被误判为“后端不可用”的问题
- 状态页 HTML 写入:修复
/api/setup-status返回 HTML 时前端直接document.write()替换页面的问题,改为标记 API 不可用 - 登录过期处理:修复配置加载、保存、上传、删除、改密、音乐 API 测试、更新检查/触发/状态查询遇到 401/403 后本地仍保持已登录状态的问题
- 配置提交前校验:修复无效本地曲目配置可能被提交到后端的问题
- 本地文件部分删除:修复本地曲目文件部分删除失败时仍从配置中移除曲目的问题
- 删除结果校验:修复删除文件接口未校验后端返回结果数量或逐项失败状态的问题
- 站点图标上传竞态:修复文件读取/上传尚未完成时就进入成功状态的问题
- 更新检查回写竞态:修复重复检查和组件卸载后异步状态回写的风险
- 更新触发反馈:修复触发更新后只能显示短暂提示、无法跟踪 GitHub Actions 执行结果的问题
- 后端错误 detail 展示:修复更新检查或触发失败时未优先展示后端
detail的问题 - 超大上传拦截:修复超大 base64 上传内容仍可能被发送到后端的问题
- 密钥耦合:修复
GH_TOKEN轮换会影响配置解密/签名的问题,配置加密和 Cookie 签名均改用CONFIG_ENCRYPTION_KEY派生 - 配置读取静默回落:修复生产配置读取失败、明文配置或解密失败时静默回落默认配置的问题,避免隐藏密钥错误或配置损坏
- 初始化重开风险:修复
admin.json明文或解密失败可能被误判为未初始化并重新开放 setup 的风险 - 代理 SSRF:修复
githubProxy可指向内网/本地地址的风险,同时避免通过代理检查更新时泄露GH_TOKEN - 跨站写请求:修复旧版 CORS/OPTIONS 行为与 Cookie 凭据组合可能形成跨站管理 API 风险的问题
- 更新接口非 JSON 触发:修复
/api/update、/api/check-update可接受非 JSON 请求并触发外部调用的问题 - 上传覆盖敏感文件:修复上传接口可覆盖
public/sw.js、manifest、robots、public/config.json、public/admin.json或上传可执行前端资源的风险 - 上传路径绕过:修复路径穿越、URL-like path、编码穿越、畸形 percent encoding 等路径校验绕过
- GitHub 路径编码:修复 GitHub 文件路径未编码导致特殊字符影响 Contents API URL 的问题
- 更新版本比较:修复预发布、tag fallback、非法 tag、字符串排序导致的更新误判
- 删除远端 404:修复删除远端已不存在的允许路径时被标记为删除失败的问题
- PWA API 缓存:修复 Service Worker 缓存优先策略可能让
/api/auth返回旧登录状态的问题,现在/api/*直接放行网络 - PWA 缓存清理范围:修复 activate 阶段清理过宽的问题,现在只清理
meliora-shell-前缀且非当前版本的缓存 - 更新 workflow 直写目标分支:修复上游同步后直接 push 目标分支的风险,现在先推临时分支、完成验证、合并目标分支最新状态并再次验证后才写回
- Cloudflare Pages 规则兼容:修复 headers/redirects 放在
wrangler.toml的兼容性问题,迁移到_headers/_redirects
Removed
- 移除前台更新检查服务:删除未接线的
src/services/updates.ts与src/tests/updates-cache.test.ts,更新入口统一收敛到后台关于页 - 移除
/api/runtime-config:公开运行配置改由构建期 public config 提供;就绪、禁用、环境未就绪状态下均返回 404 - 移除服务端内置 HTML 状态页模板:状态页接口只返回 JSON 状态
- 移除旧配置校验导出:删除
server/core/config-handler.ts内部validateConfig,配置校验迁移到shared/config-schema.ts - 移除
GH_TOKEN占位符本地模式推断:开发模式必须显式配置DEVELOPMENT - 移除前端运行时配置拉取路径:播放器不再请求
/api/runtime-config - 移除前端 Meting token 拼接:前端不再把
apiToken拼进歌单请求 URL - 移除
App.vue内联均衡器 / 定时关闭 / 触感反馈逻辑:对应逻辑改由新组件和 composable 接管 - 移除抽屉 opener
defineExpose引用 - **移除 GitHub Pages 部署 workf...
v0.2.0-rc1
Added
- 管理后台:新增完整的 Web 管理后台(前端 SPA + 后端 serverless API),访问
/admin进入。支持首次初始化、密码登录、配置可视化编辑、文件上传、版本更新检查与触发,无需手动编辑仓库文件即可管理站点 /setup初始化页面:首次部署后访问/admin自动进入设置密码流程,密码经 PBKDF2-SHA256(100k 迭代 + 16 字节随机盐)哈希后存储到public/admin.json,设置后/setup自动关闭(先到先得模型,利用 GitHub API 409/422 防并发初始化竞态)- 配置文件全文加密:
public/config.json与public/admin.json写入 GitHub 仓库时使用 AES-GCM 256 加密,密钥由CONFIG_ENCRYPTION_KEY经 PBKDF2(100k 迭代)派生,仓库中只存储v1:base64(salt+iv+ciphertext)密文;开发模式(DEVELOPMENT=true)不加密 CONFIG_ENCRYPTION_KEY环境变量:新增独立配置加密密钥,配置加密与 Cookie 签名均从它派生,与GH_TOKEN完全解耦,GH_TOKEN可独立轮换而不影响已加密配置与已签发 Cookie;生产模式校验密钥强度(至少 32 位、拒绝全相同字符 / 纯顺序字符等弱模式)ADMIN_DISABLED环境变量:设为true或1时禁用管理后台,/admin显示"已禁用"页,除状态探针外所有/api/*返回 403;播放器公开配置改为构建期硬编译,不再依赖管理 API- Cookie 鉴权体系(
server/core/auth.ts):基于 HMAC-SHA256 签名的 7 天有效期 token(payloadB64.sigB64),HttpOnly + Secure + SameSite=Lax;签名密钥从CONFIG_ENCRYPTION_KEY派生;tokenVersion机制——修改密码时自增并持久化,旧 Cookie 立即失效;timingSafeEqual常量时间比较防时序侧信道 - 速率限制(
server/core/rate-limit.ts):登录 / 初始化 / 更新端点独立限流(滑动窗口 + 超限封禁),按key + IP + UserAgent维度,登录成功后重置计数;客户端 IP 优先取CF-Connecting-IP - GitHub 持久化层(
server/core/github.ts):封装 Contents API 的文件读 / 写 / 删,8 秒超时,sha乐观锁防并发覆盖,GitHubWriteError携带 HTTP 状态码 - 版本更新检查与一键触发(
server/core/update-handler.ts+.github/workflows/update-from-upstream.yml):后台关于页检查上游 Release,完整 SemVer 实现(支持预发布版本比较);一键触发update-from-upstream.ymlworkflow 拉取上游 release 到临时分支,验证通过后自动合并回目标分支(保留用户数据:config.json/admin.json/icon.*/music/等均 exclude);支持 GitHub 代理({url}占位符或前缀拼接) - Meting API 连通性测试(
server/core/music-api-tester.ts):保存配置前并发测试所有启用歌单(并发 3),返回每个歌单的状态码 / 曲目数 / 错误信息与汇总结果,8 秒超时 - 本地音乐文件上传(
upload-handler.ts+LocalTrackEditor.vue):支持音频 / 封面 / 歌词 LRC 文件上传到public/music/{trackId}/,50MB 限制,删除曲目时联动删除已上传文件;路径白名单(仅允许public/前缀)+new URL()规范化防路径穿越 - 站点图标上传(
SiteSettingsEditor.vue):支持 SVG / PNG / JPEG / WebP 图标上传到public/icon.{ext},本地开发模式用 DataURL 预览 - 远程歌单可视化编辑(
PlaylistEditor.vue):网易云 / QQ 音乐歌单的增删改 + 启用禁用,内联编辑(Dropdown 选服务器 + BaseInput 输入 ID),删除二次确认 - 统计与分析配置(
AnalyticsSettingsEditor.vue+src/utils/site-integrations.ts):Umami(自建 script URL + Website ID)、Google Analytics 4(Measurement ID)、Google 站点验证(Search Console)、自定义 CSS / 自定义 JS 注入,运行时动态 upsert / remove 元素 - 共享组件库(
src/components/):新增Collapse(grid-template-rows: 0fr→1fr动画)、BaseInput(inheritAttrs+$attrs透传)、BaseTextarea、Toast(Teleport + Transition)、Dropdown(outside-click 封装)、ConfirmModal、ToggleSwitch、SettingRange(自动计算进度变量),管理后台与播放器统一复用 - 路由系统:引入
vue-router,AppShell.vue用<router-view>+<Transition mode="out-in">包裹;/为播放器,/admin懒加载管理后台;路由级淡入淡出 - 全局错误处理:
main.ts添加app.config.errorHandler;新增ErrorBoundary.vue用onErrorCaptured包裹router-view,渲染出错时显示降级 UI + 重试按钮,避免白屏 - 服务器端状态页(
server/core/status-pages.ts):环境变量未就绪 / 管理后台禁用时由后端直接返回完整 HTML(内联深色玻璃拟态样式 +escapeHtml防 XSS + 具体密钥生成命令),前端通过 Content-Type 检测后document.write接管页面 - 浏览器能力检测模块(
src/utils/browser.ts):集中管理 Safari 等浏览器兼容性差异——Fullscreen API(标准 + webkit 前缀 + iOS 不支持检测)、Document PiP、AudioContext(webkit 前缀)、Web Animations、iOS 设备检测(含 iPadOS 13+ 桌面 UA)、Safari 浏览器检测、iOS PWA 安装引导 useFullscreen重写:改用browser.ts的能力检测,iOS Safari 不支持 Fullscreen API 时引导用户使用浏览器菜单全屏,而非静默失败- Meting API 鉴权:
services/music.ts的fetchPlaylist支持apiToken参数(查询字符串token) robots.txt:新增public/robots.txt,Disallow: /admin/和/api/- Cloudflare
_routes.json:限制只有/api/*走 Pages Functions,静态资源不产生 Functions 调用计费 - 动态 preconnect:播放器使用构建期公开配置中的
apiEndpoint动态创建<link rel="preconnect">,移除index.html中写死的域名 - 多平台 API 路由:Vercel(
api/[...path].ts)、Cloudflare(functions/api/[[path]].ts)、Netlify(netlify/functions/api.ts+netlify.tomlredirect)三平台统一入口 - 服务端独立 tsconfig:
tsconfig.server.json用于server/目录类型检查 - 开发体验:
package.json新增dev:admin(wrangler pages dev :8788)与dev:full(并行跑前端 + 后端);vite.config.ts新增/api代理到 :8788,测试纳入server/tests/**/*.test.ts - 服务端单元测试:新增
admin-auth、admin-config-validation、crypto、router-security、update-handler、upload-handler共 6 个测试文件 update-from-upstream.ymlworkflow:fork 仓库一键同步上游最新 release,支持 GitHub 代理,rsync 保留用户数据- 主分支验证与发布自动化:main 分支 workflow 负责测试、类型检查、lint、构建、打 tag;仅稳定版自动创建 GitHub Release(
--generate-notes --latest) agent.md规范增补:§3.1 模板内联事件禁止多语句、§3.3.1 共享组件库约束表、§4.2 路由 / 浮层过渡规范、§4.4 accent 半透明色color-mix派生规范
Changed
- 环境变量模型:生产环境需
GH_TOKEN、GH_REPO、CONFIG_ENCRYPTION_KEY三项;GH_BRANCH可选(默认 main)、GITHUB_PROXY可选;密码通过/setup设置(不再使用ADMIN_PASSWORD),Cookie 签名密钥从CONFIG_ENCRYPTION_KEY派生(不再使用ADMIN_SECRET) - 公开配置构建期硬编译:播放器首屏不再请求后端配置 API,构建时从
public/config.json生成公开配置并写入前端 bundle;远程歌单仍按apiEndpoint与歌单 ID 运行时请求 Meting API config-handler默认配置:config.json不存在时返回默认空配置(不再 404),首次部署开箱即用;读取失败、明文配置或解密失败时管理后台会明确报错,避免静默覆盖已有配置src/config/music.ts清理为纯净默认配置:空 apiEndpoint、空数组,不再含个人数据;删除public/config.json(由后台初始化创建)MusicConfig类型扩展:新增siteIcon?、umami?、googleAnalytics?、googleSiteVerification?、customCss?、customJs?字段,与服务端ConfigPayload与validateConfig校验对齐App.vue重构:启动时读取构建期公开配置 →applySiteBrand(动态设置 title + favicon)+applySiteIntegrations(注入统计 / 自定义 CSS/JS);顶栏新增分享按钮;公开配置驱动站点名称显示main.ts重构:挂载AppShell(而非直接App),注册 router 与全局 errorHandler- CSP 四处统一收紧:
index.html/vercel.json/netlify.toml/wrangler.toml统一为script-src 'self' 'unsafe-inline' https:、connect-src 'self' https:,不再写死api.music.abloom.site、googletagmanager.com、cloud.umami.is等具体域名 - 管理后台 accent 颜色派生:
src/admin/下所有rgba(var(--accent-rgb), α)替换为color-mix(in srgb, var(--accent), transparent X%),确保用户自定义--accent时选中态 / 徽章跟随变化 - CORS 头合规:
router.tsOPTIONS 预检的Access-Control-Allow-Origin从*改为反射请求Origin头,添加Vary: Origin(允许 credentials 时 origin 不能为通配符) wrangler.toml:移除[build]段(构建命令改在 Cloudflare Dashboard 配置);修复Permissions-Policy中camera()→camera=()语法错误vercel.json:SPA rewrite 排除/api/*(source: "/((?!api/).*)")netlify.toml:新增functions(esbuild bundler)与/api/*→/.netlify/functions/api/:splatredirectpnpm-workspace.yaml:allowBuilds新增 esbuild / sharp / workerdpackage.json:版本号0.1.1→0.2.0-rc1;新增依赖vue-router、concurrently、npm-run-all2、wrangler
Fixed
usePreloadPool事件监听器泄漏:PreloadSlot新增cleanup字段,clearSlot与超时回调统一调用cleanup()移除canplay/loadeddata/error监听器useLyricsWindow竞态条件:toggleLyricsWindow添加isToggling守卫,快速重复调用时第二次直接返回;waitForDocumentReady的 interval 改为可清理,teardownWindow会清理它useThemeAccentrAF 未清理:onBeforeUnmount取消颜色过渡动画帧useDrawerSheettimer 泄漏:dismissAnimated的setTimeout与watch(active)的双层requestAnimationFrame存储 ID,onBeforeUnmount清理useFocusTrapsetTimeout 未清理:deactivateTrap的setTimeout存储到focusTimer,卸载时清理useBeatAnalyser卸载后多余 rAF:添加isUnmounted标志,await audioContext.resume()后检查,已卸载则不启动新 rAFupload-handler路径穿越:public/../server/core/router.ts可绕过startsWith('public/')检查,改用new URL()规范化路径后校验,拒绝空字节DashboardView模板类型错误:@notify事件处理器在vue-tsc -b下$event[1]推断为string而非'success' | 'error',改为显式参数类型注解- 模板内联事件多语句编译错误:
@click="a(); b()"会被 Prettier 拆分号导致 Vue 编译器报Unexpected token, expected ",",改用方法引用或包装函数 - 管理后台首次访问闪现 LoginView:
checking初始值从false改为true,首帧显示"正在验证"而非 LoginView - 浮层 fixed 定位被困在父容器内:
ConfirmModal/Toast使用<Teleport to="body">避免被backdrop-filter/transform父容器建立包含块 ADMIN_DISABLED拦截管理 API:禁用管理后台时仅状态探针可访问,播放器公开配置已改为构建期硬编译,不再需要运行时配置接口
v0.1.1
Added
- 五频段均衡器(Equalizer):新增
src/utils/equalizer.ts(DOM-free 纯模块)+src/composables/useEqualizer.ts,采用 Web AudioBiquadFilterNode串联(lowshelf→peaking×3→highshelf),覆盖 60Hz / 250Hz / 1kHz / 4kHz / 12kHz 五个常用频段,增益范围 ±12 dB - 6 个内置预设(平坦 / 流行 / 摇滚 / 爵士 / 人声 / 低音增强)+ 1 个"自定义"动态状态指示器:用户拖动任意频段滑块后,预设状态自动切换为"自定义"(
detectPreset兜底),也可以直接点击"自定义"按钮显式停留在自定义模式 - EQ 状态完整接入
PlayerSettings持久化与migrateSettings嵌套合并;sanitizeEqualizer对历史数据做容错(无字段时回落默认值、非法预设回落flat、频段值钳制到 ±12dB) - 设置抽屉新增"音效"section:开关 + 6 预设按钮(末尾"自定义"状态斜体区分)+ 5 频段滑块,均衡器关闭时所有按钮统一灰色(包括激活的预设),开启时 active 预设带 accent 高亮
- 频谱可视化重新设计:四柱采用互质步长 + 不同起点的交叉跳跃采样(
step∈ {7, 11, 13, 17}),每柱在频域上做伪随机扫描,任意两柱重叠 bin 极少;配合weightDecay频段偏好系数(柱 1 偏低频 0.92,柱 4 偏高频 1.06)与每柱独立的不对称时间常数(riseSpeeds/fallSpeeds),四柱在时间维度上完全脱钩,任何瞬间高度组合明显不同 equalizer.test.ts新增 26 个纯函数测试,覆盖预设 / 增益钳制 / 数据消毒 / 兜底逻辑
Changed
TrackList播放中频谱柱从"底基线对齐"(align-items: flex-end)改为"中线对称扩展"(align-items: center),柱子上下端均圆角(border-radius: 999px),视觉风格对齐 Apple MusicuseBeatAnalyser的 EQ filter chain 在 graph 首次构建时插入到MediaElementSource与Analyser之间,通过onEqFiltersReady回调暴露给useEqualizer(createMediaElementSource一次性约束下的唯一可行布线方式)- 设置抽屉中"音量""定时关闭""歌词字号""背景模糊""背景饱和度""节奏亮度"等 label 文字补回
<strong>包裹,与"平滑切歌""动态封面背景"等 toggle 项字号 / 字重 / 颜色完全统一(共用.setting-group strong的0.8rem / 560 / #fff) useFocusTrap改造为nextTick异步流程并加入pendingActivation守卫:容器节点 / 可聚焦元素首次渲染晚于激活时,会在nextTick/watch(containerRef)/onMounted三个时机重试,杜绝"抽屉打开但焦点没进入"的边界场景usePwaInstall的install()改为try / finally结构,保证prompt()或userChoice抛错时也能清空deferredPrompt引用,避免 stale 状态阻塞下一次弹窗- 更新检查统一收敛到后台
server/core/update-handler.ts:GitHub Releases / Tags API 请求统一加 8 秒超时,严格 SemVer 排序并按后台配置决定是否接收预发布版本,避免前台和后台两套逻辑分叉 pnpm-workspace.yaml新增overrides锁定传递依赖ini >=1.3.6/undici >=7.28.0,堵住 pnpm 锁文件中的若干已知安全告警agent.md §3.1修正:格式约定从"写分号"改为"不写分号"(与项目 prettier 实际配置semi: false一致)
Fixed
- 修复更新链路取消与失败反馈:后台关于页卸载时会取消检查/状态轮询,网络失败不再写入前台长时间负缓存,失败原因统一显示在后台状态卡片中
- 修复
usePwaInstall接受 / 拒绝两种userChoice.outcome路径下deferredPrompt引用残留导致的"二次安装"失效问题 - 修复
useFocusTrap.deactivateTrap中对triggerRef.value?.focus()的延迟读取:setTimeout触发时triggerRef.value可能已经被新一轮 activate 覆盖,改为闭包捕获trigger局部变量
Performance
- 频谱采样从"连续频段区间累加 + 单一峰值归一化"重构为"交叉跳跃采样 + 每柱独立 sigmoid 响应曲线 + 不对称时间常数",消除"四柱齐刷刷顶满或归零"现象,在不同曲风(电子 / 流行 / 摇滚 / 钢琴)下视觉差异始终保持
v0.1.1-rc.3
Added
- 主题色提取走 Web Worker:新增
src/workers/theme-extractor.worker.ts+src/utils/theme-core.ts(DOM-free 纯算法),通过OffscreenCanvas+transferable ImageBitmap零复制传输,主线程仅负责 DOM I/O,大幅降低切歌瞬间的主线程长任务 - Worker 路径自带 1.5s 超时与
onerror兜底,失败时自动回退到主线程同步实现,行为与之前一致 - 主封面"已就绪"二态建模:
useCoverCache新增mainCoverReadyTrackId与markMainCoverReady/resetMainCover,与曲库级loadedCovers严格分离,切歌时主封面重新走 fade-in 但抽屉小封面不闪现 - 高频 CSS 变量直写通道:
useBeatAnalyser新增getBeatTargets选项,--beat-level每帧通过el.style.setProperty写入artwork-background/background-overlay两个目标节点,跳过根:style与 Vue reactivity --accent/--accent-soft注册为@property颜色类型,在支持的浏览器(Chrome 85+ / Safari 16.4+)上由 GPU 原生过渡,JS 动画作为降级TrackList搜索输入接入 180ms 防抖(debouncedQuery),输入框值与外部过滤解耦,长曲库下输入手感显著平滑- 节拍可视化挂接
visibilitychange监听,标签页进入后台时自动暂停 RAF 循环、回到前台时无缝恢复 PlayerControls时间格式支持 ≥1 小时的曲目(H:MM:SS),原先只渲染M:SSagent.md增补 §3.4 高频 DOM 直写接口、§3.7 DOM-free 共享纯模块、§4.1 CSS 变量分类约束、§5 Worker / 合成层 / 二态缓存等专项规范
Changed
useAudioPlayer切歌路径重构:取消串行 fade-out 等待,改为 microtask 推迟syncMediaSession/ 旧 audio 收尾 / 邻曲预加载,同步阶段只做 audio 引用切换 + reactivity 写入,连续点击下一曲的感知延迟显著降低- 切歌时仅在
currentTime > 0.01时才重置时间轴,避免对预加载 slot 做无效浏览器调用 useBeatAnalyser改用模块级共享AudioContext,MediaElementAudioSourceNode统一缓存到WeakMap,从根本上消除组件重建后对同一<audio>节点重复createMediaElementSource抛出的InvalidStateError--beat-level写入加入lastValue去重与isConnected守卫,跳过相同帧值与已脱离 DOM 的目标节点,避免样式风暴will-change不再常驻挂在.artwork-background上,改为通过.beat-active条件 class 仅在播放期间启用,暂停 / 空闲时让浏览器回收合成层- 椭圆封面氛围光去掉
translate3d(-12%, -12%, 0),只保留scale,减少一层不必要的 GPU 合成层位移 - 主封面
<img>加上decoding="async"+loading="eager",抽屉小封面加上loading="lazy"+decoding="async";主封面@load回调现先await image.decode()再标记 ready,避免视觉上半解码逐行渲染 extractThemeColor改为 async API,调用方统一await(原同步版本已迁出到 worker / core 中)TrackList滚动追踪 watcher 用[tracks.length, tracks[0]?.id]替代tracks.map(id).join('|'),消除每次曲库变更的 O(n) 字符串拼接package.json补回predev钩子,pnpm dev启动前自动跑generate写入应用版本号agent.md全面修订:补充 Web Worker 目录约定、CSS 变量写入双通道规范、远程 IO 与预加载超时分类、二态缓存建模、will-change使用守则、Node 22 + pnpm 11 运行环境锁定等
Fixed
- 修复
useAudioPlayer切歌路径中transitionInProgress解锁时机错误:之前在store.selectTrack之前就被置 false,后续watch(currentTrack)可能误触发重新加载;现在改为先更新 store、再解锁,杜绝循环加载 - 修复
useLyricsWindow:歌词小窗被用户关闭后lyricsWindow引用未清空,下次开窗会复用已关闭对象 - 修复
LyricsPanel:目标行 DOM 尚未渲染时直接onComplete会跳过滚动动画;现在改为 RAF 重试直到节点出现或组件卸载 - 修复
services/lyrics.ts中signal.removeEventListener可能误删其他请求监听器的隐患:把forwardAbort引用绑到 cache entry 上精准移除 - 修复
public/sw.jsRange 请求判定:HTTP header 名按 RFC 9110 大小写不敏感,改用headers.get('range')+trim().length > 0判空,避免大写或带空白的 Range 头被错误命中缓存层
Performance
- 切歌时主线程长任务显著缩短:主题色提取从主线程同步阻塞 → Worker 异步 + transferable;
--beat-level写入路径从根:style触发整树(~233 节点)样式重算 → 直写到 2 个目标节点 - 节拍 RAF 循环在标签页隐藏时自动停摆,后台 CPU 占用降至 0
TrackList搜索从同步过滤 + O(n) 字符串拼接 → 防抖 + O(1) 变更标记,长曲库输入流畅度提升
v0.1.1-rc.2
Changed
- 移动端禁用 Safari 双击缩放,并修复 iOS 输入框聚焦时页面自动放大的问题
- 移动端保留封面氛围光,改用椭圆裁切与径向遮罩降低 iOS Safari 方形 blur 合成瑕疵
Fixed
- 修复 rc 构建会把 GitHub 最新正式版
v0.1.0误判为可更新版本的问题