Added 'ThemeManager' and theme-driven widget rendering with distinct display modes.#3
Conversation
… modes. Moved the registry, factory and capability detection off 'AbstractTheme' into a dedicated 'ThemeManager'; the base theme now ships complete neutral style and glyph defaults so concrete themes override only their palette via '+ parent::defineStyles()'. Widgets style carets, markers, highlighted rows and errors from the theme; the editor shows an underlined label header; dim chrome uses bright-black (SGR 90) instead of faint, which renderers drop. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
…logo markup fix. The Themes section now documents the manager-based creation and registration, the partial-override merge pattern, and the loud failure on unknown theme names. The Quick start uses a single-expression arrow function, and the logo lost its empty-href anchor. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
…ring. All four display modes are now visually distinct: colour modes differ by the teal accents and gray chrome, glyph modes differ by the block caret, rules and dot separators, and every editor capture shows the underlined label header. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
An invalid registration now throws immediately with a clear message instead of surfacing later as an instantiation failure inside 'create()'. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
…rm variable names. The cursor-row label styling repeated across the select, multiselect, search and suggest views now lives on 'AbstractWidget'. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe PR adds ChangesThemeManager and themed rendering
Docs asset rendering utilities
Estimated code review effort: 4 (Complex) | ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
This comment has been minimized.
This comment has been minimized.
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
A fixed 800ms capture raced the demo startup under parallel recording, producing empty frames or the leaked spawn echo. Static frames are now captured relative to the moment the demo's gate text first appears in the recording, and every generated SVG is verified to contain its expected content before the job succeeds. Recorded form demos now end with the provenance-badged answers summary instead of raw JSON. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
Every per-widget capture now shows the widget's initial state - the previously empty textarea, confirm and password frames and the leaked spawn echo are gone - and the form demos close on the panel-grouped answers summary. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
This comment has been minimized.
This comment has been minimized.
…isible frame. svg-term picks the frame nearest the requested timestamp and keeps every frame sharing it; the cast loader synthesizes a blank initial screen state at the same quantized stamp, so the blank frame could fill the viewport while the real content sat outside the viewBox - the select ASCII no-ANSI capture rendered as a plain dark rectangle. The renderer now merges all output up to the timestamp and passes svg-term exactly one frame, and the asset generator rejects any static SVG holding more than one frame window. Claude-Session: https://claude.ai/code/session_01Sy9dZFyyQJfe5rcusTtK63
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Widget/MultiSelectWidget.php`:
- Around line 189-192: The marker/highlight row rendering is duplicated across
MultiSelectWidget::view(), SearchWidget::view(), and SelectWidget::view().
Extract the repeated current/styled-on-glyph vs plain-off-glyph plus
highlightLabel logic into a shared helper on AbstractWidget, such as
renderMarkerRow(ThemeInterface $theme, string $onGlyph, string $offGlyph, bool
$current, string $label): string, then update the three widget view methods to
call it and keep the glyph/label selection there only.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 863aa600-2e61-4800-bf92-2e56ed9d71f8
⛔ Files ignored due to path filters (56)
docs/architecture/architecture.svgis excluded by!**/*.svgdocs/assets/discovery.svgis excluded by!**/*.svgdocs/assets/nested-panels.svgis excluded by!**/*.svgdocs/assets/scaffolder-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/scaffolder-ascii.svgis excluded by!**/*.svgdocs/assets/scaffolder-no-ansi.svgis excluded by!**/*.svgdocs/assets/scaffolder.svgis excluded by!**/*.svgdocs/assets/theme-ocean.svgis excluded by!**/*.svgdocs/assets/widget-confirm-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-confirm-ascii.svgis excluded by!**/*.svgdocs/assets/widget-confirm-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-confirm.svgis excluded by!**/*.svgdocs/assets/widget-multisearch-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-multisearch-ascii.svgis excluded by!**/*.svgdocs/assets/widget-multisearch-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-multisearch.svgis excluded by!**/*.svgdocs/assets/widget-multiselect-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-multiselect-ascii.svgis excluded by!**/*.svgdocs/assets/widget-multiselect-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-multiselect.svgis excluded by!**/*.svgdocs/assets/widget-number-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-number-ascii.svgis excluded by!**/*.svgdocs/assets/widget-number-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-number.svgis excluded by!**/*.svgdocs/assets/widget-password-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-password-ascii.svgis excluded by!**/*.svgdocs/assets/widget-password-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-password.svgis excluded by!**/*.svgdocs/assets/widget-pause-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-pause-ascii.svgis excluded by!**/*.svgdocs/assets/widget-pause-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-pause.svgis excluded by!**/*.svgdocs/assets/widget-search-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-search-ascii.svgis excluded by!**/*.svgdocs/assets/widget-search-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-search.svgis excluded by!**/*.svgdocs/assets/widget-select-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-select-ascii.svgis excluded by!**/*.svgdocs/assets/widget-select-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-select.svgis excluded by!**/*.svgdocs/assets/widget-suggest-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-suggest-ascii.svgis excluded by!**/*.svgdocs/assets/widget-suggest-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-suggest.svgis excluded by!**/*.svgdocs/assets/widget-text-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-text-ascii.svgis excluded by!**/*.svgdocs/assets/widget-text-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-text.svgis excluded by!**/*.svgdocs/assets/widget-textarea-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-textarea-ascii.svgis excluded by!**/*.svgdocs/assets/widget-textarea-no-ansi.svgis excluded by!**/*.svgdocs/assets/widget-textarea.svgis excluded by!**/*.svgdocs/assets/widgets-ascii-no-ansi.svgis excluded by!**/*.svgdocs/assets/widgets-ascii.svgis excluded by!**/*.svgdocs/assets/widgets-no-ansi.svgis excluded by!**/*.svgdocs/assets/widgets.svgis excluded by!**/*.svg
📒 Files selected for processing (51)
README.mddocs/architecture/README.mddocs/architecture/architecture.pumldocs/util/svg-term-render.jsdocs/util/update-assets.phpplayground/1-scaffolder/run.phpplayground/2-custom-theme/OceanTheme.phpplayground/2-custom-theme/run.phpplayground/3-widgets/widget-confirm.phpplayground/3-widgets/widget-multisearch.phpplayground/3-widgets/widget-multiselect.phpplayground/3-widgets/widget-number.phpplayground/3-widgets/widget-password.phpplayground/3-widgets/widget-pause.phpplayground/3-widgets/widget-search.phpplayground/3-widgets/widget-select.phpplayground/3-widgets/widget-suggest.phpplayground/3-widgets/widget-text.phpplayground/3-widgets/widget-textarea.phpplayground/3-widgets/widgets.phpplayground/4-nested-panels/run.phpplayground/5-discovery/run.phpplayground/README.mdsrc/Render/PanelController.phpsrc/Theme/AbstractTheme.phpsrc/Theme/DarkTheme.phpsrc/Theme/LightTheme.phpsrc/Theme/ThemeInterface.phpsrc/Theme/ThemeManager.phpsrc/Tui.phpsrc/Widget/AbstractWidget.phpsrc/Widget/ConfirmWidget.phpsrc/Widget/MultiSearchWidget.phpsrc/Widget/MultiSelectWidget.phpsrc/Widget/PasswordWidget.phpsrc/Widget/PauseWidget.phpsrc/Widget/SearchWidget.phpsrc/Widget/SelectWidget.phpsrc/Widget/SuggestWidget.phpsrc/Widget/TextWidget.phpsrc/Widget/TextareaWidget.phptests/phpunit/Unit/Render/PanelControllerTest.phptests/phpunit/Unit/Theme/ThemeManagerTest.phptests/phpunit/Unit/Theme/ThemeRenderTest.phptests/phpunit/Unit/Theme/ThemeTest.phptests/phpunit/Unit/Widget/ConfirmWidgetTest.phptests/phpunit/Unit/Widget/MultiSearchWidgetTest.phptests/phpunit/Unit/Widget/NumberWidgetTest.phptests/phpunit/Unit/Widget/PasswordWidgetTest.phptests/phpunit/Unit/Widget/SearchWidgetTest.phptests/phpunit/Unit/Widget/TextWidgetTest.php
💤 Files with no reviewable changes (1)
- playground/5-discovery/run.php
|
Summary
This branch moves the theme registry, factory (
create()/register()) and terminal capability detection (detectColor()/detectUnicode()) offAbstractThemeand into a newThemeManager, and turnsAbstractThemefrom an abstract contract into a complete, neutral base implementation that a concrete theme overrides only in part via+ parent::defineStyles()/+ parent::defineGlyphs(). All widgets and the panel editor frame now render through the theme instead of emitting plain text, so carets, markers, cursor-row highlights and validation errors are all styled, and an editing field's label is visually distinguishable from its value via a newrenderEditorHeader(). Dim chrome (status lines, hints, descriptions) moved from the faint SGR 2 code, which many terminals and the SVG renderer drop entirely, to SGR 90 (bright black), and every regenerated demo SVG underdocs/assets/now shows visibly distinct output across the four display modes (Unicode/ASCII x ANSI/no-ANSI).Changes
Theme architecture
src/Theme/ThemeManager.php: the registry and factory (create()resolves a registered name or anAbstractThemesubclass FQCN;default/empty resolve todark; an unknown name now throwsInvalidArgumentExceptioninstead of silently falling back to dark) plusdetectColor()/detectUnicode(), all moved offAbstractTheme.ThemeManager::register()validates the class is anAbstractThemesubclass at registration time and throwsInvalidArgumentExceptionimmediately, rather than deferring the failure to a latercreate()call.AbstractTheme::defineStyles()/defineGlyphs()are no longer abstract: the base class now implements complete neutral defaults (rolestitle,breadcrumb,label,value,description,marker,badge,cursor,footer,indicator, plus the newhighlight,errorandrule; the full glyph set including a newruleglyph).DarkTheme/LightThemeshrank to palette-only overrides merged with+ parent::defineStyles().caretglyph changed from│/|to█/|so Unicode and ASCII text widgets stay visually distinct from each other.footer,breadcrumb,description) moved from SGR 2 (faint - dropped by the SVG renderer and many terminals) to SGR 90 (bright black) so status and hint lines actually render dimmed.renderEditorHeader(label)(the label styled with thetitlerole over a dimruleunderline, so a field label reads as a label in every display mode, including no-color) andrenderHintLine(...hints)(dot-glyph-joined dim hints);renderStatusLine()now callsrenderHintLine()internally. Both methods were added toThemeInterface.Widget and panel rendering
markerrole, checked boxes withvalue, cursor-row labels withhighlightvia a sharedhighlightLabel()helper added toAbstractWidget, and validation errors witherror;PauseWidgetandConfirmWidgetpick up the same marker styling.PanelController::frame()renders the editing header withrenderEditorHeader()and accurate editing hints (enter accept/esc cancel) viarenderHintLine(), replacing a raw unstyled label and the panel's navigation status line.Tui.phpnow creates the theme and detects terminal capabilities throughThemeManagerinstead ofAbstractTheme.Playground and documentation
widgets.phpinplayground/3-widgets/callrenderEditorHeader()/renderHintLine()instead of raw$theme->style('title', ...)/$theme->style('footer', ...).playground/2-custom-theme/OceanTheme.phpdemonstrates the partial-override merge pattern (+ parent::defineStyles()/+ parent::defineGlyphs()), and its docblock and therun.phpcomment referenceThemeManager::register().README.md: the Themes section is rewritten aroundThemeManager, the Quick start example uses a single-expression arrow function (fn), and the logo lost its empty-href anchor.docs/architecture/architecture.puml/.svggained aThemeManagernode and the widget -> theme edge;docs/architecture/README.mdandplayground/README.mdwere updated to match.1-scaffolder,4-nested-panels,2-custom-theme,5-discovery) end with the provenance-badgedtoSummary()output instead of raw JSON, so the closing frame of each capture reads as a result summary rather than an unlabelled data dump.docs/util/update-assets.phpanchors each static widget frame to the moment the demo's gate text first appears in its recording - a fixed 800ms capture raced process startup under parallel recording and could grab an empty screen or the leaked spawn echo - and now verifies every generated SVG contains its expected content before the job succeeds.docs/assets/*.svgdemo captures were regenerated - the four display modes (Unicode/ASCII x ANSI/no-ANSI) are now visibly distinct from each other.Tests
tests/phpunit/Unit/Theme/ThemeManagerTest.phpcovering the registry, factory, class validation and capability detection.ThemeTest,ThemeRenderTest, the widget test suites andPanelControllerTestwere updated for the new styled rendering, the editor header and the hint-line output.Before / After
Summary by CodeRabbit