Skip to content

fix: plate tui — a real two-column prepare screen, and a summary that wraps - #104

Merged
DLANSAMA merged 1 commit into
mainfrom
fix/tui-prepare-two-column
Aug 5, 2026
Merged

fix: plate tui — a real two-column prepare screen, and a summary that wraps#104
DLANSAMA merged 1 commit into
mainfrom
fix/tui-prepare-two-column

Conversation

@DLANSAMA

@DLANSAMA DLANSAMA commented Aug 5, 2026

Copy link
Copy Markdown
Owner

The last known visual gap in the TUI before 0.5.0.

The prepare screen wasted ~40% of its width

Everything was stacked in one VerticalScroll, so the slice estimate — the thing you press Prepare to get — landed below the fold. The form is now on the left, the results on the right, and both are visible at once.

Below 100 columns two halves would be too narrow for a material label (ABS — strong, needs an enclosure (detected in AMS) needs 59 cells), so the screen keeps the single-column layout there and scrolls a finished — or failed — run into view instead of leaving it off-screen.

The results side is a titled Result box with copy saying what pressing Prepare will put in it. Before, the initial state was an empty half-screen with a disabled Start print… floating in the middle of it, which read as a half-rendered widget. round border, not thick0d63378 established that thick renders as solid slabs.

The summary restarted wrapped values in the label column

Rows were built with f"{label:<11}{value}", so a value that wrapped continued at column 0:

Printer     Bambu Lab P1S, 0.4mm
nozzle
Material    PLA · Quality:
standard (0.20mm) · Supports: no

nozzle and standard (0.20mm) … read like field names. They now render through one shared Rich grid (bambu_cli/tui/widgets/summary.py) used by both the prepare screen and the confirmation dialog, so continuations indent under the value.

Every cell is wrapped in rich.text.Text, the same idiom as status_panel.py / ams_panel.py / confirm.pydownload/naming.py sanitizes <>:"/\|?* but not brackets, so a [remix] in a Printables file name reaches these sinks and would otherwise be eaten as markup (or raise MarkupError on a [/x] shape).

One disclosed behaviour change: the shared grid's value column uses overflow="fold" where Rich defaults to "ellipsis", so a long file name in the confirm dialog now wraps in full instead of being cut short. That dialog is the only screen that starts a physical print; it should not abbreviate the name of the file it is about to print. Everything else about its rendering is byte-identical.

Scope

Layout and presentation only. Untouched: temp-workdir ownership and cleanup, the _material_touched guard against a late AMS read overriding a manual pick, the AMS detect worker, the settings handoff, the confirm handoff. confirm.py remains the single confirm=True call site.

A guard that had gone vacuous

test_presliced_3mf_shows_material_not_applied_caveat asserted "Material PETG" not in preview — a spelling coupled to the old f-string padding. Once the grid landed it passed unconditionally, including in the exact regression it exists to catch (the TUI claiming a chosen material was applied to a pre-sliced file it never sliced). It now asserts on the material name, and fails when the production path is sabotaged to emit it.

Verification

Screenshots were a required review step, not text assertions — rendered via export_screenshot()rsvg-convert → PNG and read as images at 80x24, 96x30, 100x30 and 120x34, covering the initial, prepared, error, slice-failure, AMS-detected and bracketed-filename states. Three defects in the first cut were found this way and fixed: the empty results column, the misaligned wrap, and a stair-step right edge on the form groups.

Every new test was sabotage-verified (break the thing, watch it fail, restore).

ruff check            All checks passed!
ruff format --check   88 files already formatted
mypy -p bambu_cli     Success: no issues found in 88 source files
bandit -ll            No issues identified
pytest -m "not live"  1419 passed, 1 deselected — coverage 89.09% (floor 83)
syntax_smoke          ok (185 files)
cli_help_smoke        ok (21 subcommands)
python_compat_smoke   ok
ci_workflow_smoke     ok (21 CLI commands, 88 package modules)
check_layers          Layer boundaries OK (1 allowlisted debt edge)
gen_schemas --check   docs/schemas up to date (26 schemas)
uv build + package_contents_smoke   ok (wheel ships summary.py and styles.tcss)

Not verified locally: the Windows/macOS legs and pip-audit (CI only), and rendering in a real terminal rather than Textual's headless screenshot.

… wraps

The prepare screen stacked everything in one column, so it wasted about 40%
of its width and put the slice estimate — the thing you pressed Prepare to
get — below the fold. The form now sits on the left and what the run produced
on the right, inside a titled Result box that says what will appear in it
rather than showing an empty half screen with a disabled button floating in
the middle of it. Below 100 columns two halves would be too narrow for a
material label, so the screen keeps the single-column layout there and scrolls
a finished (or failed) run into view instead of leaving it off-screen.

The summary rows were laid out with an f-string pad, so a value that wrapped
restarted in the label column: "nozzle" and "Supports: no" read like field
names of their own. Model/printer/material/estimate now render through one
shared Rich grid (bambu_cli/tui/widgets/summary.py) used by both the prepare
screen and the confirmation dialog, with every cell wrapped in Text() so a
"[" in a file name renders verbatim instead of being eaten as markup. The
dialog's long values now fold in full rather than ellipsizing — it is the one
screen that starts a physical print, so it should not abbreviate the name of
the file it is about to print.

Layout only: workdir ownership, the AMS-detection guard, the settings handoff
and the confirm handoff are untouched, and confirm.py remains the single
confirm=True call site.

