fix(chart): span natal and moon planet tables across the row
VedicLayout now owns the third grid item, so a wrapper around the table cannot drop the natal or Moon table into the left column. Desktop grid-column targets that row. BUG-1421. Self-test: tsc exit 0, lint 0 errors, 49 targeted tests, next build with / static. Co-Authored-By: Grok (agent loop)
This commit is contained in:
co-authored by
Grok (agent loop)
parent
d72adb5754
commit
fc2283693d
@@ -1,5 +1,10 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-10-23 — 星盘页本命和月亮的行星表重新占满整行
|
||||
|
||||
- 宽屏下,本命和月亮的行星表回到参数卡旁边那一整行,宿主、pada、状态不用横拖就能看见。分盘、Bhava、行运仍是整行。
|
||||
- Skill 版本不变(BUG-1421)。
|
||||
|
||||
## 2026-10-22 — 星盘大运右栏可以切到 Yogini
|
||||
|
||||
- 大运页左栏仍是 Vimshottari。右栏默认 Chara,可以切到 Yogini。点开当前大运能看到小运。Yogini 的小运下面写「小运未经外部软件核对。」
|
||||
|
||||
@@ -19253,3 +19253,19 @@
|
||||
- 相关记录:无
|
||||
- 复发自:无
|
||||
- 修复版本:本提交
|
||||
|
||||
## BUG-1421 | 星盘页本命和月亮的行星表被挤进左栏
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-10-22
|
||||
- 最近更新:2026-10-23
|
||||
- 影响面:星盘页桌面宽度下的本命表和月亮盘表
|
||||
- 用户现象:宽屏下选「本命」或「月亮」时,行星表只占左栏。宿主、pada、状态三列要横拖才看得到,右栏在参数卡下面空着。分盘、Bhava、行运的表正常。
|
||||
- 触发条件:桌面宽度(≥1024px)打开星盘页的本命或月亮。来自 #87 给这两张表包了一层符号说明。
|
||||
- 根因:跨栏按类名而非结构。桌面规则只认表格外层的两个类名。本命和月亮的直接子项变成了符号说明那一层,规则匹配不上,表就排进左栏。
|
||||
- 修复:网格自己用固定的整行容器装表格,跨栏只认这个容器。表格内容、列数、列宽不变。
|
||||
- 验证:`frontend/tests/chart-table-span-20261023.test.tsx`。五种盘型的网格都是三个直接子项,第三个是整行容器;桌面样式只让这个容器跨栏。
|
||||
- 防复发:上面两条断言。再往表外包一层,结构断言会失败;跨栏不再跟着表格类名走。
|
||||
- 相关记录:BUG-1083(窄屏仍只能在表自己的盒子里横滚)。issue #102。回归来源 #87。
|
||||
- 复发自:无
|
||||
- 修复版本:`codex/chart-table-span-20261023`
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# PROGRESS · 星盘页行星表整行(2026-10-23)
|
||||
|
||||
Issue #102。BUG-1421。
|
||||
|
||||
## 基线
|
||||
|
||||
- 开工时 `origin/staging` = `d72adb5754eab4d66ac3a47c4edcb12704e29815`(任务书写的 `9049b5e99` 已过期)。
|
||||
- 本文件最大编号是 BUG-1420,本单用 BUG-1421。
|
||||
|
||||
## 做了什么
|
||||
|
||||
- 星盘页网格的第三格改成固定的整行容器。本命、月亮、分盘、Bhava、行运的表都放在里面。
|
||||
- 桌面跨栏只认这个容器。表格自己的类名不再负责跨栏。
|
||||
- 容器可以缩到比内容窄,窄屏时表仍在自己的框里横滑。
|
||||
- 表格的字、列、列宽没改。符号说明的气泡还在原来的位置。
|
||||
|
||||
## 自测
|
||||
|
||||
| 检查 | 命令 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 类型 | `frontend\node_modules\.bin\tsc --noEmit -p frontend` | 退出码 0 |
|
||||
| lint | `frontend` 下 `npm run lint` | 退出码 0。0 error,122 条既有 warning,本轮不改 |
|
||||
| 定向测试 | `node node_modules/tsx/dist/cli.mjs --test` 跑新结构测试,加上 `chart-page-view.test.tsx`、`chart-varga-table.test.tsx`、`chart-page-mobile-width-20260929.test.ts` | 49 条通过,0 失败 |
|
||||
| 构建 | `frontend` 下 `npm run build` | 退出码 0。路由表 `/` 为 `○ Static`。预渲染 `index.html` 引用 33 个 JS、3 个 CSS,gzip-9 合计 646,234 字节(JS 575,154,CSS 71,080)。没有用改动前的代码再构建一次,所以不写增减百分比 |
|
||||
|
||||
未跑前端全量 `npm test`(验收时跑)。没有登录,本机浏览器截图没做,留给 `docs/testing/chart-table-span-20261023.md`。
|
||||
|
||||
## 偏离
|
||||
|
||||
- 无。编号说明:本分支的 BUG-1421 与尚未合并的 #95 分支各自占用了同一个号。若 #95 先进入 staging,验收时需要给本条改号。
|
||||
@@ -38,7 +38,7 @@
|
||||
| 任务书 | 进度 | 主题 | 状态 | 落点 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `TASK-rectification-explicit-open-20261023.md` | — | **明说要校正只回一句话(issue #103)**:找会话直接读一张对服务角色撤权的表、必然失败被吞(真正进行中的也续不上);`candidate_accepted` 被算作进行中挡住入口和提议卡;兜底只给一句话。产品选 C;只改入口口径、经 RPC 找会话、删兜底,不改共享状态集合、不动表 | 待执行(循环) | 分支 `codex/rectification-explicit-open-20261023` |
|
||||
| `TASK-chart-table-span-20261023.md` | — | **星盘页本命 / 月亮行星表挤进左栏(issue #102)**:#87 给表包了 `ChartSymbolHelp` 后跨栏规则失配;根因修法是 `VedicLayout` 用固定整行容器承载表格,跨栏只认容器;加结构断言防复发 | 待执行(循环) | 分支 `codex/chart-table-span-20261023` |
|
||||
| `TASK-chart-table-span-20261023.md` | `PROGRESS-chart-table-span-20261023.md` | **星盘页本命 / 月亮行星表挤进左栏(issue #102)**:#87 给表包了 `ChartSymbolHelp` 后跨栏规则失配;根因修法是 `VedicLayout` 用固定整行容器承载表格,跨栏只认容器;加结构断言防复发。BUG-1421 | 待验收 | 分支 `codex/chart-table-span-20261023` |
|
||||
| `TASK-ui-kit-migration-20261023.md` | — | **界面统一换成组件库**:shadcn base-nova(补 `shadcn/tailwind.css`)· Lucide · Motion · react-resizable-panels · dnd-kit · Zustand;lint 拦手写控件(白名单只减不增);对话旁星盘面板;会话、档案拖动排序(动表);长相不变。产品 2026-10-23 拍板 D1~D8;8 个 issue(K0~K7)串行,取代全站动效 #98 #100 #101 | 待领取(循环) | 原型 https://claude.ai/artifact/PGnWsjHhu38KJseEfEMNWG |
|
||||
| `TASK-onboarding-journey-20261022.md` | — | **新用户启程**:建档加「先看谁的盘」(自己 / 帮别人看,本人资料可后补)、人物关系字段、首问免费(服务端原子)、起步三问、启程清单(七项从数据推导,收费项标价,老用户可见)、八条一次性提示(含星盘四页签)、启程流程专用动效。产品 2026-10-22 拍板 D1~D6;动表经产品同意进循环,拆四个 issue 串行。BUG-1420 起 | 待领取(循环) | 原型 https://claude.ai/artifact/PcTwMVb9RD611R3qStxv3U |
|
||||
| `TASK-site-motion-20261022.md` | — | **全站动效**:换页按侧栏方向滑入、选中块跟随、页签、盘型切换星体移宫、换人、翻日期、列表补位、弹窗从触发处长出、抽屉、顶部提示、点数滚动、中英交叉淡入;重写 DESIGN §6(推翻 180 ms 上限与 Spatial Instant),不引动画库,先验证 View Transitions。产品 2026-10-22 拍板;三个 issue,排在启程(三)之后、启程(四)之前;**2026-10-23 被 ui-kit-migration 取代** | 已取代 | 原型 https://claude.ai/artifact/7u9goivnmTNSGTNWNeVMQd |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# 星盘页行星表整行(2026-10-23)
|
||||
|
||||
登录后打开星盘页。不要用真实出生资料做记录,用站内已有的示例盘即可。
|
||||
|
||||
1. 把窗口拉到大约 1280 宽。依次看「本命」「月亮」「D9」「Bhava」「行运」。每张表都占满盘和参数卡下面的整行,宿主、pada、状态不用横拖就能看见。
|
||||
2. 把窗口收到大约 390 宽(或手机)。页面本身不左右滚动。表如果放不下,只在表自己的框里横滑。
|
||||
+1
-1
@@ -1136,7 +1136,7 @@ D40 / D45 / D60 的表格上方,出生时间不是「已校正」时多一句
|
||||
|
||||
**不再按断点复制参数。** 原本同一组参数有三种画法:中宫叠一张 `.chart-page-center-card`(手机两行、桌面六行),盘下方再来一张 `.chart-page-params-below` 重复那六行,由 media query 三选一。同样的事实画三遍、靠断点开关,改一处要改三处。键值表让每个字段只出现一次,`.chart-page-center-card` / `-compact` / `-extra` / `.chart-page-params-below` / `.chart-page-boundary-desktop` 五个类连同它们的 767/768 开关块一并删除,由 `chart-page-view.test.tsx` 守着不许回来。
|
||||
|
||||
桌面(≥1024)盘面与参数表左右并排,行星表是跨两列的整行——它的列宽需要完整版心。2026-09-29 起桌面印度盘宽 440px;当晚(BUG-1100,产品「框太丑」)上限从绘图区移到外框 `.chart-page-vedic-stage`(440px + 两侧 `--space-3` 内边距 + 边框),外框贴合星图,不再是一张宽卡片中间放一张窄盘;手机端外框不设上限;西洋桌面绘图区上限 480px。低于此断点的结果盘不收紧,参数区和表格不缩。等待盘与结果盘复用同一桌面列宽与绘图容器,不再使用独立 400px 等待盘上限;印度等待绘图容器以 400/452 比例预留结果图例带,内部空 SVG 仍为 400×400,避免揭幕时外框和说明区纵向跳动,不改共享绘图。只有盘位动(2026-09-30 起为宫位依次点亮,不再整张呼吸),失败后退出,参数表不新增骨架。
|
||||
桌面(≥1024)盘面与参数表左右并排,行星表是跨两列的整行——它的列宽需要完整版心。表格区由 `VedicLayout` 的整行容器承载,不靠表格自身类名跨栏。2026-09-29 起桌面印度盘宽 440px;当晚(BUG-1100,产品「框太丑」)上限从绘图区移到外框 `.chart-page-vedic-stage`(440px + 两侧 `--space-3` 内边距 + 边框),外框贴合星图,不再是一张宽卡片中间放一张窄盘;手机端外框不设上限;西洋桌面绘图区上限 480px。低于此断点的结果盘不收紧,参数区和表格不缩。等待盘与结果盘复用同一桌面列宽与绘图容器,不再使用独立 400px 等待盘上限;印度等待绘图容器以 400/452 比例预留结果图例带,内部空 SVG 仍为 400×400,避免揭幕时外框和说明区纵向跳动,不改共享绘图。只有盘位动(2026-09-30 起为宫位依次点亮,不再整张呼吸),失败后退出,参数表不新增骨架。
|
||||
|
||||
**符号说明(2026-09-29;当晚改为气泡,BUG-1101)。** `/chart` 开启共享 SVG 的可选交互模式:说明是标记旁的小气泡(`.chart-symbol-bubble`,最宽 220px,`--color-canvas` 底 + 细边 + `--shadow-elevated`),默认在标记上方 8px,离外框顶不足 48px 时翻到下方,左右被夹在外框内;气泡不接收指针,不会挡住旁边的标记。电脑:鼠标悬停出现、移开消失,点击与键盘聚焦同样出现。手机:轻点标记出现,触摸产生的离开事件不关它,点盘外任意处或 3 秒后自动消失。盘下不再有「停在或轻点……」提示行和说明行;读屏器仍从一个隐藏的 `role="status"` 读到同一句话;Esc、离开、失焦可关闭,切盘/人物/数据身份变化清理旧说明。行星与其逆行短线共用一个目标;西洋 caption 和命中圆用一致的行星解析,原点击过滤相位及再次点击取消保留。报告、校正、静态导出默认不启用该模式;纯绘图层不持有 hooks,静态 SVG 白名单不扩。
|
||||
|
||||
|
||||
@@ -5808,8 +5808,9 @@ button.synastry-dd-add { border-radius: 0 0 var(--radius-md) var(--radius-md); }
|
||||
@media print { .chart-page-type-sheet { display: none !important; } }
|
||||
.chart-page-precision-hint,
|
||||
.chart-page-table-note { margin: 0 0 var(--space-2); }
|
||||
/* A note + table pair is one grid item, so the table keeps its own scroll box. */
|
||||
.chart-page-varga-table-block { min-width: 0; }
|
||||
/* The table row is one grid item. A note and its table stay in one scroll box. */
|
||||
.chart-page-varga-table-block,
|
||||
.chart-page-vedic-tables { min-width: 0; }
|
||||
.chart-page-varga-table-block > .chart-page-planet-table-wrap + .chart-page-planet-table-wrap { margin-top: var(--space-4); }
|
||||
/* A body the Bhava boundaries move to another house. */
|
||||
.chart-page-planet-table tr.is-shifted > td,
|
||||
@@ -6341,10 +6342,9 @@ button.synastry-dd-add { border-radius: 0 0 var(--radius-md) var(--radius-md); }
|
||||
card with empty sides around a 440px chart. 440 + the card's padding. */
|
||||
.chart-page-vedic-stage { width: 100%; max-width: calc(440px + 2 * var(--space-3) + 2px); }
|
||||
.chart-page-western-wheel { max-width: 480px; }
|
||||
/* Chart and parameters sit side by side; the planet table is a full-width
|
||||
row under both, because its columns need the whole measure. */
|
||||
.chart-page-planet-table-wrap,
|
||||
.chart-page-varga-table-block { grid-column: 1 / -1; }
|
||||
/* Chart and parameters sit side by side. The third grid item is the table
|
||||
row, so a wrapper around a table cannot drop it back into the left column. */
|
||||
.chart-page-vedic-tables { grid-column: 1 / -1; }
|
||||
.chart-page-basics-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
|
||||
@@ -350,7 +350,9 @@ function VedicLayout({ view, chart, ariaLabel, ascendantHouse, children }: {
|
||||
chart can and cannot be read for. */}
|
||||
<p className="chart-page-boundary">{view.vedic.boundary}</p>
|
||||
</div>
|
||||
{children}
|
||||
<div className="chart-page-vedic-tables">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import test from "node:test";
|
||||
|
||||
import { ChartVedicTab } from "../src/components/chart-page/chart-vedic-tab.tsx";
|
||||
import { assembleChartView } from "../src/lib/chart-view-load.ts";
|
||||
import { chartViewOkSchema, type ChartViewOk } from "../src/lib/chart-view-contract.ts";
|
||||
|
||||
Object.assign(globalThis, { React });
|
||||
|
||||
const golden = JSON.parse(readFileSync(new URL("./fixtures/chart-view-golden.json", import.meta.url), "utf8"));
|
||||
const bhavaGolden = JSON.parse(readFileSync(new URL("./fixtures/chart-view-bhava-golden.json", import.meta.url), "utf8"));
|
||||
const transitPacket = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-chart-transit-2026-09-15.golden.json", import.meta.url), "utf8"));
|
||||
const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
|
||||
const profile = {
|
||||
name: "示例",
|
||||
date: "1990-06-15",
|
||||
time: "12:00",
|
||||
placeLabel: "北京",
|
||||
latitude: 39.9042,
|
||||
longitude: 116.4074,
|
||||
timezoneOffset: 8,
|
||||
timezoneId: "Asia/Shanghai",
|
||||
ayanamsa: "raman",
|
||||
birthTimeStatus: "confirmed",
|
||||
};
|
||||
|
||||
function directDivClasses(html: string, parentClass: string): string[] {
|
||||
const needle = `<div class="${parentClass}">`;
|
||||
const start = html.indexOf(needle);
|
||||
assert.ok(start >= 0, parentClass);
|
||||
let index = start + needle.length;
|
||||
let depth = 1;
|
||||
const classes: string[] = [];
|
||||
while (index < html.length && depth > 0) {
|
||||
const nextOpen = html.indexOf("<div", index);
|
||||
const nextClose = html.indexOf("</div>", index);
|
||||
assert.ok(nextClose >= 0);
|
||||
if (nextOpen >= 0 && nextOpen < nextClose) {
|
||||
if (depth === 1) {
|
||||
const tagEnd = html.indexOf(">", nextOpen);
|
||||
const tag = html.slice(nextOpen, tagEnd + 1);
|
||||
classes.push(tag.match(/class="([^"]*)"/)?.[1] ?? "");
|
||||
}
|
||||
depth += 1;
|
||||
index = html.indexOf(">", nextOpen) + 1;
|
||||
} else {
|
||||
depth -= 1;
|
||||
index = nextClose + "</div>".length;
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
async function fullView(): Promise<ChartViewOk> {
|
||||
const result = await assembleChartView({
|
||||
userId: "user-1",
|
||||
profile,
|
||||
asOf: "2026-09-15",
|
||||
layers: ["varga", "bhava", "transit"],
|
||||
postEngine: async (path, _body, cacheScope) => {
|
||||
if (path === "/api/chart" && cacheScope?.startsWith("transit:")) return { status: "ok", payload: transitPacket };
|
||||
if (path === "/api/chart") return { status: "ok", payload: golden.chart };
|
||||
if (path === "/api/varga_full") return { status: "ok", payload: golden.varga_full };
|
||||
if (path === "/api/bhava_chalit") return { status: "ok", payload: bhavaGolden.bhava_chalit };
|
||||
return { status: "http_error", path, elapsedMs: 1, httpStatus: 500 };
|
||||
},
|
||||
});
|
||||
assert.equal(result.body.status, "ok");
|
||||
return chartViewOkSchema.parse(result.body);
|
||||
}
|
||||
|
||||
test("every Vedic chart type puts its table in the layout's full-width row", async () => {
|
||||
const view = await fullView();
|
||||
for (const vargaId of ["D1", "MOON", "D9", "BHAVA", "TRANSIT"]) {
|
||||
const markup = renderToStaticMarkup(React.createElement(ChartVedicTab, { view, vargaId, onSelectVarga: () => {} }));
|
||||
const children = directDivClasses(markup, "chart-page-vedic-layout");
|
||||
assert.deepEqual(children, [
|
||||
"chart-page-vedic-stage",
|
||||
"chart-page-params",
|
||||
"chart-page-vedic-tables",
|
||||
], vargaId);
|
||||
}
|
||||
});
|
||||
|
||||
test("the desktop chart grid spans the table row, not a table's own class", () => {
|
||||
const desktopStart = globalStyles.indexOf("@media (min-width: 1024px) {\n .chart-page-vedic-layout,");
|
||||
assert.ok(desktopStart >= 0);
|
||||
const desktopEnd = globalStyles.indexOf("\n}", desktopStart);
|
||||
const desktop = globalStyles.slice(desktopStart, desktopEnd);
|
||||
assert.match(desktop, /\.chart-page-vedic-tables \{ grid-column: 1 \/ -1; \}/);
|
||||
assert.doesNotMatch(desktop, /\.chart-page-planet-table-wrap[\s\S]*grid-column/);
|
||||
assert.doesNotMatch(desktop, /\.chart-page-varga-table-block[\s\S]*grid-column/);
|
||||
});
|
||||
Reference in New Issue
Block a user