Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5fe4f56
feat: harden runtime for Jido Console
mikehostetler Aug 20, 2026
4314b83
feat: complete TermUI 1.0 runtime and widgets
mikehostetler Aug 20, 2026
053d3a4
fix: harden runtime and terminal cleanup
mikehostetler Aug 22, 2026
89f5f58
ci: harden release candidate delivery
mikehostetler Aug 22, 2026
b79aec7
ci: use Node 24 action runtimes
mikehostetler Aug 22, 2026
68b8a83
fix: preserve UI acronym in run task
mikehostetler Aug 22, 2026
f709051
ci: adopt Jido package quality platform
mikehostetler Aug 22, 2026
8b56def
test: add release matrix coverage margin
mikehostetler Aug 22, 2026
c2fc464
feat: add bounded stream delivery
mikehostetler Aug 22, 2026
f25725b
feat: parse streaming markdown incrementally
mikehostetler Aug 22, 2026
58c9db7
feat: add reusable advanced UI state
mikehostetler Aug 22, 2026
04bc739
docs: update advanced feature parity
mikehostetler Aug 22, 2026
f6bf6bd
deps: update CI compatibility packages
mikehostetler Aug 22, 2026
1f5cfa9
fix: suppress logger during full-screen sessions
mikehostetler Aug 23, 2026
e994f75
deps: refresh dependency lock
mikehostetler Aug 23, 2026
4e14665
fix: preserve terminal control input in raw mode
mikehostetler Aug 25, 2026
ff2b46b
fix: harden core runtime boundaries
mikehostetler Aug 25, 2026
cf6f6c5
feat: add interactive TermUI showcase
mikehostetler Aug 25, 2026
5f2906a
feat: add live data to showcase
mikehostetler Aug 25, 2026
976d56e
fix: harden runtime and widget behavior
mikehostetler Aug 25, 2026
d894a03
test: raise TermUI coverage above 90%
mikehostetler Aug 26, 2026
f3eb1c9
feat(widgets): add TUIStudio parity controls
mikehostetler Aug 26, 2026
5f5de9a
refactor(structs): limit Zoi to data boundaries
mikehostetler Aug 26, 2026
c2574c8
chore: sync upstream develop
mikehostetler Aug 31, 2026
4514c93
feat(ssh): restore v2 remote sessions (#48)
mikehostetler Aug 31, 2026
7621d7a
feat(app): add v2 compatibility facade (#49)
mikehostetler Aug 31, 2026
d205e22
feat(runtime): add safe v1 aliases (#50)
mikehostetler Aug 31, 2026
6210f63
feat(config): adapt v1 runtime settings (#51)
mikehostetler Aug 31, 2026
aa57f95
feat(widget): add parent-owned routing helpers (#52)
mikehostetler Aug 31, 2026
0f10a86
feat(test): publish deterministic v2 backend (#53)
mikehostetler Aug 31, 2026
a23133e
feat(layout): add pure constraint helpers (#54)
mikehostetler Aug 31, 2026
091f71f
feat(build): make TTY NIF optional (#55)
mikehostetler Aug 31, 2026
1960c99
feat(widget): add honest compatibility facade (#56)
mikehostetler Aug 31, 2026
8e7f439
feat(table): restore sorting and multi-select (#57)
mikehostetler Aug 31, 2026
0f91466
feat(input): add v2 event normalization helpers (#58)
mikehostetler Aug 31, 2026
67cdc17
feat(form): restore validation error flow (#59)
mikehostetler Aug 31, 2026
b2c2664
feat(menu): restore nested navigation (#60)
mikehostetler Aug 31, 2026
a49dbd5
feat(markdown): add optional syntax highlighting (#61)
mikehostetler Aug 31, 2026
2790c79
feat(split-pane): add versioned state restore (#62)
mikehostetler Aug 31, 2026
6aa4c80
feat(snapshot): add optional system providers (#63)
mikehostetler Aug 31, 2026
43ba77e
feat(toast): add explicit expiry commands (#64)
mikehostetler Aug 31, 2026
f4e8a5c
ci(runtime): verify supported baseline (#65)
mikehostetler Aug 31, 2026
9bc2674
docs(architecture): record UI context decision (#66)
mikehostetler Aug 31, 2026
80e5da6
fix(ci): compile cleanly on Elixir 1.20 (#65)
mikehostetler Aug 31, 2026
93eb1a0
fix(build): repair cached native output path (#65)
mikehostetler Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .dialyzer_ignore.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
# MDEx 0.13.5 refers to the type of its optional Lumis dependency.
{"lib/mdex/document.ex", "Unknown type: Lumis.options/0."}
]
9 changes: 8 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
inputs: [
"{mix,.formatter}.exs",
"{config,lib,test}/**/*.{ex,exs}",
"examples/iex_counter/{mix,run}.exs",
"examples/iex_counter/{lib,test}/**/*.{ex,exs}",
"examples/showcase/{mix,run,.formatter}.exs",
"examples/showcase/{lib,test}/**/*.{ex,exs}"
]
]
25 changes: 25 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
version: 2
updates:
- package-ecosystem: "mix"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "elixir"
commit-message:
prefix: "deps"
include: "scope"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "deps"
include: "scope"
107 changes: 71 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,53 +1,88 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group:
push:
branches:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
MIX_ENV: test
permissions:
actions: read
contents: read

jobs:
test:
name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }})
runs-on: ubuntu-latest
ci:
name: CI
uses: agentjido/github-actions/.github/workflows/jido-ci.yml@v5
secrets: inherit
with:
test_matrix: '[{"otp":"28","elixir":"1.18.4"},{"otp":"28","elixir":"1.19"},{"otp":"28","elixir":"1.20"},{"otp":"29","elixir":"1.20"}]'
format_command: mix do format --check-formatted, xref graph --format cycles --fail-above 0
docs_command: mix docs --warnings-as-errors -f html && mix doctor --raise
test_command: mix coveralls --warnings-as-errors

tty-nif-policy:
name: TTY NIF (${{ matrix.mode }}, OTP ${{ matrix.otp }}, Elixir ${{ matrix.elixir }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- elixir: "1.19"
- mode: source
otp: "28"

elixir: "1.18.4"
- mode: source
otp: "29"
elixir: "1.20"
- mode: disabled
otp: "29"
elixir: "1.20"
env:
TERM_UI_TTY_NIF: ${{ matrix.mode }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Elixir
uses: erlef/setup-beam@v1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}
- name: Compile selected native policy
run: |
mix deps.get
mix compile --warnings-as-errors
- name: Verify selected native policy
run: |
if [ "$TERM_UI_TTY_NIF" = "source" ]; then
mix run --no-start --no-compile -e \
'unless TermUI.Terminal.TtyNif.ensure_loaded() == :ok and TermUI.Terminal.TtyNif.loaded?(), do: raise("TTY NIF did not load")'
else
test ! -e _build/dev/lib/term_ui/priv/term_ui_tty_nif.so
mix run --no-start --no-compile test/support/no_nif_backend_probe.exs
fi
- name: Test selected native policy
run: mix test --warnings-as-errors

- name: Cache dependencies
uses: actions/cache@v4
examples:
name: Example (${{ matrix.directory }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
directory: [iex_counter, showcase]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
with:
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}-

- name: Install dependencies
run: mix deps.get

- name: Check formatting
run: mix format --check-formatted

- name: Compile with warnings as errors
run: mix compile --warnings-as-errors

- name: Run tests
run: mix test
otp-version: "29"
elixir-version: "1.20"
- name: Compile and test example
working-directory: examples/${{ matrix.directory }}
run: |
mix deps.get
mix deps.unlock --check-unused
git diff --exit-code -- mix.lock
mix test --warnings-as-errors
59 changes: 59 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Release

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
operation:
description: "Release operation: auto, prepare, or publish"
required: false
type: choice
default: auto
options:
- auto
- prepare
- publish
tag_name:
description: "Optional v-prefixed tag for publish simulation"
required: false
type: string
default: ""
dry_run:
description: "Dry run (no git push, no tag, no GitHub release, no Hex publish)"
required: false
type: boolean
default: false
hex_dry_run:
description: "Hex dry run only (run all git/release steps, but skip actual Hex publish)"
required: false
type: boolean
default: false
skip_tests:
description: "Skip tests before release"
required: false
type: boolean
default: false
version_override:
description: "Optional bare SemVer override (for example 1.2.3, not v1.2.3)"
required: false
type: string
default: ""

permissions:
actions: write
contents: write

jobs:
release:
name: Release
uses: agentjido/github-actions/.github/workflows/jido-release.yml@v5
with:
operation: ${{ inputs.operation || 'auto' }}
tag_name: ${{ inputs.tag_name || '' }}
dry_run: ${{ inputs.dry_run || false }}
hex_dry_run: ${{ inputs.hex_dry_run || false }}
skip_tests: ${{ inputs.skip_tests || false }}
version_override: ${{ inputs.version_override || '' }}
secrets: inherit
21 changes: 21 additions & 0 deletions .github/workflows/review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Jido Review

on:
pull_request:
branches:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
actions: read
contents: read
issues: write
pull-requests: write

jobs:
review:
name: Jido Review
uses: agentjido/github-actions/.github/workflows/jido-review.yml@v5
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TermUI Agent Instructions

TermUI is a small Elm-style terminal runtime for Elixir and the BEAM.

Keep these runtime boundaries:

- One runtime process owns application state and update order.
- Application views return one complete `TermUI.Frame`.
- One backend owner controls terminal input, output, size, capabilities, and cleanup.
- Widgets are pure. The parent application owns widget state and effects.
- Commands are data values. Do not run effects in widget code.

Use `develop` as the pull request target. Preserve the public `TermUI` namespace
and the Jido Console runtime contract. Run `mix quality` and `mix coveralls`
before a commit. Terminal lifecycle changes also need a real terminal check.

Use Conventional Commits. Never mention an AI assistant in a commit message.
73 changes: 70 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added a tested interactive showcase for live widgets, input, rich content,
BEAM snapshots, and the TermUI architecture.
- Added pure checkbox, toggle, radio group, select, spinner, and breadcrumb
widgets, with a Controls page in the showcase.
- Added pure row, column, fixed-grid, inset, placement, and mouse-region layout
helpers.
- Added complete raw control-byte input on OTP 28 and OTP 29. A small native
terminal layer now passes Ctrl+O, Ctrl+C, Ctrl+S, and Ctrl+Q to applications
and restores the original terminal flags during shutdown.
- Restored the general widget feature set as parent-owned pure widgets under
`TermUI.Widget`.
- Added an MDEx Markdown renderer and scrollable Markdown viewer.
- Added unified and side-by-side terminal diff views.
- Added frame overlay composition for widget frames.
- Added Zoi schemas for public boundary data, including cells, styles, frames,
events, commands, clipboard operations, mouse regions, selections, and table
columns.
- Added bounded clipboard commands that run through the serialized backend owner.
- Added pure Unicode grapheme selection for single-line and multiline text input.
- Added pure mouse regions, local-coordinate routing, hover state, and drag tracking.
- Added local mouse behavior for interactive widgets, scrollbars, and split panes.

### Changed

- Private backend, parser, stream, and widget state now uses plain structs.
Zoi remains at public data boundaries instead of defining every struct.
- Boundary schemas now validate colors, cell invariants, frame bounds, nested
cells, and command payloads.
- Process, stream, supervision, and cluster widgets now render data snapshots
supplied by the parent application. They do not own effect processes.
- Markdown rendering and incremental documents now share one internal parser
dependency, which removes their cross-reference cycle.
- Buttons, lists, menus, tabs, trees, blocks, and dialogs now support richer
decoration, disabled-item navigation, and child-frame composition.

### Fixed

- Unsupported terminal control sequences no longer emit their parameter bytes
as application text.
- Frame mutations preserve wide-grapheme pairs and cannot place a wide
grapheme past the frame boundary.
- Timed-out terminal command tasks are now stopped.
- SGR mouse releases retain their button, and X10 releases are no longer
reported as presses.

## [1.0.0-rc] - 2026-08-19

### Changed

- Replaced the component process system with one Elm application runtime.
- Made `TermUI.Frame` the only application render value.
- Moved terminal lifecycle, input, output, size, cursor, and capabilities into backends.
- Split printable text from named and modified key events.
- Replaced effect tuples with `TermUI.Command` data.
- Replaced process widgets with parent-owned pure widgets.

### Removed

- Removed component servers, registries, supervisors, event routers, and focus managers.
- Removed legacy input handlers, render nodes, renderer buffers, and duplicate widget namespaces.
- Removed the SSH backend until it can own a complete terminal session lifecycle.

See `guides/migration-1.0.md` for the replacement map.

## [0.2.0] - 2024-12-01

### Added
Expand Down Expand Up @@ -95,6 +161,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Developer guides (architecture, runtime, rendering, events, buffers, terminal, creating widgets)
- Widget examples with READMEs

[Unreleased]: https://github.com/pcharbon70/term_ui/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/pcharbon70/term_ui/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/pcharbon70/term_ui/releases/tag/v0.1.0
[Unreleased]: https://github.com/mikehostetler/term_ui/compare/v1.0.0-rc...HEAD
[1.0.0-rc]: https://github.com/mikehostetler/term_ui/compare/v0.2.0...v1.0.0-rc
[0.2.0]: https://github.com/mikehostetler/term_ui/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/mikehostetler/term_ui/releases/tag/v0.1.0
Loading
Loading