Also repairs a guard that had gone vacuous: the pre-sliced-3mf test asserted
"Material   PETG" not in the preview, a spelling coupled to the old f-string
padding, so it passed unconditionally once the grid landed and could no
longer catch the TUI claiming a material was applied to a file it never
sliced. It now asserts on the material name and fails when sabotaged.
@DLANSAMA
DLANSAMA merged commit 6aa3e07 into main Aug 5, 2026
6 checks passed
@DLANSAMA
DLANSAMA deleted the fix/tui-prepare-two-column branch August 5, 2026 19:37
DLANSAMA added a commit that referenced this pull request Aug 5, 2026
…#107)

* docs: 0.5.0 truth pass — correct what the docs now get wrong

Audits every tracked doc against the 0.5.0 code and the release commit's own
CI run (31044588411 on 5b08720), and fixes what had gone false or stale.

Actively false, now corrected:
- CONTRIBUTING claimed the four 2026-07 audit gaps were open. All four had
  closed: B.4 cli helper extraction, B.5 single TLS pin helper, the remaining
  JSON schemas (generated now), and the camera bind/pin hardenings. It also
  cited ~82% coverage against a measured 89.
- quality-roadmap said "Phase D schemas largely landed but not complete for
  every command" while docs/api.md and a contract test say the opposite and
  enforce it. Schemas are complete and generated.
- quality-roadmap still framed the TUI as unmerged work on feat/tui that "has
  not reached CI yet". It shipped in 0.5.0 (#97 + #104).
- test-backlog put the mutation floor at 40 (it is 48) and asked for a mutmut
  re-run on slicer/output.py that already happened on 2026-08-04 and disproved
  the prediction it was based on.
- manual's OrcaSlicer detection table listed only the legacy Flatpak app id
  and no Flatpak profile paths at all, contradicting the prose two paragraphs
  above and the real candidate list in config.py.
- docs/plans/interactive-mode-plan.md still read "Draft for implementation"
  and told the reader not to start until 0.4.0 was tagged; plate go shipped in
  0.4.0. Marked implemented, historical text kept as written.

Stale or incomplete:
- Measured numbers refreshed with their source and date: 1419 passing / 1420
  collected, 89.1% branch coverage over 8120 statements on local Linux, and
  the full CI matrix (Windows 88.8% binding, macOS 89.1%, Linux 89.2-89.3%).
  Local and CI figures are cited separately, not reconciled.
- tui/ coverage row was one module behind (#104 added widgets/summary.py):
  now 14 of 18 at 100%, package minimum 95.8%.
- AGENTS module table was missing bambu.py and printer.py; its repo-only list
  was missing docs/releasing.md, docs/README.md and docs/plans/*.
- AGENTS and CONTRIBUTING gate lists omitted pip-audit, gen_schemas --check
  and check_layers, all blocking in the same CI job.
- releasing.md's dev-bump example still said 0.5.0.dev0.
- README told pipx / uv tool users to `pip install 'platecli[tui]'`, which
  installs into the wrong environment; and its agent section did not mention
  that go and tui refuse --json.

No grade was inflated: the scoreboard stays A- / A, coverage 89 against a
target of 92, per-module floors still unenforced, camera residuals still open.

* fix: plate tui — stop the scrollbar clipping which material was detected

On a terminal short enough that the prepare form overflows, #prepare-inputs
grows a vertical scrollbar, and a Textual scrollbar takes real cells rather
than overlaying. The longest radio label is the AMS-detected material —
"PLA — easy, rigid, most models  (detected in AMS)", 49 cells, plus 4 for the
toggle and its padding = 53 — against a column that offered 54. One cell of
slack, which the scrollbar consumed: the label rendered as "(detected in AMS"
with the closing paren shaved off, so the screen silently truncated the one
thing that tells you which filament it found.

Widening the column to 62 leaves 54 cells even while scrolling. Found by
recording the TUI and looking at the frames — every existing assertion passed,
because the label *string* was intact and only its rendering was cut. The new
test asserts the RadioSet has room for its widest button while the scrollbar is
up, and fails when the column goes back to 60.

* chore: untrack the raw hero capture, and ignore raw captures going forward

docs/job-hero.mp4 is the full-length VHS capture of the hero run — 2.3 MB, most
of it the dead air while OrcaSlicer works. The postable cut that scripts/
trim_hero.sh produces from it (docs/job-hero-post.mp4, 427 KB) is already
committed and is what the launch material uses, so the raw file was pure repo
weight and no doc referenced it.

It was never meant to be tracked. It was untracked AND unignored, which the
post-audit gameplan explicitly called out as a trap, and it duly got swept into
#102 — a test-decomposition PR — by a `git add`. Ignoring it closes the trap.

The file stays on disk for re-cutting the video without re-running a real print;
it stays in history, which is fine: frames were extracted and checked and it
leaks no IP, serial, or home path. This only removes it from HEAD.

* docs: record the TUI against a real print, and put each GIF under what it shows

The 0.5.0 headline feature had no imagery at all, and the picture below its
README section was the `plate doctor` GIF — health-check output captioned as if
it illustrated the live dashboard. Meanwhile `plate doctor` is introduced 40
lines earlier with nothing beside it. Both are now under what they actually
show: doctor beside the `plate doctor` line, the new capture under "Watch the
printer while it works".

docs/tui.gif is a real printer mid-print, not a mockup and not --sim: the
dashboard at 13% with nozzle 219.9->220C, bed 60->60C and layer 4/240, then the
job monitor (including its "the print keeps going; nothing here can stop it"
line), then the two-column prepare screen. Frames were extracted and read
before committing — no IP, serial, access code or home path in any of them,
and the shell prompt is bare.

docs/tui.tape records how, and carries the two things that cost time here:
vhs Width/Height are PIXELS, not cells, so the obvious 1100px window is only
~93 columns and silently captures the NARROW single-column prepare layout
rather than the two-column one; and an idle printer records as a static 0%,
so the print has to be running before the tape is rolled.

---------

Co-authored-by: DLANSAMA <258674612+DLANSAMA@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant