Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9523f2bb83 |
@@ -8,6 +8,20 @@ This project follows the repository versioning rule:
|
||||
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
|
||||
- `bugfix` -> `+0.0.1`
|
||||
|
||||
## [0.76.8] — 2026-10-02
|
||||
|
||||
Released: 2026-10-02
|
||||
|
||||
### Highlights
|
||||
- 修复展开新闻面板时后台 ticker / realtime 更新导致卡片重建和 hover 抖动的问题。
|
||||
|
||||
### Added / Fixed / Improved
|
||||
- 面板浏览期间冻结卡片 DOM,后台更新只写入最新 payload。
|
||||
- 关闭面板时一次性应用待处理新闻,保持滚动位置和 hover 稳定。
|
||||
- 展开面板时暂停隐藏 ticker,手动刷新仍可立即更新面板内容。
|
||||
|
||||
---
|
||||
|
||||
## [0.76.7] — 2026-10-02
|
||||
|
||||
Released: 2026-10-02
|
||||
|
||||
@@ -444,7 +444,7 @@ The current state machine keeps data, ticker visibility, panel lifecycle and sel
|
||||
|
||||
- `getNewsBreakingState()` derives the data state from `items` and `cruise_items`, ignores expired stories, and ranks `critical > breaking > watch`. The expiry timestamp only schedules the next recomputation.
|
||||
- A ticker viewport watcher runs every 120 ms and chooses the item with the greatest viewport overlap, breaking ties by distance to the viewport center. Only when that current item has a non-`none` level does the ticker switch its horizontal rules to `has-breaking-*`; it returns to neutral as soon as the current item is not breaking.
|
||||
- The panel lifecycle is `closed → opening → open → closing → closed`. Opening and closing control morphing, visibility and ticker hiding; they do not mutate the breaking data.
|
||||
- The panel lifecycle is `closed → opening → open → closing → closed`. Opening and closing control morphing, visibility and ticker hiding; they do not mutate the breaking data. During `opening/open`, the current card DOM stays stable while incoming payloads are deferred and applied once on close, so realtime updates cannot rebuild a hovered card.
|
||||
- `selectedCruiseStoryId` is independent of breaking severity. Selection, focus, hover and close only change the selected story and reading position; close also clears the selection and filter popover.
|
||||
- The expanded panel always keeps its blue glass surface. Breaking colors remain on the matching story cards, so opening the panel never turns the entire glass panel red; the ticker can still show a red signal when the currently scrolling story is breaking.
|
||||
|
||||
|
||||
@@ -577,7 +577,7 @@ Earth 原有 Roboto Condensed 字体已随 `assets/fonts/` 本地分发,许可
|
||||
|
||||
- 数据态由 `getNewsBreakingState()` 从 `items` 与 `cruise_items` 合并计算,只接受未过期项目,并按 `critical > breaking > watch` 排序;到期时间只负责安排下一次重新计算。
|
||||
- ticker 的显示态由滚动视口 watcher 每 120ms 取视口内重叠最多、距离中心最近的当前项目。只有当前项目的 breaking 等级非 `none` 时,ticker 的上下细线才切换为对应的 `has-breaking-*` 颜色;当前项目离开后立即恢复中性。
|
||||
- 面板生命周期 `panelPhase` 只有 `closed → opening → open → closing → closed`。打开/关闭只控制形变、可见性和 ticker 的隐藏,不改变 breaking 数据态。
|
||||
- 面板生命周期 `panelPhase` 只有 `closed → opening → open → closing → closed`。打开/关闭只控制形变、可见性和 ticker 的隐藏,不改变 breaking 数据态;进入 `opening/open` 后保留当前卡片 DOM,后台 payload 先暂存,关闭时再一次性应用,避免 hover 卡片被实时更新重建。
|
||||
- `selectedCruiseStoryId` 独立于 breaking 等级。选择、聚焦、悬停和关闭只更新选中项与阅读位置;关闭时清空选中项及筛选弹层。
|
||||
- 展开面板始终使用蓝色玻璃底。breaking 只在对应新闻卡片上使用等级颜色,因此点开面板不会把整块玻璃染成红色,之后滚动到 breaking 新闻时仍可让 ticker 显示红色提示。
|
||||
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
## Current Version
|
||||
|
||||
- `main` 当前主线历史推导到:`0.16.5`
|
||||
- `dev` 当前开发分支历史推导到:`0.76.7`
|
||||
- `dev` 当前开发分支历史推导到:`0.76.8`
|
||||
|
||||
## Timeline
|
||||
|
||||
| Version | Type | Branch | Commit | Summary |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `0.76.8` | bugfix | `dev` | `v0.76.8` | 展开新闻面板时冻结卡片 DOM,隔离 ticker/realtime 更新并修复 hover 抖动 |
|
||||
| `0.76.7` | bugfix | `dev` | `v0.76.7` | 统一 breaking 卡片 hover 行为并通过卡片等级变量注入颜色 tint |
|
||||
| `0.76.6` | bugfix | `dev` | `v0.76.6` | breaking ticker 全域扫光闪烁,新闻列表首卡增加顶部呼吸空间 |
|
||||
| `0.76.5` | bugfix | `dev` | `v0.76.5` | breaking 玻璃反射式警灯脉冲、等级颜色统一及新闻卡片内容高度修复 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.76.7",
|
||||
"version": "0.76.8",
|
||||
"private": true,
|
||||
"packageManager": "bun@1",
|
||||
"dependencies": {
|
||||
|
||||
@@ -61,6 +61,7 @@ let selectedCruiseStoryId = null;
|
||||
let morphTimer = null;
|
||||
let morphFrame = null;
|
||||
let panelPhase = "closed";
|
||||
let deferredPanelPayload = null;
|
||||
let breakingExpiryTimer = null;
|
||||
let tickerBreakingTimer = null;
|
||||
let newsRealtimeSocket = null;
|
||||
@@ -215,6 +216,8 @@ function finishHudOpen(hud) {
|
||||
}
|
||||
|
||||
function finishHudClose(hud, ticker, restoreRect = null) {
|
||||
const pendingPayload = deferredPanelPayload;
|
||||
deferredPanelPayload = null;
|
||||
panelPhase = "closed";
|
||||
hud.classList.add("hud-panel-hidden");
|
||||
hud.classList.remove("is-morphing");
|
||||
@@ -230,6 +233,14 @@ function finishHudClose(hud, ticker, restoreRect = null) {
|
||||
hud.style.transform = "";
|
||||
}
|
||||
ticker?.classList.remove("is-hidden");
|
||||
setTickerPlayback(true);
|
||||
if (pendingPayload) renderPayload(pendingPayload, { forcePanelRender: true });
|
||||
}
|
||||
|
||||
function setTickerPlayback(running) {
|
||||
const { tickerTrack } = getElements();
|
||||
if (!(tickerTrack instanceof HTMLElement)) return;
|
||||
tickerTrack.style.animationPlayState = running ? "" : "paused";
|
||||
}
|
||||
|
||||
function setNewsHudOpen(open, { highlightId = null } = {}) {
|
||||
@@ -258,6 +269,7 @@ function setNewsHudOpen(open, { highlightId = null } = {}) {
|
||||
hud.classList.add("is-morphing");
|
||||
setHudRectWithoutTransition(hud, tickerRect, 1);
|
||||
ticker?.classList.add("is-hidden");
|
||||
setTickerPlayback(false);
|
||||
morphFrame = requestAnimationFrame(() => {
|
||||
morphFrame = null;
|
||||
setHudRect(hud, targetRect, 1);
|
||||
@@ -814,7 +826,7 @@ function renderEmptyState(message) {
|
||||
renderTicker({ items: [], focus: payload?.focus || { region: "global" } });
|
||||
}
|
||||
|
||||
function renderPayload(nextPayload) {
|
||||
function renderPayload(nextPayload, { forcePanelRender = false } = {}) {
|
||||
payload = nextPayload;
|
||||
reconcileNewsSourceFilters(nextPayload);
|
||||
const {
|
||||
@@ -847,6 +859,12 @@ function renderPayload(nextPayload) {
|
||||
syncFilterSummaries(nextPayload);
|
||||
applyNewsBreakingShellState(nextPayload);
|
||||
|
||||
if (!forcePanelRender && (panelPhase === "opening" || panelPhase === "open")) {
|
||||
deferredPanelPayload = nextPayload;
|
||||
return;
|
||||
}
|
||||
if (forcePanelRender) deferredPanelPayload = null;
|
||||
|
||||
if (!board || !status || !focusLabel || !focusCoords || !sourceCount || !regionChip) {
|
||||
if (document.body.classList.contains("layout-mode-mobile")) {
|
||||
// Mobile page omits the region chip shell, but the rest of the page is still renderable.
|
||||
@@ -1103,7 +1121,7 @@ async function fetchNews(lat, lon, context = {}) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function refreshNews(lat, lon, { silent = false } = {}) {
|
||||
async function refreshNews(lat, lon, { silent = false, forcePanelRender = false } = {}) {
|
||||
const targetRegion = inferRegion(lat, lon);
|
||||
const categorySignature = getNewsCategorySignature();
|
||||
const sourceSignature = getNewsSourceSignatureForFetch(lat, lon);
|
||||
@@ -1129,7 +1147,7 @@ async function refreshNews(lat, lon, { silent = false } = {}) {
|
||||
if (requestToken !== activeRefreshToken) {
|
||||
return nextPayload;
|
||||
}
|
||||
renderPayload(nextPayload);
|
||||
renderPayload(nextPayload, { forcePanelRender });
|
||||
lastFetchAt = Date.now();
|
||||
lastCategorySignature = getNewsCategorySignature();
|
||||
lastSourceSignature = getNewsSourceSignature(nextPayload);
|
||||
@@ -1369,7 +1387,7 @@ export function initNewsPanel() {
|
||||
const refreshBtn = document.getElementById(id);
|
||||
refreshBtn?.addEventListener("click", async () => {
|
||||
try {
|
||||
await refreshNews(lastFocus?.lat, lastFocus?.lon);
|
||||
await refreshNews(lastFocus?.lat, lastFocus?.lon, { forcePanelRender: true });
|
||||
showStatusMessage(earthMessage("status.newsRefresh", { ok: true }), "info");
|
||||
} catch {
|
||||
showStatusMessage(earthMessage("status.newsRefresh", { ok: false }), "error");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "planet"
|
||||
version = "0.76.7"
|
||||
version = "0.76.8"
|
||||
description = "智能星球计划 - 态势感知系统"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
|
||||
Reference in New Issue
Block a user