ElyTable: under LOK an AutoFilter range keeps its rows through list, apply, toggle, undo and reopen (R2.9)
This commit is contained in:
5 files changed
+141
-120
No files matched your search
@@ -263,4 +263,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 2026-10-09(用户定:“打开方式”里不得出现 LibreOffice):系统会登记磁盘上能找到的 .app、挂载镜像里的 .app 与运行过的包,构建目录里的 `LibreOffice.app` 与测试挂载的官方镜像曾因此成为 xlsx 等的默认程序。现构建后由 `scripts/inert-lo.sh` 去掉其全部文件与 URL 声明、快速查看与聚焦插件,注销它,并在 `engines/` 放 `.metadata_never_index`;Ely 只取其 Frameworks 与 Resources。任何脚本与测试都不挂载 LibreOffice 镜像:LOK 探测用本构建的内核,单测所需的官方内核由 `scripts/extract-official-lo.sh` 以 7zz 解成无包的文件夹。`tests/scripts/lo-unregistered.sh` 以 NSWorkspace 核对 docx、xlsx、pptx、odt、csv 的“打开方式”里没有 LibreOffice。
|
||||
- 2026-10-09(用户报:ElyTable 点格跳到别处、拖不出选区):Calc 在 `sc_print_twips_msgs` 下以打印 twips(行高原值)报光标、选区与 `.uno:SheetGeometryData`,图块与鼠标却在图块 twips(每行按 `ScViewData::ToPixel` 截成整像素),两者随行号漂移(5000 行处约 140 行)。视图对外一律打印 twips;`LokView::set_geometry` 收下工作表几何后,`sheet::Axis::tile`、`print` 照内核的截断换算(每像素 twips 取图块的像素与 twips 之比),绘制、显示、跳转与冻结都先换成图块 twips,鼠标以图块 twips 发出(`tile_point`),拖动句柄仍为打印 twips;`.uno:` 读回与应用(表头、填充柄)照旧用打印 twips,表头的起点取 `LokView::scrolled_start`。内核读鼠标用的比例原取最后一块图块,worker 现于每次 `setClientVisibleArea` 前以 `setClientZoom` 告知图块比例,点击不再依赖先画过图块。
|
||||
- 2026-10-09(用户报:ElyPDF 改字报“这种字体画不出 V”):文档自带的子集字体常无可用的 Unicode 表(LibreOffice 导出的子集表为占位,0x00 起逐一对应字形号),按 Unicode 找字形一律失败。现以“文档自带字体”写字时直接用页面上的那个字体对象(`shim/ely_pen.c`;`pdf_load_font` 按对象缓存,与文字层的字体同一对象),以它自己的编码写:按文字提取的读法(ToUnicode 或 cid_to_ucs)倒出 Unicode 到编码的表,排版取它的宽度(`pdf_lookup_hmtx`),原有的 ToUnicode 与宽度不动,两个字共用一个字形时各写各的编码(原先按字形号写、重建 ToUnicode,共用字形的字被改写,且 CMap 漏写条目数,PDFKit 读不出字)。删去原文字块后页面资源里可能已无该字体,故按对象号以新名字重新引用。只支持编码即 CID 的字体(简单字体与 Identity-H),其余拒绝并说明;页面上没有的字仍报缺字。排版按同一套宽度量出的框,比较时留 0.01 点的余量(原块宽度即这些宽度之和,浮点误差曾使末字折行)。
|
||||
- 2026-10-09:ElyTable 的自动筛选(R2.9)。内核每次打开筛选列表都把未命名的筛选区向下扩到紧邻的数据行(为接上后来追加的行),分不出选区时就在下方的合计行,合计因此进了列表、随数据一起被隐藏。补丁 `0040-lok-calc-autofilter-keeps-a-marked-block.patch`:LOK 下对跨行的选区开自动筛选时,筛选区记下“选定的行”(不存入文件),此后不再扩展;单格开筛选照旧取整个数据区。已知限制:存后重开的文件不带此记号,其筛选区照内核原样扩展。
|
||||
- 2026-10-09:ElyTable 的自动筛选(R2.9)。内核会重新推算未命名的自动筛选区:每次打开筛选列表时向下扩到紧邻的数据行(`ExtendDataArea`、`ExtendBackColorArea`,为接上后来追加的行),命令从单格找到它时按连续数据区重算(`ScDocShell::GetDBData`:应用条件、关闭筛选及其撤销);选区下方紧邻的合计行因此进了列表、随数据一起被隐藏,存后重开的筛选区同样会扩。补丁 `0040-lok-calc-autofilter-keeps-its-rows.patch`:LOK 下已有的自动筛选区保持设定时的行(同 Excel),不再隐式扩展;单格开筛选时仍取整个数据区。代价:紧贴在筛选区下方追加的行不自动并入,需重新选定后再开筛选。
|
||||
@@ -1,4 +1,4 @@
|
||||
//! An AutoFilter keeps to the marked block (TABLE-08).
|
||||
//! An AutoFilter range keeps its rows (TABLE-08).
|
||||
|
||||
use super::sheet::open_book;
|
||||
use super::*;
|
||||
@@ -18,14 +18,33 @@ const BLOCK: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
</table:table></office:spreadsheet></office:body>
|
||||
</office:document>"#;
|
||||
|
||||
/// The filter entries after an AutoFilter on `marked`.
|
||||
fn entries_after(marked: &str) -> Vec<String> {
|
||||
let (_dir, mut worker, doc) = open_book(BLOCK);
|
||||
/// Column A's filter entries.
|
||||
fn entries(worker: &mut Worker, doc: &Doc) -> Vec<String> {
|
||||
let query = sheet::cell_query(sheet::FILTER, 0, 0);
|
||||
let json = worker.values(doc, &query).expect("the entries");
|
||||
sheet::filter_values(&json).expect("parses").entries.into_iter().map(|e| e.text).collect()
|
||||
}
|
||||
|
||||
/// A book with an AutoFilter set on `marked`.
|
||||
fn filtered(marked: &str) -> (tempfile::TempDir, Worker, Doc) {
|
||||
let (dir, mut worker, doc) = open_book(BLOCK);
|
||||
worker.run(&doc, "cell.goto", &[marked]).expect("marks the cells");
|
||||
worker.run(&doc, "data.autofilter", &[]).expect("filters");
|
||||
let query = sheet::cell_query(sheet::FILTER, 0, 0);
|
||||
let json = worker.values(&doc, &query).expect("the entries");
|
||||
sheet::filter_values(&json).expect("parses").entries.into_iter().map(|e| e.text).collect()
|
||||
(dir, worker, doc)
|
||||
}
|
||||
|
||||
/// Keeps 1: the saved range, and whether the total hides.
|
||||
fn keep_one(worker: &mut Worker, doc: &Doc, dir: &std::path::Path) -> (String, bool) {
|
||||
worker.run(doc, "cell.goto", &["A1"]).expect("A1");
|
||||
let one = r#"{"column":0,"conditions":[{"op":"values","values":["1"]}]}"#;
|
||||
worker.run(doc, "data.filter", &[one]).expect("keeps 1");
|
||||
let out = dir.join("kept.fods");
|
||||
doc.save_as(worker, &crate::file_url(&out), "fods").expect("saves");
|
||||
let xml = std::fs::read_to_string(&out).expect("the saved book");
|
||||
let range = xml.split("table:target-range-address=\"").nth(1).expect("a filter range");
|
||||
let total = &xml[..xml.find("office:value=\"6\"").expect("the total")];
|
||||
let row = &total[total.rfind("<table:table-row").expect("its row")..];
|
||||
(range[..range.find('"').expect("closed")].to_string(), row.contains("table:visibility=\"filter\""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -34,7 +53,9 @@ fn an_autofilter_on_a_marked_block_leaves_the_row_below_out() {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
}
|
||||
assert_eq!(entries_after("A1:A4"), ["1", "2", "3"], "the total below stays out");
|
||||
let (dir, mut worker, doc) = filtered("A1:A4");
|
||||
assert_eq!(entries(&mut worker, &doc), ["1", "2", "3"], "the total below stays out");
|
||||
assert_eq!(keep_one(&mut worker, &doc, dir.path()), ("S.A1:S.A4".into(), false), "applied from one cell");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -43,5 +64,43 @@ fn an_autofilter_on_one_cell_takes_the_data_area() {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
}
|
||||
assert_eq!(entries_after("A1"), ["1", "2", "3", "6"], "the whole column of data");
|
||||
let (_dir, mut worker, doc) = filtered("A1");
|
||||
assert_eq!(entries(&mut worker, &doc), ["1", "2", "3", "6"], "the whole column of data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn off_and_undone_or_set_on_a_filtered_block_the_rows_stay() {
|
||||
if install().is_none() {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
}
|
||||
let (_dir, mut worker, doc) = filtered("A1:A4");
|
||||
worker.run(&doc, "cell.goto", &["A1"]).expect("A1");
|
||||
worker.run(&doc, "data.autofilter", &[]).expect("off");
|
||||
worker.run(&doc, "edit.undo", &[]).expect("back on");
|
||||
assert_eq!(entries(&mut worker, &doc), ["1", "2", "3"], "undo keeps the rows");
|
||||
let (_dir, mut worker, doc) = open_book(BLOCK);
|
||||
worker.run(&doc, "cell.goto", &["A1:A4"]).expect("marks the block");
|
||||
let one = r#"{"conditions":[{"field":0,"op":"equals","value":"1"}]}"#;
|
||||
worker.run(&doc, "data.filter", &[one]).expect("a standard filter");
|
||||
worker.run(&doc, "cell.goto", &["A1:A4"]).expect("marks it again");
|
||||
worker.run(&doc, "data.autofilter", &[]).expect("filters");
|
||||
assert_eq!(entries(&mut worker, &doc), ["1", "2", "3"], "a filtered block keeps its rows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_saved_autofilter_range_keeps_its_rows_when_reopened() {
|
||||
if install().is_none() {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
}
|
||||
for format in ["xlsx", "ods"] {
|
||||
let (dir, mut worker, doc) = filtered("A1:A4");
|
||||
let path = dir.path().join(format!("book.{format}"));
|
||||
doc.save_as(&mut worker, &crate::file_url(&path), format).expect("saves");
|
||||
worker.close_doc(doc).expect("closes");
|
||||
let doc = worker.load(&crate::file_url(&path)).expect("reopens");
|
||||
assert_eq!(entries(&mut worker, &doc), ["1", "2", "3"], "{format}");
|
||||
assert_eq!(keep_one(&mut worker, &doc, dir.path()), ("S.A1:S.A4".into(), false), "{format}");
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
LOK Calc: an AutoFilter on marked rows keeps to those rows
|
||||
|
||||
Each time the filter list opens, GetFilterEntries grows an unnamed
|
||||
filter range to the data rows right under it (ExtendDataArea and
|
||||
ExtendBackColorArea), so appended rows join. It cannot tell them from
|
||||
a totals row that was there when the user marked the block: the
|
||||
total joined the filter, showed in its list and hid with the data.
|
||||
Under LOK, an AutoFilter set on a marked block that spans rows keeps
|
||||
that block: ScDBData remembers it (not saved) and never extends.
|
||||
|
||||
diff --git a/sc/inc/dbdata.hxx b/sc/inc/dbdata.hxx
|
||||
index 6d80216b4..54515b138 100644
|
||||
--- a/sc/inc/dbdata.hxx
|
||||
+++ b/sc/inc/dbdata.hxx
|
||||
@@ -119,6 +119,7 @@ private:
|
||||
|
||||
sal_uInt16 nIndex; ///< unique index formulas
|
||||
bool bAutoFilter; ///< AutoFilter? (not saved)
|
||||
+ bool bMarkedRows; ///< rows were marked; never extend (not saved)
|
||||
bool bModified; ///< is set/cleared for/by(?) UpdateReference
|
||||
|
||||
::std::vector< OUString > maTableColumnNames; ///< names of table columns
|
||||
@@ -244,6 +245,9 @@ public:
|
||||
bool HasAutoFilter() const { return bAutoFilter; }
|
||||
void SetAutoFilter(bool bSet) { bAutoFilter = bSet; }
|
||||
|
||||
+ bool HasMarkedRows() const { return bMarkedRows; }
|
||||
+ void SetMarkedRows(bool bSet) { bMarkedRows = bSet; }
|
||||
+
|
||||
bool IsModified() const { return bModified; }
|
||||
void SetModified(bool bMod) { bModified = bMod; }
|
||||
|
||||
diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx
|
||||
index b1d714c55..e7c5a16c1 100644
|
||||
--- a/sc/source/core/tool/dbdata.cxx
|
||||
+++ b/sc/source/core/tool/dbdata.cxx
|
||||
@@ -75,6 +75,7 @@ ScDBData::ScDBData( const OUString& rName,
|
||||
bDBSelection(false),
|
||||
nIndex (0),
|
||||
bAutoFilter (false),
|
||||
+ bMarkedRows (false),
|
||||
bModified (false),
|
||||
mbTableColumnNamesDirty(true),
|
||||
nFilteredRowCount(SCSIZE_MAX)
|
||||
@@ -110,6 +111,7 @@ ScDBData::ScDBData( const ScDBData& rData ) :
|
||||
bDBSelection (rData.bDBSelection),
|
||||
nIndex (rData.nIndex),
|
||||
bAutoFilter (rData.bAutoFilter),
|
||||
+ bMarkedRows (rData.bMarkedRows),
|
||||
bModified (rData.bModified),
|
||||
maTableColumnNames (rData.maTableColumnNames),
|
||||
maTableColumnAttributes(rData.maTableColumnAttributes),
|
||||
@@ -146,6 +148,7 @@ ScDBData::ScDBData( const OUString& rName, const ScDBData& rData ) :
|
||||
bDBSelection (rData.bDBSelection),
|
||||
nIndex (rData.nIndex),
|
||||
bAutoFilter (rData.bAutoFilter),
|
||||
+ bMarkedRows (rData.bMarkedRows),
|
||||
bModified (rData.bModified),
|
||||
maTableColumnNames (rData.maTableColumnNames),
|
||||
maTableColumnAttributes(rData.maTableColumnAttributes),
|
||||
@@ -192,6 +195,7 @@ ScDBData& ScDBData::operator= (const ScDBData& rData)
|
||||
bDBSelection = rData.bDBSelection;
|
||||
nIndex = rData.nIndex;
|
||||
bAutoFilter = rData.bAutoFilter;
|
||||
+ bMarkedRows = rData.bMarkedRows;
|
||||
nFilteredRowCount = rData.nFilteredRowCount;
|
||||
|
||||
if (bHeaderRangeDiffers)
|
||||
@@ -646,6 +650,8 @@ bool ScDBData::UpdateReference(const ScDocument& rDoc, UpdateRefMode eUpdateRefM
|
||||
|
||||
void ScDBData::ExtendDataArea(const ScDocument& rDoc)
|
||||
{
|
||||
+ if (bMarkedRows)
|
||||
+ return;
|
||||
// Extend the DB area to include data rows immediately below.
|
||||
SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol;
|
||||
SCROW nOldEndRow = nEndRow;
|
||||
@@ -665,6 +671,8 @@ void ScDBData::ExtendDataArea(const ScDocument& rDoc)
|
||||
|
||||
void ScDBData::ExtendBackColorArea(const ScDocument& rDoc)
|
||||
{
|
||||
+ if (bMarkedRows)
|
||||
+ return;
|
||||
// Extend the DB area to include data rows immediately below.
|
||||
SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol;
|
||||
SCROW nOldEndRow = nEndRow;
|
||||
diff --git a/sc/source/ui/view/dbfunc.cxx b/sc/source/ui/view/dbfunc.cxx
|
||||
index db3d23d86..8140ce4d0 100644
|
||||
--- a/sc/source/ui/view/dbfunc.cxx
|
||||
+++ b/sc/source/ui/view/dbfunc.cxx
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include <scitems.hxx>
|
||||
+#include <comphelper/lok.hxx>
|
||||
#include <sfx2/bindings.hxx>
|
||||
#include <vcl/svapp.hxx>
|
||||
#include <vcl/weld.hxx>
|
||||
@@ -286,6 +287,11 @@ void ScDBFunc::ToggleAutoFilter()
|
||||
return;
|
||||
|
||||
pDBData->SetByRow( true ); //! undo, retrieve beforehand ??
|
||||
+ // LOK: marked rows are the range, as in Excel.
|
||||
+ ScRange aMarked;
|
||||
+ pDBData->SetMarkedRows(comphelper::LibreOfficeKit::isActive()
|
||||
+ && rViewData.GetSimpleArea(aMarked) == SC_MARK_SIMPLE
|
||||
+ && aMarked.aStart.Row() != aMarked.aEnd.Row());
|
||||
pDBData->GetQueryParam( aParam );
|
||||
|
||||
SCCOL nCol;
|
||||
@@ -0,0 +1,65 @@
|
||||
LOK Calc: an AutoFilter range keeps its rows
|
||||
|
||||
Calc regrows an unnamed AutoFilter range from the data around it: each
|
||||
time the filter list opens (ExtendDataArea, ExtendBackColorArea, so
|
||||
appended rows join) and whenever a command finds it from a single cell
|
||||
(ScDocShell::GetDBData, so applying a condition, toggling it off, and
|
||||
its undo). A totals row right under a marked block joined the filter,
|
||||
showed in its list and hid with the data; a saved range reopened grew
|
||||
the same way. Under LOK the range keeps the rows it was set on, as in
|
||||
Excel. A single cell still takes the data area when the filter is set.
|
||||
|
||||
diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx
|
||||
index b1d714c55..9a01847ee 100644
|
||||
--- a/sc/source/core/tool/dbdata.cxx
|
||||
+++ b/sc/source/core/tool/dbdata.cxx
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <sal/config.h>
|
||||
#include <sal/log.hxx>
|
||||
+#include <comphelper/lok.hxx>
|
||||
|
||||
#include <o3tl/safeint.hxx>
|
||||
#include <unotools/transliterationwrapper.hxx>
|
||||
@@ -646,6 +647,9 @@ bool ScDBData::UpdateReference(const ScDocument& rDoc, UpdateRefMode eUpdateRefM
|
||||
|
||||
void ScDBData::ExtendDataArea(const ScDocument& rDoc)
|
||||
{
|
||||
+ // LOK: an AutoFilter range keeps its rows, as in Excel.
|
||||
+ if (comphelper::LibreOfficeKit::isActive() && bAutoFilter)
|
||||
+ return;
|
||||
// Extend the DB area to include data rows immediately below.
|
||||
SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol;
|
||||
SCROW nOldEndRow = nEndRow;
|
||||
@@ -665,6 +669,8 @@ void ScDBData::ExtendDataArea(const ScDocument& rDoc)
|
||||
|
||||
void ScDBData::ExtendBackColorArea(const ScDocument& rDoc)
|
||||
{
|
||||
+ if (comphelper::LibreOfficeKit::isActive() && bAutoFilter)
|
||||
+ return;
|
||||
// Extend the DB area to include data rows immediately below.
|
||||
SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol;
|
||||
SCROW nOldEndRow = nEndRow;
|
||||
diff --git a/sc/source/ui/docshell/docsh5.cxx b/sc/source/ui/docshell/docsh5.cxx
|
||||
index d154597c3..54a2344af 100644
|
||||
--- a/sc/source/ui/docshell/docsh5.cxx
|
||||
+++ b/sc/source/ui/docshell/docsh5.cxx
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <sal/config.h>
|
||||
+#include <comphelper/lok.hxx>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -147,7 +148,9 @@ ScDBData* ScDocShell::GetDBData( const ScRange& rMarked, ScGetDBMode eMode, ScGe
|
||||
if (!bSelected)
|
||||
{
|
||||
bUseThis = true;
|
||||
- if ( bIsNoName && (eMode == SC_DB_MAKE || eMode == SC_DB_AUTOFILTER) )
|
||||
+ // LOK: an AutoFilter range keeps its rows, as in Excel.
|
||||
+ const bool bKeep = comphelper::LibreOfficeKit::isActive() && pData->HasAutoFilter();
|
||||
+ if ( bIsNoName && !bKeep && (eMode == SC_DB_MAKE || eMode == SC_DB_AUTOFILTER) )
|
||||
{
|
||||
// If nothing marked or only one row marked, adapt
|
||||
// "unnamed" to contiguous area.
|
||||
@@ -0,0 +1,7 @@
|
||||
FAIL
|
||||
|
||||
patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch:73:固定选区只拦截 ExtendDataArea / ExtendBackColorArea,应用筛选条件仍可通过 ScDocShell::GetDBData 扩区。复现:使用 filter.rs 的 BLOCK,选择 A1:A4 开自动筛选,再点选 A1;读取 AutoFilterValues 得到 ["1", "2", "3"],随后执行 data.filter,参数为 {"column":0,"conditions":[{"op":"values","values":["1"]}]}。导出 ODS 的 database-range 已为 S.A1:S.A5,值为 6 的合计行带 table:visibility="filter"。GetDBData 在 sc/source/ui/docshell/docsh5.cxx:150 对单格重新计算连续数据区,172 行直接 SetArea,绕过新增守卫。同样,单格处关闭自动筛选再撤销,也恢复为包含合计行的范围。修复方向:所有重算已有筛选区的入口都遵守固定边界,切换和撤销保留原范围及其固定状态;补充单格处应用条件、关闭再撤销后的实际行可见性回归。独立 cargo build -p ely-lok --bins --locked、两个 client::tests::filter 测试及八个 client::tests::rules 测试通过,现有测试仅在初始选区仍存在时读取列表,漏掉此失败路径。证据:/tmp/ely-r29-cursor.log、/tmp/ely-r29-confirm.log。
|
||||
|
||||
patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch:106:选区判定漏掉 SC_MARK_SIMPLE_FILTERED,已有条件筛选的连续选区无法获得固定边界。复现:同一 BLOCK,选择 A1:A4,执行 data.filter,参数为 {"conditions":[{"field":0,"op":"equals","value":"1"}]};再次选择 A1:A4 并执行 data.autofilter。AutoFilterValues 随即返回 ["1", "2", "3", "6"];应用仅保留 1 的条件后,导出的筛选区为 S.A1:S.A5,合计行 6 被隐藏。GetDBData 已支持 SC_MARK_SIMPLE_FILTERED,新增标记判定却只接受 SC_MARK_SIMPLE。修复方向:对跨行的连续已筛选选区同样记录固定边界,并增加先条件筛选、再开启自动筛选的回归。该场景以独立 worker 重跑两次结果一致,证据:/tmp/ely-r29-confirm.log、/tmp/ely-r29-cursor.log。
|
||||
|
||||
patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch:19:固定范围依赖未持久化的 bMarkedRows,XLSX / ODS 保存重开后恢复原缺陷。复现:选择同一 BLOCK 的 A1:A4 开自动筛选,保存并关闭;独立读取存档确认 XLSX 的 autoFilter ref="A1:A4"、ODS 的 target-range-address="S.A1:S.A4"。重开后首次读取筛选列表返回 ["1", "2", "3", "6"];仅保留 1 后导出,范围变成 S.A1:S.A5,合计行带 table:visibility="filter"。两种格式均复现,违反 R2.9 的选区边界及 TABLE-01 / TABLE-13 的保存使用路径;agents.md:266 记录了此限制。修复方向:保存重开后保留或从已保存的筛选范围恢复固定边界,覆盖 XLSX、ODS 往返后的列表和实际隐藏行。证据:/tmp/ely-r29-confirm.log。
|
||||
Reference in new issue
Block a user