v0.14.0
A full rewrite. Same application, same keys, same flags — new internals.
Still a 0.x release: the internals are new enough to want real-world mileage
before anything is called 1.0.
Fixed
- Tainted resources are shown again. Terraform writes the taint marker after
the colon (# address: (tainted)); it is now parsed there. Rrefreshes the state. The binding pointed at a method that was not an
action handler, so the key did nothing.- Typing in the search box no longer moves the tree cursor.
jandkwere
handled application-wide, so searching forjupiterscrolled the tree. Vim
keys are now bindings on the tree widget, active only when it has focus. - Plan and resource output re-wraps when the terminal is resized. Each line
was wrapped once, at the width in effect when it was written, and never
re-wrapped afterwards. Output written before its pane had been laid out fell
back to an 80-column default, which mangled long resource addresses. - Terraform's root outputs are no longer discarded when parsing state.
terraform initactually runs on startup unless-nis passed. The flag
was read but never acted on.- Sensitive values are matched to attributes by name. Substitution used to be
positional, which could not distinguish one redacted attribute from another. - Only genuinely sensitive attributes are treated as secrets. Attributes that
Terraform lists with afalsesensitivity flag were previously included. - Selections survive a search or a refresh. They were held as tree-node
objects, which are discarded whenever the tree is rebuilt; they are now held
as resource addresses. - Listing workspaces no longer crashes when
terraform workspace listfails. - A crash on startup at certain terminal sizes is gone (#92). The old
resize handler touched a widget before it was assigned; layout is now CSS. - File content that looks like a block header no longer breaks parsing
(#91, thanks to @RafaelWO for finding and diagnosing it). A header must now
match# <address>:anchored at both ends, so a binary blob in a
data.local_filecannot be mistaken for one. - Absolute paths to the executable work on Windows.
--executablewas
validated component by component against a pattern that the drive anchor
(C:\) could never match, so every absolute Windows path was rejected — and
spaces were disallowed, which ruled outC:\Program Files\...as well. - Runs on current Textual. The application used APIs removed in Textual 1.0.
Security
- Terraform is never invoked through a shell, and arguments are never re-split
on whitespace. Resource addresses containing spaces, quotes or$are passed
through verbatim. - The
--executablevalue is validated against a conservative pattern and
resolved withshutil.which, so it cannot smuggle in shell syntax. - Plan files are written to a private temporary directory (mode
0700) and
removed when superseded, applied, or on exit. Previouslytftui.planwas
written into the working directory, where a plan — which embeds resource
attributes, including sensitive ones — could be committed by accident. - A plan is discarded once applied, so a stale plan cannot be reapplied against
a state that has moved on. - Usage tracking runs on a background thread, cannot block the UI, and cannot
propagate an exception into the application. - Terraform runs with
TF_INPUT=0and closed stdin, so it can never prompt for
input behind the UI.
Changed
- Destructive confirmations now default to No.
- Data sources are dimmed and cannot be selected — taint, untaint and remove do
not apply to them. - Resources are rendered with HCL syntax highlighting.
- The help screen is grouped by task.
- The plan pane clears Terraform's refresh preamble once the plan proper begins.
- The state tree root shows resource, data-source and tainted counts.
- The layout is expressed in CSS rather than recomputed on every resize event.
Added
- Space on a module selects every resource under it (#82). Expanding and
collapsing moved to Enter, the arrow keys and the digit keys. The selection
count now appears on the pane border. /searches the plan and the resource view (#89), highlighting matches
in place rather than filtering, since a diff or a definition is meaningless
without its context.nandNstep through matches, and the title shows the
position. The tree keeps filtering, as before.-f/--var-filemay be repeated (#62, #85), applied in order. The
files are passed toinitas well as toplan, which is what OpenTofu 1.8+
needs to evaluate variables used in abackendblock.TFTUI_VAR_FILEtakes
a list separated by the platform path separator.- Every flag can now be set through a
TFTUI_*environment variable. -C/--chdirto run against another directory.Ctrl+Aclears the current selection.- Search is case-insensitive.
Release process
Pushing to main now publishes, but only when the version in pyproject.toml
is absent from PyPI and newer than everything published there. The workflow
runs the full suite, publishes to TestPyPI then PyPI through trusted
publishing, and creates the tag and GitHub release with notes taken from this
file. Any other state - an unchanged version, a revert, a downgrade, or PyPI
being unreachable - skips the pipeline. scripts/should_release.py makes that
call and is unit tested.
A filename on TestPyPI can never be replaced, so scripts/check_testpypi.py
compares digests before that upload: an identical re-run passes, a rebuilt file
claiming a taken name stops the release rather than being silently skipped and
leaving TestPyPI serving the older build.
Whether a human approves before PyPI depends on the production environment's
Required reviewers rule, which is a repository setting rather than part of the
workflow.
Internals
src/layout, PEP 621 metadata, hatchling build backend, uv lockfile.- Python 3.10+ (3.9 is end of life). Textual 8, and
requestsis no longer a
dependency — the update check uses the standard library. - Terraform integration is separated from the UI: parsing and plan colouring are
pure functions with no Textual import, and widgets never build argv. - 405 tests: unit, end-to-end through Textual's
Pilot, and an integration suite
driving a real Terraform binary against a bundled example project. - CI across Linux, macOS and Windows on Python 3.10–3.13, plus integration runs
against both Terraform and OpenTofu. mypy --strictandruffclean.
Compatibility
Every keybinding and command-line flag from 0.13 is preserved. The two-word
handle used for usage tracking is derived exactly as before, so returning users
keep their identity.