Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
09a9551
fix(web-deploy): correct broken WSGI target + honest multi-worker doc…
Aaronhaohao Jul 3, 2026
c473398
Merge fix/p0-deploy-wsgi: SWARM P0 F01/F02 deploy-doc fixes
Aaronhaohao Jul 3, 2026
c1532ad
docs: add SWARM_REVIEW_2026 comprehensive review report
Aaronhaohao Jul 3, 2026
828a312
docs: Adaptive Workbench Section 1 (design) + Section 2 (batch plan) …
Aaronhaohao Jul 3, 2026
506b9c3
Merge docs/adaptive-workbench-specs: Section 1 + 2 planning docs
Aaronhaohao Jul 3, 2026
8447cad
feat(desktop): icon-ified menu bar + result overview popover + status…
Aaronhaohao Jul 4, 2026
6dce23c
fix(desktop): menu-bar review — gate-reveal on gated checkbox actions…
Aaronhaohao Jul 4, 2026
43e8035
test(desktop): programmatic reachability sweep — enumerate all schema…
Aaronhaohao Jul 4, 2026
43749d8
test(desktop): reachability sweep includes QAbstractSpinBox (QDoubleS…
Aaronhaohao Jul 4, 2026
b09bcc4
test(desktop): reachability covers custom editors + closed type-exhau…
Aaronhaohao Jul 4, 2026
85c3db6
test(desktop): reachability sweep covers runtime-relabelled schema keys
Aaronhaohao Jul 4, 2026
95aec0c
feat(desktop): in-menu editors for 计算/LaTeX options (mirror widgets, …
Aaronhaohao Jul 4, 2026
86e012d
test(desktop): assert mirror edit re-runs real spin's downstream slot…
Aaronhaohao Jul 4, 2026
363b817
fix(desktop): overview popover opens on left-click only (CodeRabbit)
Aaronhaohao Jul 4, 2026
ece3785
docs(desktop): toolbar options-popup (QFrame) design spec — pivot fro…
Aaronhaohao Jul 4, 2026
d9733f2
docs(desktop): amend options spec → INLINE toggle panel (dual-model V…
Aaronhaohao Jul 4, 2026
22973ca
feat(desktop): inline toolbar options panels (计算/LaTeX), remove left …
Aaronhaohao Jul 4, 2026
67e82e3
fix(desktop): repoint GUI schema scanner at the option panels (serial…
Aaronhaohao Jul 4, 2026
984abc6
fix(desktop): overview popover shows 0 points for empty tabular resul…
Aaronhaohao Jul 4, 2026
2cfadd4
test(desktop): reachability sweep only toggles user-operable selector…
Aaronhaohao Jul 4, 2026
d76ee47
docs(desktop): finalize 2-pane layout spec — complete 3-pane blast ra…
Aaronhaohao Jul 5, 2026
cb60f59
feat(desktop): move compute-mode selector to the toolbar (Stage ① of …
Aaronhaohao Jul 5, 2026
5690384
feat(desktop): merge 3-pane workbench into 2 panes (Stage ②+③)
Aaronhaohao Jul 5, 2026
f8a4b34
fix(desktop): GUI scan fails loud if an options panel is missing (Cod…
Aaronhaohao Jul 5, 2026
14768e5
docs(desktop): LaTeX/PDF window + toolbar/result cleanup design spec
Aaronhaohao Jul 5, 2026
65cd8c7
docs(desktop): revise LaTeX/PDF spec — fold in Codex design-review (5…
Aaronhaohao Jul 5, 2026
8ecef38
docs(desktop): LaTeX spec — note pdf rasterizer reuse + verified web …
Aaronhaohao Jul 5, 2026
cb239be
docs(desktop): finalize LaTeX spec — workspace migration verified (no…
Aaronhaohao Jul 5, 2026
37d8f9d
feat(desktop): tectonic-only PDF compile — drop local-tex fallback (M…
Aaronhaohao Jul 5, 2026
b08164e
feat(desktop): options as QDialogs, drop LaTeX output-path field (Mod…
Aaronhaohao Jul 5, 2026
9242680
feat(desktop): run writes LaTeX to a temp path, not a user output fie…
Aaronhaohao Jul 5, 2026
be870af
feat(desktop): LaTeX preview dialog with TeX/PDF tabs (Module 1b)
Aaronhaohao Jul 5, 2026
f029d0d
docs(desktop): on-demand LaTeX refactor spec (result-panel model)
Aaronhaohao Jul 5, 2026
c82f693
docs(desktop): revise on-demand LaTeX spec — Codex design review (6 c…
Aaronhaohao Jul 5, 2026
fee0ed2
docs(desktop): on-demand LaTeX spec — rebuild tex from persisted resu…
Aaronhaohao Jul 5, 2026
d5e760e
fix(desktop): LaTeX preview PDF renders on compile-completion, not sy…
Aaronhaohao Jul 5, 2026
3d870ee
docs(desktop): 4·2 integration notes — per-mode on-demand LaTeX snaps…
Aaronhaohao Jul 5, 2026
877bda9
feat(desktop): on-demand LaTeX foundation + root_solving rebuild (4·2)
Aaronhaohao Jul 5, 2026
5981c38
feat(desktop): on-demand LaTeX rebuild — extrapolation (4·2, 2/5 modes)
Aaronhaohao Jul 5, 2026
2a65388
feat(desktop): on-demand LaTeX rebuild — error propagation (4·2, 3/5 …
Aaronhaohao Jul 5, 2026
8e68243
feat(desktop): on-demand LaTeX rebuild — statistics (4·2, 4/5 modes)
Aaronhaohao Jul 5, 2026
45a753b
feat(desktop): on-demand LaTeX rebuild — fitting single-fit (4·2 COMP…
Aaronhaohao Jul 5, 2026
d16b783
feat(desktop): on-demand LaTeX dispatcher (4·3 part 1)
Aaronhaohao Jul 5, 2026
9252711
feat(desktop): 生成TeX/预览PDF result-panel buttons + open_latex_preview …
Aaronhaohao Jul 5, 2026
182c014
refactor(desktop): remove empty output_setup_section from the merged …
Aaronhaohao Jul 5, 2026
24e2dce
feat(desktop): collapse history section by default, click header to e…
Aaronhaohao Jul 5, 2026
4224fad
feat(desktop): run no longer writes tex — on-demand generates it (4·4)
Aaronhaohao Jul 5, 2026
4f4c7c6
feat(desktop): move LaTeX options to result panel, drop toolbar LaTeX…
Aaronhaohao Jul 6, 2026
1afab05
feat(desktop): remove TeX/PDF tabs from result_tabs, keep widgets off…
Aaronhaohao Jul 6, 2026
df94b00
feat(desktop): delete bottom 开始执行 button, drive run/stop from toolbar…
Aaronhaohao Jul 6, 2026
33da84a
feat(desktop): remove generate_latex_checkbox, LaTeX options always v…
Aaronhaohao Jul 6, 2026
10791a8
fix(desktop): LaTeX preview never shows a stale PDF or stays stuck co…
Aaronhaohao Jul 6, 2026
075af1f
fix(desktop): PDF preview tab no longer shows a fake "编译 PDF 中…" + co…
Aaronhaohao Jul 6, 2026
bfc28d2
fix(latex): group the integer part too (group-digits=all), not just d…
Aaronhaohao Jul 6, 2026
1c4e4d1
feat(latex): engine capability probe + mode resolution (Step 1 of eng…
Aaronhaohao Jul 6, 2026
b89add4
feat(latex): sisetup emitter takes emit_digit_group_size from the pro…
Aaronhaohao Jul 6, 2026
9482212
feat(latex): engine-adaptive digit grouping for statistics mode (Step…
Aaronhaohao Jul 6, 2026
e6a047a
feat(latex): compile uses the resolved engine per mode, not hardcoded…
Aaronhaohao Jul 6, 2026
ac8478a
feat(latex): engine MODE selector UI (自动/内置/本地) driving the adaptive …
Aaronhaohao Jul 6, 2026
d618b15
feat(latex): engine dropdown lists the actual detected compilers, not…
Aaronhaohao Jul 6, 2026
a7ee91e
fix(latex): don't follow the engine symlink + show the engine selecto…
Aaronhaohao Jul 6, 2026
34bbce3
feat(latex): engine-adaptive digit grouping for root-solving mode (St…
Aaronhaohao Jul 6, 2026
f87f63d
feat(latex): engine-adaptive digit grouping for error-propagation mod…
Aaronhaohao Jul 6, 2026
95c5cca
feat(latex): engine-adaptive digit grouping for fitting mode (Step 3b·3)
Aaronhaohao Jul 6, 2026
b7b8edf
feat(latex): engine-adaptive digit grouping for extrapolation mode (S…
Aaronhaohao Jul 6, 2026
41ac5e6
test(latex): fitting preamble test reflects unguarded digit-group-siz…
Aaronhaohao Jul 6, 2026
b17cdd6
fix(latex): resolve 5 dual-model adversarial-review findings (F1-F5)
Aaronhaohao Jul 7, 2026
74109e7
fix(latex): local engine mode must not fall back to Tectonic auto-ins…
Aaronhaohao Jul 7, 2026
6f03a2d
fix(desktop): batch fit can now generate LaTeX on demand (+ F1 leftover)
Aaronhaohao Jul 7, 2026
d61697b
fix(desktop): long compile-error dialog no longer pushes OK off-screen
Aaronhaohao Jul 7, 2026
662b0ff
fix(desktop): result font-size control now live-updates + survives re…
Aaronhaohao Jul 7, 2026
fd36002
fix(desktop): fit model line honours display digits + scientific toggles
Aaronhaohao Jul 7, 2026
ece89d3
feat(desktop): result overview → clickable toolbar status chip; remov…
Aaronhaohao Jul 7, 2026
603b3a5
chore: satisfy mypy-strict + file-size ratchet after bugfix/redesign …
Aaronhaohao Jul 7, 2026
33cbf33
fix(desktop): batch-fit on-demand builder returns None on write failure
Aaronhaohao Jul 7, 2026
5dc9f60
fix(desktop): serial-review findings — batch-tex fidelity + test/widg…
Aaronhaohao Jul 7, 2026
2c3d990
feat(desktop): move 不确定度位数 to result panel with live re-render
Aaronhaohao Jul 8, 2026
6498f36
feat(desktop): persist tex-rebuild stash in workspace (生成 TeX works a…
Aaronhaohao Jul 8, 2026
85cd78a
feat(desktop): move history to a toolbar 历史 button → popup panel
Aaronhaohao Jul 8, 2026
2410965
feat(desktop): Excel-like cell copy + kill hollow gap around short co…
Aaronhaohao Jul 8, 2026
1197d7d
feat(desktop): merge input data + constants into sheet tabs (输入数据 / 常数)
Aaronhaohao Jul 8, 2026
141a1d9
fix(desktop): self-contained input/constants tabs + per-tab data-file…
Aaronhaohao Jul 8, 2026
7010f21
docs: spec — unify left column into data + one config card
Aaronhaohao Jul 8, 2026
bd0ba19
docs(spec): add three-model review findings (S1-S5 in-scope + pre-exi…
Aaronhaohao Jul 8, 2026
c524e62
fix(desktop): S1 — lossless mpf workspace serialization (was two-side…
Aaronhaohao Jul 8, 2026
c817469
fix(desktop): review findings P-A + S2..S5 (crash, round-trip, clip, …
Aaronhaohao Jul 8, 2026
9ad3c7c
revert(desktop): S2 was a misjudgment — file-source decoupling is by …
Aaronhaohao Jul 8, 2026
e597689
fix(desktop): pre-existing capture edge cases P-B/P-C/P-D (from review)
Aaronhaohao Jul 8, 2026
ea1885d
feat(desktop): unify left column into [input data] + [one config card]
Aaronhaohao Jul 8, 2026
6752268
docs(spec): mark config-card restructure implemented (ea1885d)
Aaronhaohao Jul 8, 2026
0fed9c7
fix(desktop): UI polish — tab border, 参数 title, remove collapse, dark…
Aaronhaohao Jul 8, 2026
f1bab71
feat(desktop): remove the units (单位标注) feature + fix option reachability
Aaronhaohao Jul 8, 2026
a2f3027
refactor(desktop): remove dead _unit_rows_to_map helper (Claude self-…
Aaronhaohao Jul 8, 2026
d2bd1ec
fix(desktop): restyle formula preview + input tabs on theme toggle (C…
Aaronhaohao Jul 8, 2026
0f03cd4
fix(desktop): constants editor restyles on theme toggle (P2/P3 siblin…
Aaronhaohao Jul 8, 2026
122fb73
feat(desktop): default to a result-heavy pane ratio (~1:3 target)
Aaronhaohao Jul 9, 2026
a450ebf
feat(desktop): drop 使用数据文件 checkbox — file path takes precedence over…
Aaronhaohao Jul 9, 2026
014bcf8
fix(desktop): keep formula preview pixmap inside the padded box (bott…
Aaronhaohao Jul 9, 2026
2d35642
feat(desktop): input-data area expand/collapse toggle with smooth ani…
Aaronhaohao Jul 9, 2026
435e509
fix(desktop): put 数据文件 label on the same row as the path edit + Browse
Aaronhaohao Jul 9, 2026
7520296
fix(desktop): make 输入数据 and 常数 tab file-row layouts consistent
Aaronhaohao Jul 9, 2026
d947517
fix(desktop): align 输入数据/常数 cards — row count, ? placement, file-row …
Aaronhaohao Jul 9, 2026
b0ef466
feat(desktop): add 输入常数 title to the constants card (mirrors 输入数据)
Aaronhaohao Jul 9, 2026
ff7a1c0
style(desktop): use workbench_title_text_style() for the constants ti…
Aaronhaohao Jul 9, 2026
0d6796e
refactor(theme): converge near-duplicate colors into semantic tokens …
Aaronhaohao Jul 9, 2026
fbfb405
refactor(theme): converge border-radius to a 3-tier scale (design rev…
Aaronhaohao Jul 9, 2026
675bca1
refactor(desktop): unify card padding + de-border nested variable car…
Aaronhaohao Jul 9, 2026
08bb86c
fix(desktop): stop in-flight input-expand animation before starting a…
Aaronhaohao Jul 9, 2026
8b3603b
fix(desktop): file-precedence live feedback + tutorial contrast (Code…
Aaronhaohao Jul 9, 2026
cfa2a56
fix(theme): pin tutorial title to dark text on its always-white card …
Aaronhaohao Jul 9, 2026
1825505
feat(desktop): give the embedded 输入常数 card a border like 输入数据
Aaronhaohao Jul 9, 2026
77ada2e
fix(desktop): restore full border on variable section cards (参数 card …
Aaronhaohao Jul 9, 2026
02adc4d
refactor(desktop): /simplify cleanup — radius convergence, keystroke …
Aaronhaohao Jul 9, 2026
1d8496b
fix(desktop): address CodeRabbit PR #84 review findings
Aaronhaohao Jul 9, 2026
a9c177a
fix(desktop): register data/constants file-edit tooltip + bilingual p…
Aaronhaohao Jul 9, 2026
ba9678d
test(desktop): fix fit-batches result tests that hung CI on a blockin…
Aaronhaohao Jul 9, 2026
2c3872a
fix: satisfy precision-guardrail + file-size ratchet (CI quality gates)
Aaronhaohao Jul 9, 2026
0fe1aeb
fix(desktop): palette-change slot must not crash the event loop on a …
Aaronhaohao Jul 9, 2026
1cdaed0
test(desktop): pin UI language in input-constants-tabs tests (platfor…
Aaronhaohao Jul 9, 2026
83048b1
fix(web): clamp user mpmath precision on all compute routes (audit A1…
Aaronhaohao Jul 10, 2026
5721a1a
fix(latex): floor formatter working precision to `places` (audit A3, …
Aaronhaohao Jul 10, 2026
3437533
fix(desktop): drop oversized latex_inputs stash instead of failing th…
Aaronhaohao Jul 10, 2026
9a4e754
fix(fitting): report NaN uncertainty for dof<=0 custom fits (audit A5…
Aaronhaohao Jul 10, 2026
8b2bf90
fix(web): repair docs heading-id regex so TOC anchors resolve (audit …
Aaronhaohao Jul 10, 2026
1376e75
fix(desktop): token-ify input_data_tabs_style so it matches the resul…
Aaronhaohao Jul 10, 2026
75a254f
fix(web): rate-limit the heavy compute POST routes (audit A2, medium)
Aaronhaohao Jul 10, 2026
8a2f29b
fix(extrapolation): derive Shanks diagnostics from convergents, not t…
Aaronhaohao Jul 10, 2026
c8cfac1
fix(desktop): don't restore a stale constants text draft over authori…
Aaronhaohao Jul 10, 2026
68b4c75
fix(latex): satisfy mypy-strict in _format_workdps (int(mp.dps))
Aaronhaohao Jul 10, 2026
42ee385
feat(extrapolation): expose quadratic three-point method in desktop c…
Aaronhaohao Jul 10, 2026
caa4e27
feat(web-fitting): add self_consistent (implicit) fitting to Flask ba…
Aaronhaohao Jul 10, 2026
b6862b1
fix(web-fitting): clean bilingual error + N/A for undefined self_cons…
Aaronhaohao Jul 10, 2026
17153d0
fix(latex/web): root-guard non-finite values in LaTeX formatting + bi…
Aaronhaohao Jul 10, 2026
3dc29da
fix(latex/report): sink non-finite guard into private formatter + sup…
Aaronhaohao Jul 10, 2026
92dbf4e
fix(latex-tables): pass \multicolumn literal cells through app-side g…
Aaronhaohao Jul 10, 2026
288a305
fix(desktop): keep manual table editable after opening an example wor…
Aaronhaohao Jul 11, 2026
a95a141
fix(desktop): remove redundant 预览 PDF button from result panel
Aaronhaohao Jul 11, 2026
daca737
fix(desktop): also clear file-mode constants path on example open (co…
Aaronhaohao Jul 11, 2026
dbbb838
test(desktop): assert constants content (ALPHA) survives, not just ro…
Aaronhaohao Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 51 additions & 15 deletions app_desktop/constants_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from PySide6.QtWidgets import (
QCheckBox,
QHBoxLayout,
QLabel,
QPushButton,
QPlainTextEdit,
QStackedWidget,
Expand All @@ -21,7 +22,7 @@
normalize_constants_state,
parse_constants_text,
)
from app_desktop.theme import constants_editor_style
from app_desktop.theme import CARD_PADDING, constants_editor_style
from app_desktop.widget_hints import set_accessible_description


Expand Down Expand Up @@ -73,22 +74,12 @@ def __init__(
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(8)

header_layout = QHBoxLayout()
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.setSpacing(6)
# The checkbox is legacy/hidden; keep it for callers that still poke it, but it no longer
# occupies its own header row (the summary + ? sit in the controls row, like the data card).
self.checkbox = QCheckBox(checkbox_text)
self.checkbox.setChecked(bool(checked))
self.checkbox.toggled.connect(self._on_checked_changed)
self.checkbox.hide()
header_layout.addWidget(self.checkbox)
self.help_button = _HelpButton("?")
self.help_button.setFlat(True)
self.help_button.setFocusPolicy(Qt.NoFocus)
self.help_button.setFixedWidth(24)
self.help_button.hide()
header_layout.addWidget(self.help_button)
header_layout.addStretch()
layout.addLayout(header_layout)

self.controls_widget = QWidget()
controls_layout = QHBoxLayout(self.controls_widget)
Expand All @@ -103,18 +94,37 @@ def __init__(
self.remove_button.clicked.connect(self._remove_row)
self.clear_button.clicked.connect(self.clear)
self.view_toggle_button.clicked.connect(self._toggle_view)
# Card title on the LEFT of the controls row (mirrors the data card's "输入数据" title).
self.title_label = QLabel("输入常数")
self.title_label.setObjectName("constants_title")
controls_layout.addWidget(self.title_label)
controls_layout.addWidget(self.add_button)
controls_layout.addWidget(self.remove_button)
controls_layout.addWidget(self.clear_button)
controls_layout.addWidget(self.view_toggle_button)
controls_layout.addStretch()
# Row-count summary (mirrors the data card's "N 行"), then the ? help button — both on the
# RIGHT of the controls row, not a separate header line.
self.summary_label = QLabel("")
self.summary_label.setObjectName("constants_summary")
controls_layout.addWidget(self.summary_label)
self.help_button = _HelpButton("?")
self.help_button.setFlat(True)
self.help_button.setFocusPolicy(Qt.NoFocus)
self.help_button.setFixedWidth(24)
self.help_button.hide()
controls_layout.addWidget(self.help_button)
layout.addWidget(self.controls_widget)

self.stack = QStackedWidget()
self.table_view = QTableWidget(self._min_rows, 2)
self.table_view.setHorizontalHeaderLabels(["Name", "Value"])
self.table_view.setMinimumHeight(120)
self.table_view.itemChanged.connect(self._on_table_changed)
# Excel-like block copy (Ctrl/Cmd+C → TSV).
from app_desktop.table_copy import install_cell_copy

install_cell_copy(self.table_view)
self.stack.addWidget(self.table_view)

self.text_view = QPlainTextEdit()
Expand All @@ -130,15 +140,29 @@ def __init__(
layout.addWidget(self.stack)

self._on_checked_changed(self.checkbox.isChecked())
self._update_summary()
self._constructed = True

def set_embedded_in_workbench(self, embedded: bool) -> None:
embedded = bool(embedded)
self.setProperty("datalab_constants_embedded", embedded)
layout = self.layout()
if layout is not None:
margin = 0 if embedded else 8
layout.setContentsMargins(margin, margin, margin, margin)
# Embedded card now has its own border (like the data card) → pad content off it with
# the shared CARD_PADDING; standalone keeps its tighter 8px inset.
if embedded:
layout.setContentsMargins(*CARD_PADDING)
else:
layout.setContentsMargins(8, 8, 8, 8)
self.setStyleSheet(constants_editor_style(embedded=embedded))
self.style().unpolish(self)
self.style().polish(self)

def refresh_theme_style(self) -> None:
"""Re-apply the (theme-dependent) editor style for the current embedded state. Its style is
otherwise set once at construction/embedding, so a live light↔dark toggle would leave the
button colors stale — the theme refresh calls this."""
embedded = bool(self.property("datalab_constants_embedded"))
self.setStyleSheet(constants_editor_style(embedded=embedded))
self.style().unpolish(self)
self.style().polish(self)
Expand Down Expand Up @@ -273,7 +297,19 @@ def _apply_inputs_visibility(self) -> None:
self.controls_widget.setEnabled(visible)
self.stack.setEnabled(visible)

def _update_summary(self) -> None:
"""Show the number of filled constant rows (mirrors the data card's "N 行")."""
label = getattr(self, "summary_label", None)
if label is None:
return
try:
count = len([r for r in self.rows() if (r.get("name") or r.get("value"))])
except Exception:
count = 0
label.setText(f"{count} 行")

def _emit_changed(self, *_args: object) -> None:
self._update_summary()
if not self._syncing:
self.changed.emit()

Expand Down
39 changes: 32 additions & 7 deletions app_desktop/current_page_stack.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,45 @@
from __future__ import annotations

from PySide6.QtCore import QSize
from PySide6.QtWidgets import QStackedWidget
from PySide6.QtWidgets import QStackedWidget, QWidget


class CurrentPageStack(QStackedWidget):
"""QStackedWidget whose layout hints come only from the current page."""
"""QStackedWidget whose height tracks the CURRENT page only.

A plain QStackedWidget sizes to its tallest page, so a short mode config would sit in a hollow
gap; capping it at Maximum policy instead clipped a mode whose config grows after layout
(fitting→comparison). This subclass pins its own fixed height to the active page's sizeHint,
re-syncing on page change and when the active page's layout invalidates — so it is always
exactly as tall as the current page needs (no gap, no clip).
"""

def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.currentChanged.connect(lambda _index: self._sync_height_to_current())

def sizeHint(self) -> QSize:
page = self.currentWidget()
if page is None:
return super().sizeHint()
return page.sizeHint()
return page.sizeHint() if page is not None else super().sizeHint()

def minimumSizeHint(self) -> QSize:
page = self.currentWidget()
return page.minimumSizeHint() if page is not None else super().minimumSizeHint()

def _sync_height_to_current(self) -> None:
page = self.currentWidget()
if page is None:
return super().minimumSizeHint()
return page.minimumSizeHint()
return
# Fix the stack to the current page's preferred height so the layout neither inflates a
# short page (gap) nor caps a taller/grown page (clip).
self.setFixedHeight(max(page.sizeHint().height(), page.minimumSizeHint().height()))

def event(self, evt) -> bool: # type: ignore[no-untyped-def]
# LayoutRequest fires when the current page's contents change size (e.g. a mode reveals
# extra fields). Re-sync so a dynamically growing page is not clipped.
result = super().event(evt)
from PySide6.QtCore import QEvent

if evt.type() == QEvent.Type.LayoutRequest:
self._sync_height_to_current()
return result
59 changes: 43 additions & 16 deletions app_desktop/fitting_latex_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,13 @@ def latex_escape(text: str) -> str:
return "".join(mapping.get(ch, ch) for ch in str(text))


def build_fit_latex_preamble(*, use_dcolumn: bool, digits: int, latex_group_size: int) -> list[str]:
group_size = max(1, int(latex_group_size))
def build_fit_latex_preamble(
*, use_dcolumn: bool, digits: int, latex_group_size: int, native_group_width: bool = True
) -> list[str]:
# max(0, ...) not max(1, ...): group_size 0 must stay 0 so build_sisetup_block emits the
# "no grouping" body (group-digits = false). max(1,..) forced 0→1 → grouping stayed ON,
# contradicting the UI's "0 = 不分组" (dual-model review F1).
group_size = max(0, int(latex_group_size))
lines = [
"\\documentclass{article}",
"\\usepackage{ifxetex}",
Expand All @@ -65,12 +70,14 @@ def build_fit_latex_preamble(*, use_dcolumn: bool, digits: int, latex_group_size
]
)
lines.append("\\usepackage{siunitx}")
# Centralized v2/v3-compatible \sisetup{...} block — see helper for
# the ``\@ifpackagelater`` guard around v3-only ``digit-group-size``.
# native_group_width True → emit siunitx digit-group-size (native S-column variable-width
# grouping); False (bundled Tectonic) → don't (cells are pre-grouped app-side).
emit_dgs = bool(native_group_width and not use_dcolumn and group_size > 0)
lines.append(
build_sisetup_block(
group_size=group_size,
include_dcolumn=use_dcolumn,
emit_digit_group_size=emit_dgs,
).rstrip("\n")
)
lines.extend(
Expand Down Expand Up @@ -105,10 +112,25 @@ def build_fit_latex_block(
default_uncertainty_digits: int | None = None,
cleaned_substituted: str | None = None,
units: Mapping[str, Any] | None = None,
native_group_width: bool = True,
) -> list[str]:
from datalab_latex.latex_formatting import group_digits_both_sides

default_unc_digits = default_uncertainty_digits
variable_pairs = variable_pairs or []
target_column = (target_column or "").strip()
# App-side grouping when the engine can't vary the siunitx group WIDTH (non-native) and
# grouping is on in siunitx (non-dcolumn) mode: pre-group each value cell + use a plain r
# column instead of an S column siunitx would re-group at a fixed 3.
_group = max(0, int(latex_group_size))
app_group = (not native_group_width) and (not use_dcolumn) and _group > 0

def _maybe_group(cell: str) -> str:
if app_group and "\\multicolumn" not in cell and "\\text" not in cell:
# The grouped value goes into a plain r column, so return the grouped string directly —
# wrapping it in \text{...} (with its \, thin-spaces) broke TeX compilation (CodeRabbit).
return group_digits_both_sides(cell, _group)
return cell
Comment thread
coderabbitai[bot] marked this conversation as resolved.

def _format_cell_value(val: mp.mpf, sigma_obj, *, is_input: bool) -> str:
sigma_digits = None if is_input else default_unc_digits
Expand All @@ -125,14 +147,16 @@ def _format_cell_value(val: mp.mpf, sigma_obj, *, is_input: bool) -> str:
sigma = mp.mpf(sigma)
except Exception:
sigma = None
return format_value_for_latex_file(
mp.mpf(val),
sigma,
use_dcolumn=use_dcolumn,
latex_input_decimals=digits,
is_input=is_input,
latex_group_size=latex_group_size,
uncertainty_digits=sigma_digits,
return _maybe_group(
format_value_for_latex_file(
mp.mpf(val),
sigma,
use_dcolumn=use_dcolumn,
latex_input_decimals=digits,
is_input=is_input,
latex_group_size=latex_group_size,
uncertainty_digits=sigma_digits,
)
)

def _format_key(key: str) -> str:
Expand Down Expand Up @@ -221,26 +245,26 @@ def _target_unit() -> str:
("RMSE", fit_result.rmse),
]
for label, value in metrics:
val_text = format_value_for_latex_file(
val_text = _maybe_group(format_value_for_latex_file(
mp.mpf(value),
None,
use_dcolumn=use_dcolumn,
latex_input_decimals=digits,
is_input=True,
latex_group_size=latex_group_size,
)
))
row_unit = output_unit if label == "RMSE" else ""
table_rows.append((label, val_text, row_unit))

def _format_diagnostic_value(value: object) -> str:
return format_value_for_latex_file(
return _maybe_group(format_value_for_latex_file(
mp.mpf(value),
None,
use_dcolumn=use_dcolumn,
latex_input_decimals=digits,
is_input=True,
latex_group_size=latex_group_size,
)
))

diagnostic_entries, diagnostic_warnings = build_fitting_diagnostic_latex_entries(
fit_result,
Expand Down Expand Up @@ -296,6 +320,9 @@ def _format_diagnostic_value(value: object) -> str:
value_cells = [val for _, val, _unit in table_rows]
if use_dcolumn:
numeric_spec = calculate_dcolumn_format_for_column(value_cells, "fit_values")
elif app_group:
# Cells are pre-grouped + wrapped in \text{}; a plain right-aligned column.
numeric_spec = "r"
else:
numeric_spec = siunitx_column_spec(value_cells)
include_unit_column = any(unit for _key, _val, unit in table_rows)
Expand Down
20 changes: 16 additions & 4 deletions app_desktop/formula_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
_IDENTIFIER_RE: Final = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
_INLINE_PREVIEW_MAX_WIDTH: Final = 520
_INLINE_PREVIEW_MAX_HEIGHT: Final = 104
# The label reserves 12px padding + a 1px border on each side (formula_inline_preview_style). The
# rendered pixmap must fit INSIDE that inset, otherwise a tall formula fills the label edge-to-edge
# and paints over the bottom padding/rounded border (the "border not closed" bug). Cap the pixmap a
# little smaller than the label content box so the rounded border always stays visible.
_INLINE_PREVIEW_INSET: Final = 2 * (12 + 1)
_INLINE_PREVIEW_PIXMAP_MAX_HEIGHT: Final = _INLINE_PREVIEW_MAX_HEIGHT - _INLINE_PREVIEW_INSET
_INLINE_PREVIEW_PIXMAP_MAX_WIDTH: Final = _INLINE_PREVIEW_MAX_WIDTH - _INLINE_PREVIEW_INSET


class FormulaPreviewLabel(QLabel):
Expand Down Expand Up @@ -192,6 +199,9 @@ def configure_formula_preview_label(label: QLabel, *, constrain_size: bool = Fal
label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
label.setCursor(Qt.CursorShape.PointingHandCursor)
label.setToolTip("Click to enlarge formula")
# WA_StyledBackground makes Qt honour the stylesheet's border-radius on a QLabel — without
# it the rounded background/border isn't clipped to the corners, so they look squared-off.
label.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
label.setStyleSheet(formula_inline_preview_style())


Expand Down Expand Up @@ -264,14 +274,16 @@ def update_formula_preview_with_empty_text(
)
pixmap = QPixmap()
if result.ok and result.png_bytes and _load_png_pixmap(pixmap, result.png_bytes):
if pixmap.width() > _INLINE_PREVIEW_MAX_WIDTH:
# Scale to fit INSIDE the label's padded content box (leaving the border visible), not to the
# label's outer max size — see _INLINE_PREVIEW_PIXMAP_MAX_* above.
if pixmap.width() > _INLINE_PREVIEW_PIXMAP_MAX_WIDTH:
pixmap = pixmap.scaledToWidth(
_INLINE_PREVIEW_MAX_WIDTH,
_INLINE_PREVIEW_PIXMAP_MAX_WIDTH,
Qt.TransformationMode.SmoothTransformation,
)
if pixmap.height() > _INLINE_PREVIEW_MAX_HEIGHT:
if pixmap.height() > _INLINE_PREVIEW_PIXMAP_MAX_HEIGHT:
pixmap = pixmap.scaledToHeight(
_INLINE_PREVIEW_MAX_HEIGHT,
_INLINE_PREVIEW_PIXMAP_MAX_HEIGHT,
Qt.TransformationMode.SmoothTransformation,
)
label.setPixmap(pixmap)
Expand Down
Loading
Loading