[#139] Specified the structure of a screen, and rebuilt the library on it. - #143
Open
AlexSkrypnyk wants to merge 110 commits into
Open
[#139] Specified the structure of a screen, and rebuilt the library on it.#143AlexSkrypnyk wants to merge 110 commits into
AlexSkrypnyk wants to merge 110 commits into
Conversation
…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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.mdxdefines 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-methodThemeInterfacemonolith) 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.DrevOps\Tui\Widget\SelectWidgetisDrevOps\Tui\Field\Select, docs routes moved from/widgets/*to/fields/*, and every asset, playground and test follows.Structure
Block\Capabilitydeclares 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\LayoutholdsLayoutInterface,AbstractLayoutand the shipped layouts;LayoutManagerdiscovers them from its own directory and registers consumer layouts.RouterisKeyRouter.Block\Fieldis 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, andForm::root()is the canonical output.Behaviour
Screen\Collectoris the headless path at exact engine parity: source precedence, provenance, update mode, dynamic entries, the settle fixpoint, refusal order and theAnswerssummary - proven byte-identical by a dumped parity harness before the engine was deleted.CollectExceptionsits besideCancelExceptionandInterruptException.Screen\ScreenControllerdrives 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.ScreenTestertests it deterministically besideTuiTester.Theme
ThemeInterfaceis two methods:contentWidth()andkeyGlyph(). Everything else lives on per-block element interfaces (plusChromeElementsInterfacefor the frame andPrimitiveElementsInterfacefor the output primitives), implemented byAbstractThemeas the unstyled floor and byDefaultThemein full.Theme\Capability:ColorSchemeCapableInterface,UnicodeCapableInterface,OccupyCapableInterface,DimCapableInterface,MarkdownCapableInterface, each backed by a trait.Tui::theme()also takes a closure overThemeBuilder, 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 theprogress()/output()primitives unchanged.Translations, playgrounds, docs, assets
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