Skip to content

[#139] Specified the structure of a screen, and rebuilt the library on it. - #143

Open
AlexSkrypnyk wants to merge 110 commits into
mainfrom
feature/139-validation-naming
Open

[#139] Specified the structure of a screen, and rebuilt the library on it.#143
AlexSkrypnyk wants to merge 110 commits into
mainfrom
feature/139-validation-naming

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jul 30, 2026

Copy link
Copy Markdown
Member

Closes #139

Summary

Issue #139 started as a question about validation method names and ended as the library's specification and the rebuild that follows it. docs/content/specification.mdx defines the model precisely - four levels (Screen, Layout, Region, Block), seventeen capabilities each owned by exactly one level, per-block theme elements, keys travelling inward and drawing travelling outward, and the four capabilities that survive with no screen at all. This branch then makes that model the only implementation: the parallel legacy path (Engine, PanelController, Model\Field/Panel/FormDefinition, the 50-method ThemeInterface monolith) is deleted, and every documented feature - conditional logic, derivation, discovery, provenance, modals, external editors, custom key bindings, themes, translations - now runs on the block tree.

Changes

Specification and nomenclature

  • docs/content/specification.mdx - the source of truth: hierarchy, capabilities, the ownership matrix, per-level contracts, theme elements, driving and headless behaviour, and a what-is-built table with nothing left unbuilt.
  • docs/content/fields/anatomy.mdx - records the shipped element contract for window chrome, view mode and edit mode.
  • The widget vocabulary is gone: DrevOps\Tui\Widget\SelectWidget is DrevOps\Tui\Field\Select, docs routes moved from /widgets/* to /fields/*, and every asset, playground and test follows.

Structure

  • Block\Capability declares each block-level capability as an interface with a trait carrying shared behaviour; a data-provider test pins every block's implemented set to the specification's table.
  • Screen\Layout holds LayoutInterface, AbstractLayout and the shipped layouts; LayoutManager discovers them from its own directory and registers consumer layouts. Router is KeyRouter.
  • Block\Field is the one field: declaration (entries, bounds, conditions, derivation, discovery, validation) plus runtime (modes, draft, value). The former widgets are its capture engines. The builders write this tree directly, and Form::root() is the canonical output.

Behaviour

  • Screen\Collector is the headless path at exact engine parity: source precedence, provenance, update mode, dynamic entries, the settle fixpoint, refusal order and the Answers summary - proven byte-identical by a dumped parity harness before the engine was deleted. CollectException sits beside CancelException and InterruptException.
  • Screen\ScreenController drives the interactive session: focus walk, capture through the engines, descend and modal overlay, re-settling after every accepted edit, region scrolling, live breadcrumb and legend, help, external editor, standalone fields, fullscreen chrome and the resize guard. ScreenTester tests it deterministically beside TuiTester.
  • The legend keeps whole hints against the theme's content width - a line out of room drops its last hint rather than cutting a word in half.

Theme

  • ThemeInterface is two methods: contentWidth() and keyGlyph(). Everything else lives on per-block element interfaces (plus ChromeElementsInterface for the frame and PrimitiveElementsInterface for the output primitives), implemented by AbstractTheme as the unstyled floor and by DefaultTheme in full.
  • Support is declared through Theme\Capability: ColorSchemeCapableInterface, UnicodeCapableInterface, OccupyCapableInterface, DimCapableInterface, MarkdownCapableInterface, each backed by a trait.
  • Tui::theme() also takes a closure over ThemeBuilder, patching elements - each glyph with its Unicode form and ASCII stand-in - on whatever theme is current.

Facade

new Tui(Form $form), run(), collect(), layout(), theme(), keys(), color(), unicode(), markdown(), fullscreen(), footer(), clearOnExit(), translator(), schema(), agentHelp(), validate(), root(), registry(), and the progress()/output() primitives unchanged.

Translations, playgrounds, docs, assets

  • Ukrainian at full parity: 33 renderings added, plural forms complete, and mutation-tested guards that derive the expected key set from the code - including the month names and button labels a literal scan can never find.
  • All 82 playground scripts verified against the final surface, plus three new ones: a registered custom layout, the element override DSL, and region flow.
  • Every docs page audited line by line against the source; a new layouts page; architecture diagrams re-traced from the live code.
  • All 661 SVG assets regenerated or re-recorded on the new driver, audited clean, key statics confirmed visually.

Scale

110 commits, 1492 files, +32,884 / -18,544 lines. 2,592 tests and 5,682 assertions, PHPStan level 9 clean, full matrix green on every push.

Before / After

Before: two parallel implementations
────────────────────────────────────

  Builder ──▸ Model\Field / FormDefinition
                  │
      ┌───────────┴────────────┐
      ▼                        ▼
  Engine (headless)      PanelController (interactive)
      │                        │
      └────────┬───────────────┘
               ▼
     ThemeInterface: ~50 atoms, one class knows everything

  + a second, spec-shaped skeleton (Screen/, Block/) driving
    one playground script and nothing else


After: one tree, one path each way
──────────────────────────────────

  Builder\Form ──▸ Block\Panel ▸ Layout ▸ Region ▸ Block\Field
                        │
            ┌───────────┴────────────┐
            ▼                        ▼
   Screen\Collector          Screen\ScreenController
   (headless: Collect,       (keys inward via KeyRouter,
   Constrain, Reject,         drawing outward via
   Depend survive)            ScreenRenderer)
                                     │
                                     ▼
              elements per block ▸ AbstractTheme floor
              ThemeInterface: contentWidth() + keyGlyph()

  Capabilities: 17, each owned by one level, pinned by test
  Deleted: Engine, PanelController, Model\{Field,Panel,
  FormDefinition,Modal}, both bridging factories - 12k lines

…trait.

Ten interfaces under 'Block\Capability' state the spec's block-level capabilities in observable terms, and three traits - Bind, Depend, Focus - carry the implementations more than one block shares. Each shipped block now implements exactly the set the specification's table grants it, and a data-provider test holds the two in lockstep. 'AbstractBlock' carries the one thing every kind shares: narrowing the theme to the block's own elements, or failing with a type error instead of a blank line.

Layouts moved into 'Screen\Layout' behind a 'LayoutInterface', and 'LayoutManager' no longer names the shipped ones: it discovers every instantiable layout in its own directory and derives the name from the class. 'Router' is 'KeyRouter', since keys are all it routes.

'Field::error()' became 'refusal()' so Field and Actions share one Reject vocabulary, and Actions selects buttons with 'select()' now that 'focus()' belongs to the block-level capability.
…nts.

'AbstractTheme' is the floor the specification promises: it implements every element interface by handing strings back unstyled, so a theme that declares nothing still draws. 'DefaultTheme' now extends it and declares its support through 'Theme\Capability': 'ColorSchemeCapableInterface' merges colour and dark/light into the one declaration they are decided by, and both it and 'UnicodeCapableInterface' carry traits so a consumer theme composes the implementation instead of rewriting it.

The per-block element interfaces now cover every element the anatomy names - the frame's border and overflow marker land on 'ChromeElementsInterface', since neither is something a block could ask for - and 'DefaultTheme' implements each by delegating to the atom that draws it today, which is what keeps the five shipped themes and every rendered asset byte-identical.

'ThemeBuilder' collects per-block element overrides - each glyph as its Unicode form and ASCII stand-in - and the delegating elements consult them, so overriding a separator no longer means subclassing the theme.
'Block\Field' now declares everything a field can: entries with headings, dividers and disabled reasons, the three dynamic entry sources with their settle lifecycle, every bounds object, the picker constraints, templates, derivation, discovery, environment names, and the required/validate/transform semantics - refusing in the same order the engine refuses today. Entries are a list of 'Option' objects carrying their own values, so a numeric-string value never meets an array key and never coerces.

'Depend' now takes a closure or a 'ConditionInterface' and answers against the answers collected so far, on every block that claims it. 'Panel' gained its description, buttons and preload step, 'Markup' its bordered and table presentations through the one card renderer, and 'Progress' its determinate declaration and the advancing label.
…follows.

A field's edit mode is now its kind engine: 'open()' builds it from the declaration through the factory's block path, 'capture()' hands it each key and promotes its value through the block's own refusal chain, and a refused value re-seeds the engine so correcting continues from what is on screen.

Bind is the key system rather than a placeholder: a block resolves its scope against an injectable 'KeyMap', a settled field binds nothing, a nested panel binds nothing until entered, and the router simply asks each level in turn - focused block, panel, screen - so an open engine's Space arrives with no special case. The legend advertises the innermost binder's hints, translated at draw time, and the assembler's hardcoded entries are now derived.

The renderer draws its frame through 'chromeBorder()', writes a scrolling region's overflow through 'chromeOverflowMarker()' without spending a row, and marks focus down the selector column.
…the field vocabulary.

PHPUnit's coverage run refuses a trait named through 'CoversClass', which only the coverage pipeline surfaces - plain runs pass. The sidebar test and five formatted pages still spoke the old vocabulary.
@github-actions

This comment has been minimized.

…tion from it.

The one DSL now constructs blocks as the declaration is written: a panel builder opens a 'Block\Panel', each field call places a 'Block\Field' - or a 'Markup' or 'Progress' where that is what the answer needs - into the current region, and 'seal()' assembles the declarations stated in parts so every contradiction still refuses at build time with the same message. 'Form::root()' exposes the canonical tree; 'Form::build()' still answers the engine and the controller through 'DefinitionFactory', which walks the regions and maps every declared property onto the definition it used to be.

The panel DSL now speaks the specification's layout language - a named layout resolved through the manager, blocks targeted into regions by name - while the integer grid keeps meaning the sub-panel arrangement it always has. The skeleton builders under 'Screen' are gone; the playground builds through the real DSL end to end.

'Progress::work()' takes the reporter its declaration documents, and every rendered asset in the corpus is byte-identical through the derived definition.
@github-actions

This comment has been minimized.

…t answer.

The collector now settles a form the way the engine does, stage for stage: entries load once, values resolve supplied-then-detected-then-declared against the answers so far, transforms normalize what was supplied, derivations pin what arrived from outside, and a fixpoint loop lets entries, conditions and fix-ups settle each other before refusals are measured - required, then shape, then the field's own reasons. Provenance lands per answer, and the summary formats as it always has.

The facade's headless half - collect, validate, schema, agent help - runs on the collector and the block tree; the schema walkers read blocks now, emitting the same bytes. Type acceptance and value reconciliation moved onto 'Block\Field' where the rest of its refusal chain already lives. A parity harness dumped every headless surface over the fixture forms on both implementations: identical.

The interactive half still runs the engine, untouched, until the screen driver replaces it.
@github-actions

This comment has been minimized.

…wing outward.

'ScreenController' owns the session: the assembler builds the screen around the form's root, the collector seeds values and provenance before the first frame, and the loop paints, reads one key, and hands it to the router - which finishes everything except the two things a router cannot: ending the session and running work against the terminal. Submit returns the answers, cancel and interrupt raise the exceptions the playgrounds document, and the frame clears on the way out when asked.

The actions and the refusal line live in the root panel's own region, which is why they sit beside the entered panel and stay behind when you descend. Scrolling follows the focused row through whichever region holds it. 'ScreenTester' runs the driver deterministically over scripted keys and keeps its frames assertable even when the session ends by exception; fifty-four scenarios pin the core loop.
…and prose.

Accepting an edit now re-settles the form through the collector's own stages, so a dependent row appears the moment its condition turns true and leaves from under the cursor without stranding it. A modal panel overlays the dimmed screen with its own buttons, keeping and restoring a snapshot as it closes; the external editor suspends the session exactly as before; a standalone field takes the whole frame and hands it back.

The chrome seams are filled: banner and version behind a keypress, the fullscreen frame anchored by its alignments with the resize notice guarding the minimum, the theme's background washed under it all. 'Block\Prose' routes the markdown subset through the theme's atoms for markup and descriptions alike. A display-only row no longer takes the cursor, quit answers to the innermost binder, and an entered panel is sized against its region siblings so the overflow marker tells the truth. The theme grew accessors only - occupancy, dimming and markdown as capability interfaces.
…ir, replacing descent.

A settled row now reads its answer the way the legacy row reads it - masked passwords, translated yes and no, a drawn rating scale, loading rows saying so, lists joined by the value separator - composed line by line with continuations aligned under the value column. The provenance badge sits in its own column at the frame's edge, aligned against the same 'contentWidth()' every other frame-wide renderer lays out to, rather than teaching a block how wide the world is.

Spacing is the region flow's concern: padded air goes between the rows a region holds, a block with nothing to say costs no row, and the driver measures scrolling through the renderer's own arithmetic so counted rows and drawn rows come from one rule. Entering a panel replaces the view - the parent's rows and the root's buttons stay behind, the trail carries the way back.
'Tui::run()' and 'interact()' now drive 'ScreenController', with every facade option carried across - themes and their display options, key presets, colour and glyph forcing, markdown, fullscreen, the footer, clearing, the translator, banners and update mode - and the interactive-or-headless decision unchanged. 'TuiTester' keeps its public surface and drives the new path, folding the driver's cancel and interrupt back into the flags its callers read.

The last documented looks landed first: a settled row's description under the answer, a nested panel's row drawn as the way in - selector, title, descend mark, guidance and a summary of what is inside - the legend advertising quit and help where the legacy footer did, and the sub-panel grid dealt into equal columns by the region that holds it. The switch also surfaced three behaviours only the old driver carried - reusable handler behaviour offered per field, panel row loaders paid on entry behind a loading frame, and live query sources re-answering per read - all now the screen path's own.
@github-actions

This comment has been minimized.

… collector.

The engine, the panel controller, the navigator, the model's field, panel, definition and modal, and both bridging factories are gone - twelve thousand lines that said the same thing twice. The collector is the headless path, the screen controller the interactive one, and both read the block tree the builder writes. 'CollectException' sits beside the cancel and interrupt endings, since all three are ways a collection ends without a complete answer set, and it keeps the message formats translations already resolve.

The facade takes a 'Form', hands back 'root()', and its fluent surface is unchanged. What only the deleted half remembered was re-homed first: labels, titles and descriptions translate where the legacy theme translated them, the form-level grid and button declarations reach the root through the builder path, the cursor moves four ways across a grid of windows as the docs promise, and the renderer measures a grid the way it draws one.

Every behavioural assertion the deleted tests carried was either ported onto the surviving surface or retired as duplicate coverage of one behaviour through two drivers.
@github-actions

This comment has been minimized.

…ot list.

A field hanging off an earlier answer indents one step per link again: the builder stamps each field's depth from the condition graph it declares - a closure names no target, so it stamps flat - and the row draws the gutter through a 'fieldIndent()' element, so the floor stays flush and the default theme honours the option it always carried. Continuations, descriptions and refusal lines align under the stepped label because they all measure the label as drawn.

A nested panel's one-line summary counts a list it has no room to spell out - more than three picks reads as a plural-formed count, through 'formatPlural' so the Ukrainian one, few and many forms all reach the screen - while a field's own row keeps joining its values, which is the only place the old renderer ever did either.
…re else.

'ThemeInterface' is down to the two things genuinely shared by everything drawn: the one content width every layout runs to, and the theme's spelling of the keyboard. Every field engine now styles through the field elements - entries carrying picked, focused and exclusive as the facts they are - prose spans through the markup elements, and the output primitives through their own elements interface, which keeps 'renderCard()' and 'renderTable()' each the single renderer behind the standalone piece and its in-panel twin.

Atoms with one consumer folded into that element; the hues shared by several survive as a protected palette written once. The named themes collapsed onto it - most are now an accent, a value and a border. 'Tui::theme()' also takes a closure over 'ThemeBuilder', so a consumer patches the elements of whatever theme is current, and the override DSL now demonstrably reaches the screen in both display modes.
'Tui::layout()' resolves the name through the layout manager at declaration time, so a typo fails where it was written rather than mid-session, and the tester passes the name through. The assembler furnishes only the regions the named layout keeps a place for - a two-column screen simply shows no trail rather than being refused for not being the default - and the controller keeps live but undrawn furniture for the pieces that lost their region. Help, the standalone stage and a dialog keep their own three-band arrangement, since each is one thing to read whatever the session behind it uses.
@github-actions

This comment has been minimized.

…s new powers.

Every script runs headlessly and does what its comments promise; display options now speak in enum cases where they name a closed set, imports sort where they had drifted, and the agent-help demo describes the schema it actually returns. Byte-identical output before and after, so the recorded interactions still replay.

Three new demonstrations: a consumer layout registered and picked by name for a panel and for the screen, the theme element DSL patching separators, markers and the caret with each glyph's ASCII stand-in beside it, and one screen's header flowing its two blocks stacked and across. The playground README lists what was missing, including the specification screen itself.
The catalog audit found what a literal scan could never see: the twelve month names the calendar formats before translating, and the default button labels translated at the frame. Both derive into the guard now, so the template must equal the emitted set computed from the code rather than a hand-kept list. Every locale catalog must carry exactly the template's keys, preserve every placeholder, and state every plural form its own rule can ask for - nine mutations of the catalogs all fail.

Ukrainian gained thirty-three renderings and lost thirteen orphans of the old driver's vocabulary. The legend fragment that read half-English joins key and action the way Ukrainian does - no preposition - and the sessions prove it end to end: the closed and open legends, help and quit, a refusal beside a provenance badge, the count phrasing through one, few and many, the calendar's heading and the localized headless summary.
The handler spy's last caller left with the engine suite; the locale-forced run and the coverage pass hold at the levels the refactor started from.
@github-actions

This comment has been minimized.

The specification catches up with what it specified: the real capability interfaces and their traits, an example theme that compiles, the override DSL typed as the builders it hands you, three shipped layouts, and a what-is-built table with nothing left in not-built-yet. Anatomy records the shipped element contract in place of its proposal tables. Themes, testing, panels, key bindings, configuration, the headless pages and the agent schema are audited line by line against the source - the collect ending is 'CollectException' everywhere, the help key opens the field's own help, and the harness page introduces the screen tester beside the facade one.

Layouts get a consumer page of their own under Forms - regions, sizing, scrolling, flow, writing and registering one - and the architecture diagrams are re-traced from the live code: one tree, drawing outward, keys inward, and the three ways a session ends. US spelling swept across the prose; two honest limits documented rather than papered over.
@github-actions

This comment has been minimized.

…ole.

Every asset shows the new driver: outer box only, descriptions stepped to the value column, focus carried by the selector, radio pairs where one answer is exclusive, nested rows wearing their descend mark and summary. The thirty-five recorded jobs replay on re-anchored expect bodies - the montage's pause gate takes its two Enters, the vim story no longer detours through an overlay that no longer exists, and the field specs hand each display mode a fresh form so no run opens on another run's answer.

The legend now keeps whole hints against the one content width everything lays out to: a line out of room drops its last hints rather than cutting a word in half, which is what lets the Ukrainian recording run at the full frame width. The table demo spells its header the way the prose does, in the spec, the page and the playground alike. Audited clean at 661 assets, every dark one with its light twin, and the key statics confirmed by eye.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Code Coverage Report:
  2026-08-03 01:08:51

 Summary:
  Classes: 78.77% (115/146)
  Methods: 96.36% (1405/1458)
  Lines:   97.87% (5465/5584)

DrevOps\Tui\Answers\Answer
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Answers\Answers
  Methods: 100.00% (11/11)   Lines: 100.00% ( 23/ 23)
DrevOps\Tui\Answers\Provenance
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Answers\SummaryFormatter
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 22/ 22)
DrevOps\Tui\Answers\ValueFormatter
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Block\AbstractBlock
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Block\Actions
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 22/ 22)
DrevOps\Tui\Block\Breadcrumb
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Block\Capability\BindCapableTrait
  Methods:  80.00% ( 4/ 5)   Lines:  90.00% (  9/ 10)
DrevOps\Tui\Block\Capability\DependCapableTrait
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Block\Capability\FocusCapableTrait
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Block\Field
  Methods:  96.92% (126/130)   Lines:  95.36% (370/388)
DrevOps\Tui\Block\Legend
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 39/ 39)
DrevOps\Tui\Block\Markup
  Methods: 100.00% (12/12)   Lines: 100.00% ( 28/ 28)
DrevOps\Tui\Block\Panel
  Methods:  89.19% (33/37)   Lines:  94.17% (113/120)
DrevOps\Tui\Block\Progress
  Methods: 100.00% (13/13)   Lines: 100.00% ( 35/ 35)
DrevOps\Tui\Block\Prose
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Block\Tree
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Builder\FieldBuilder
  Methods:  98.46% (64/65)   Lines:  99.01% (201/203)
DrevOps\Tui\Builder\Form
  Methods:  82.61% (19/23)   Lines:  93.16% (109/117)
DrevOps\Tui\Builder\LayoutGuard
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Builder\PanelBuilder
  Methods: 100.00% (31/31)   Lines: 100.00% ( 73/ 73)
DrevOps\Tui\Condition\CompositeCondition
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Condition\Condition
  Methods: 100.00% (10/10)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Derive\Derive
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 10/ 10)
DrevOps\Tui\Derive\Deriver
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 13/ 13)
DrevOps\Tui\Derive\Transform
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Discovery\AbstractDiscover
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Discovery\Dotenv
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Discovery\JsonValue
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Discovery\PathExists
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Discovery\Scan
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Field\AbstractField
  Methods:  91.30% (21/23)   Lines:  97.14% ( 68/ 70)
DrevOps\Tui\Field\Calendar
  Methods: 100.00% (13/13)   Lines: 100.00% ( 52/ 52)
DrevOps\Tui\Field\Capability\CompletionCapableTrait
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Field\Capability\FilterCapableTrait
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Field\Capability\OptionsCapableTrait
  Methods: 100.00% (10/10)   Lines: 100.00% ( 36/ 36)
DrevOps\Tui\Field\Capability\PagingCapableTrait
  Methods:  80.00% ( 4/ 5)   Lines:  94.74% ( 18/ 19)
DrevOps\Tui\Field\Capability\PlaceholderCapableTrait
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Field\Capability\QueryOptionsCapableTrait
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 36/ 36)
DrevOps\Tui\Field\Capability\SearchCapableTrait
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  4/  4)
DrevOps\Tui\Field\Capability\SelectionBoundedTrait
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 11/ 11)
DrevOps\Tui\Field\Capability\SelectionCapableTrait
  Methods: 100.00% (14/14)   Lines: 100.00% ( 95/ 95)
DrevOps\Tui\Field\Capability\TextEditCapableTrait
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 35/ 35)
DrevOps\Tui\Field\Confirm
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 24/ 24)
DrevOps\Tui\Field\FieldFactory
  Methods: 100.00% (12/12)   Lines: 100.00% ( 60/ 60)
DrevOps\Tui\Field\FilePicker
  Methods: 100.00% (33/33)   Lines: 100.00% (189/189)
DrevOps\Tui\Field\MatchResult
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Field\MatchTier
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Field\Matcher
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 73/ 73)
DrevOps\Tui\Field\Number
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 36/ 36)
DrevOps\Tui\Field\Password
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 47/ 47)
DrevOps\Tui\Field\PasswordDisplay
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Field\Pause
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 10/ 10)
DrevOps\Tui\Field\Rating
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 25/ 25)
DrevOps\Tui\Field\Reorder
  Methods: 100.00% (11/11)   Lines: 100.00% ( 58/ 58)
DrevOps\Tui\Field\Search
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 17/ 17)
DrevOps\Tui\Field\Select
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Field\Suggest
  Methods: 100.00% (19/19)   Lines: 100.00% ( 75/ 75)
DrevOps\Tui\Field\Template
  Methods: 100.00% (14/14)   Lines: 100.00% ( 63/ 63)
DrevOps\Tui\Field\Text
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 18/ 18)
DrevOps\Tui\Field\Textarea
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 49/ 49)
DrevOps\Tui\Field\Toggle
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Handler\HandlerRegistry
  Methods:  85.71% ( 6/ 7)   Lines:  95.45% ( 21/ 22)
DrevOps\Tui\Input\Binding
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Input\DefaultKeyMap
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 48/ 48)
DrevOps\Tui\Input\Hint
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  2/  2)
DrevOps\Tui\Input\Key
  Methods:  87.50% ( 7/ 8)   Lines:  63.64% (  7/ 11)
DrevOps\Tui\Input\KeyMap
  Methods: 100.00% (11/11)   Lines: 100.00% ( 61/ 61)
DrevOps\Tui\Input\KeyMapManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Input\KeyParser
  Methods:  85.71% ( 6/ 7)   Lines:  98.95% ( 94/ 95)
DrevOps\Tui\Input\Scope
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 17/ 17)
DrevOps\Tui\Input\ScopedKeyMap
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  4/  4)
DrevOps\Tui\Input\VimKeyMap
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Model\Buttons
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Model\DateBounds
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 28/ 28)
DrevOps\Tui\Model\FieldType
  Methods:  88.89% ( 8/ 9)   Lines:  82.22% ( 37/ 45)
DrevOps\Tui\Model\FilePickerConstraints
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 53/ 53)
DrevOps\Tui\Model\NumberBounds
  Methods:  83.33% ( 5/ 6)   Lines:  95.24% ( 20/ 21)
DrevOps\Tui\Model\Option
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Model\SelectionBounds
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 23/ 23)
DrevOps\Tui\Model\TableSpec
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Model\Template
  Methods: 100.00% (19/19)   Lines: 100.00% ( 67/ 67)
DrevOps\Tui\Model\Weekday
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Primitive\Output
  Methods: 100.00% (16/16)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Primitive\Progress
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 28/ 28)
DrevOps\Tui\Primitive\ProgressReporter
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
DrevOps\Tui\Render\Ansi
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 22/ 22)
DrevOps\Tui\Render\Box
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\ExternalEditor
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Render\Markup
  Methods: 100.00% (10/10)   Lines: 100.00% ( 69/ 69)
DrevOps\Tui\Render\MarkupSegment
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Render\Overlay
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 24/ 24)
DrevOps\Tui\Render\Scroller
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 14/ 14)
DrevOps\Tui\Render\Table
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 64/ 64)
DrevOps\Tui\Render\Terminal
  Methods:  95.65% (22/23)   Lines:  96.97% ( 64/ 66)
DrevOps\Tui\Render\TerminalControl
  Methods: 100.00% (11/11)   Lines: 100.00% ( 11/ 11)
DrevOps\Tui\Render\Viewport
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Resolver\EnvNameResolver
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Resolver\InputResolver
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Schema\AgentHelp
  Methods:  75.00% ( 3/ 4)   Lines:  98.57% ( 69/ 70)
DrevOps\Tui\Schema\DefaultResolver
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Schema\OptionsResolver
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Schema\SchemaGenerator
  Methods:  66.67% ( 2/ 3)   Lines:  98.00% ( 49/ 50)
DrevOps\Tui\Schema\SchemaValidator
  Methods:  85.71% ( 6/ 7)   Lines:  97.44% ( 38/ 39)
DrevOps\Tui\Screen\Assembler
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 11/ 11)
DrevOps\Tui\Screen\Collector
  Methods: 100.00% (30/30)   Lines: 100.00% (203/203)
DrevOps\Tui\Screen\KeyRouter
  Methods:  85.19% (23/27)   Lines:  92.25% (131/142)
DrevOps\Tui\Screen\Layout\AbstractLayout
  Methods:  71.43% ( 5/ 7)   Lines:  82.05% ( 32/ 39)
DrevOps\Tui\Screen\Layout\DefaultLayout
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  4/  4)
DrevOps\Tui\Screen\Layout\LayoutManager
  Methods:  71.43% ( 5/ 7)   Lines:  56.67% ( 17/ 30)
DrevOps\Tui\Screen\Layout\TwoColumnLayout
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Screen\Region
  Methods:  70.59% (12/17)   Lines:  69.70% ( 23/ 33)
DrevOps\Tui\Screen\Screen
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Screen\ScreenController
  Methods:  95.16% (59/62)   Lines:  99.05% (416/420)
DrevOps\Tui\Screen\ScreenRenderer
  Methods:  95.45% (21/22)   Lines:  99.45% (180/181)
DrevOps\Tui\Testing\ArrayKeyStream
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Testing\BufferedTerminal
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Testing\FieldRunner
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Testing\KeyEncoder
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 22/ 22)
DrevOps\Tui\Testing\ScreenTester
  Methods: 100.00% (24/24)   Lines: 100.00% ( 71/ 71)
DrevOps\Tui\Testing\TuiTester
  Methods:  93.33% (14/15)   Lines:  95.45% ( 42/ 44)
DrevOps\Tui\Theme\AbstractTheme
  Methods: 100.00% (55/55)   Lines: 100.00% ( 61/ 61)
DrevOps\Tui\Theme\Capability\ColorSchemeCapableTrait
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Theme\Capability\UnicodeCapableTrait
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
DrevOps\Tui\Theme\DefaultTheme
  Methods:  98.25% (112/114)   Lines:  99.40% (334/336)
DrevOps\Tui\Theme\DosTheme
  Methods: 100.00% (12/12)   Lines: 100.00% ( 14/ 14)
DrevOps\Tui\Theme\EmberTheme
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Theme\FrostTheme
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Theme\MidnightTheme
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Theme\MonoTheme
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Theme\Override\BreadcrumbOverrides
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Theme\Override\FieldOverrides
  Methods:  85.71% ( 6/ 7)   Lines:  84.62% ( 11/ 13)
DrevOps\Tui\Theme\Override\Glyph
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Theme\Override\LegendOverrides
  Methods:  66.67% ( 2/ 3)   Lines:  60.00% (  3/  5)
DrevOps\Tui\Theme\Override\Overrides
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Theme\Sgr
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Theme\ThemeBuilder
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Theme\ThemeManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Translation\Translator
  Methods: 100.00% (18/18)   Lines: 100.00% ( 87/ 87)
DrevOps\Tui\Tui
  Methods: 100.00% (30/30)   Lines: 100.00% (101/101)
DrevOps\Tui\Utils\Strings
  Methods:  88.89% ( 8/ 9)   Lines:  97.30% ( 36/ 37)

@AlexSkrypnyk AlexSkrypnyk changed the title [#139] Specified the structure of a screen, and built it. [#139] Specified the structure of a screen, and rebuilt the library on it. Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs review Pull request needs a review from assigned developers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decide whether 'violation' and 'error' both earn their keep in validation method names

1 participant