this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

init

+16412
+37
.gitignore
··· 1 + # Dependencies 2 + node_modules/ 3 + 4 + # OpenClaude 5 + .openclaude-profile.json 6 + 7 + # OS files 8 + .DS_Store 9 + Thumbs.db 10 + 11 + # Editor directories 12 + .vscode/ 13 + .idea/ 14 + 15 + 16 + # START Ruler Generated Files 17 + /.agent/rules/ruler.md 18 + /.agent/rules/ruler.md.bak 19 + /.agent/skills 20 + /.claude/skills 21 + /.gemini/settings.json 22 + /.gemini/settings.json.bak 23 + /.gemini/skills 24 + /.mcp.json 25 + /.mcp.json.bak 26 + /.opencode/skills 27 + /.vscode/mcp.json 28 + /.vscode/mcp.json.bak 29 + /.zed/settings.json 30 + /.zed/settings.json.bak 31 + /AGENTS.md 32 + /AGENTS.md.bak 33 + /CLAUDE.md 34 + /CLAUDE.md.bak 35 + /opencode.json 36 + /opencode.json.bak 37 + # END Ruler Generated Files
+201
LICENSE
··· 1 + Apache License 2 + Version 2.0, January 2004 3 + http://www.apache.org/licenses/ 4 + 5 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 + 7 + 1. Definitions. 8 + 9 + "License" shall mean the terms and conditions for use, reproduction, 10 + and distribution as defined by Sections 1 through 9 of this document. 11 + 12 + "Licensor" shall mean the copyright owner or entity authorized by 13 + the copyright owner that is granting the License. 14 + 15 + "Legal Entity" shall mean the union of the acting entity and all 16 + other entities that control, are controlled by, or are under common 17 + control with that entity. For the purposes of this definition, 18 + "control" means (i) the power, direct or indirect, to cause the 19 + direction or management of such entity, whether by contract or 20 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 + outstanding shares, or (iii) beneficial ownership of such entity. 22 + 23 + "You" (or "Your") shall mean an individual or Legal Entity 24 + exercising permissions granted by this License. 25 + 26 + "Source" form shall mean the preferred form for making modifications, 27 + including but not limited to software source code, documentation 28 + source, and configuration files. 29 + 30 + "Object" form shall mean any form resulting from mechanical 31 + transformation or translation of a Source form, including but 32 + not limited to compiled object code, generated documentation, 33 + and conversions to other media types. 34 + 35 + "Work" shall mean the work of authorship, whether in Source or 36 + Object form, made available under the License, as indicated by a 37 + copyright notice that is included in or attached to the work 38 + (an example is provided in the Appendix below). 39 + 40 + "Derivative Works" shall mean any work, whether in Source or Object 41 + form, that is based on (or derived from) the Work and for which the 42 + editorial revisions, annotations, elaborations, or other modifications 43 + represent, as a whole, an original work of authorship. For the purposes 44 + of this License, Derivative Works shall not include works that remain 45 + separable from, or merely link (or bind by name) to the interfaces of, 46 + the Work and Derivative Works thereof. 47 + 48 + "Contribution" shall mean any work of authorship, including 49 + the original version of the Work and any modifications or additions 50 + to that Work or Derivative Works thereof, that is intentionally 51 + submitted to Licensor for inclusion in the Work by the copyright owner 52 + or by an individual or Legal Entity authorized to submit on behalf of 53 + the copyright owner. For the purposes of this definition, "submitted" 54 + means any form of electronic, verbal, or written communication sent 55 + to the Licensor or its representatives, including but not limited to 56 + communication on electronic mailing lists, source code control systems, 57 + and issue tracking systems that are managed by, or on behalf of, the 58 + Licensor for the purpose of discussing and improving the Work, but 59 + excluding communication that is conspicuously marked or otherwise 60 + designated in writing by the copyright owner as "Not a Contribution." 61 + 62 + "Contributor" shall mean Licensor and any individual or Legal Entity 63 + on behalf of whom a Contribution has been received by Licensor and 64 + subsequently incorporated within the Work. 65 + 66 + 2. Grant of Copyright License. Subject to the terms and conditions of 67 + this License, each Contributor hereby grants to You a perpetual, 68 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 + copyright license to reproduce, prepare Derivative Works of, 70 + publicly display, publicly perform, sublicense, and distribute the 71 + Work and such Derivative Works in Source or Object form. 72 + 73 + 3. Grant of Patent License. Subject to the terms and conditions of 74 + this License, each Contributor hereby grants to You a perpetual, 75 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 + (except as stated in this section) patent license to make, have made, 77 + use, offer to sell, sell, import, and otherwise transfer the Work, 78 + where such license applies only to those patent claims licensable 79 + by such Contributor that are necessarily infringed by their 80 + Contribution(s) alone or by combination of their Contribution(s) 81 + with the Work to which such Contribution(s) was submitted. If You 82 + institute patent litigation against any entity (including a 83 + cross-claim or counterclaim in a lawsuit) alleging that the Work 84 + or a Contribution incorporated within the Work constitutes direct 85 + or contributory patent infringement, then any patent licenses 86 + granted to You under this License for that Work shall terminate 87 + as of the date such litigation is filed. 88 + 89 + 4. Redistribution. You may reproduce and distribute copies of the 90 + Work or Derivative Works thereof in any medium, with or without 91 + modifications, and in Source or Object form, provided that You 92 + meet the following conditions: 93 + 94 + (a) You must give any other recipients of the Work or 95 + Derivative Works a copy of this License; and 96 + 97 + (b) You must cause any modified files to carry prominent notices 98 + stating that You changed the files; and 99 + 100 + (c) You must retain, in the Source form of any Derivative Works 101 + that You distribute, all copyright, patent, trademark, and 102 + attribution notices from the Source form of the Work, 103 + excluding those notices that do not pertain to any part of 104 + the Derivative Works; and 105 + 106 + (d) If the Work includes a "NOTICE" text file as part of its 107 + distribution, then any Derivative Works that You distribute must 108 + include a readable copy of the attribution notices contained 109 + within such NOTICE file, excluding those notices that do not 110 + pertain to any part of the Derivative Works, in at least one 111 + of the following places: within a NOTICE text file distributed 112 + as part of the Derivative Works; within the Source form or 113 + documentation, if provided along with the Derivative Works; or, 114 + within a display generated by the Derivative Works, if and 115 + wherever such third-party notices normally appear. The contents 116 + of the NOTICE file are for informational purposes only and 117 + do not modify the License. You may add Your own attribution 118 + notices within Derivative Works that You distribute, alongside 119 + or as an addendum to the NOTICE text from the Work, provided 120 + that such additional attribution notices cannot be construed 121 + as modifying the License. 122 + 123 + You may add Your own copyright statement to Your modifications and 124 + may provide additional or different license terms and conditions 125 + for use, reproduction, or distribution of Your modifications, or 126 + for any such Derivative Works as a whole, provided Your use, 127 + reproduction, and distribution of the Work otherwise complies with 128 + the conditions stated in this License. 129 + 130 + 5. Submission of Contributions. Unless You explicitly state otherwise, 131 + any Contribution intentionally submitted for inclusion in the Work 132 + by You to the Licensor shall be under the terms and conditions of 133 + this License, without any additional terms or conditions. 134 + Notwithstanding the above, nothing herein shall supersede or modify 135 + the terms of any separate license agreement you may have executed 136 + with Licensor regarding such Contributions. 137 + 138 + 6. Trademarks. This License does not grant permission to use the trade 139 + names, trademarks, service marks, or product names of the Licensor, 140 + except as required for reasonable and customary use in describing the 141 + origin of the Work and reproducing the content of the NOTICE file. 142 + 143 + 7. Disclaimer of Warranty. Unless required by applicable law or 144 + agreed to in writing, Licensor provides the Work (and each 145 + Contributor provides its Contributions) on an "AS IS" BASIS, 146 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 + implied, including, without limitation, any warranties or conditions 148 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 + PARTICULAR PURPOSE. You are solely responsible for determining the 150 + appropriateness of using or redistributing the Work and assume any 151 + risks associated with Your exercise of permissions under this License. 152 + 153 + 8. Limitation of Liability. In no event and under no legal theory, 154 + whether in tort (including negligence), contract, or otherwise, 155 + unless required by applicable law (such as deliberate and grossly 156 + negligent acts) or agreed to in writing, shall any Contributor be 157 + liable to You for damages, including any direct, indirect, special, 158 + incidental, or consequential damages of any character arising as a 159 + result of this License or out of the use or inability to use the 160 + Work (including but not limited to damages for loss of goodwill, 161 + work stoppage, computer failure or malfunction, or any and all 162 + other commercial damages or losses), even if such Contributor 163 + has been advised of the possibility of such damages. 164 + 165 + 9. Accepting Warranty or Additional Liability. While redistributing 166 + the Work or Derivative Works thereof, You may choose to offer, 167 + and charge a fee for, acceptance of support, warranty, indemnity, 168 + or other liability obligations and/or rights consistent with this 169 + License. However, in accepting such obligations, You may act only 170 + on Your own behalf and on Your sole responsibility, not on behalf 171 + of any other Contributor, and only if You agree to indemnify, 172 + defend, and hold each Contributor harmless for any liability 173 + incurred by, or claims asserted against, such Contributor by reason 174 + of your accepting any such warranty or additional liability. 175 + 176 + END OF TERMS AND CONDITIONS 177 + 178 + APPENDIX: How to apply the Apache License to your work. 179 + 180 + To apply the Apache License to your work, attach the following 181 + boilerplate notice, with the fields enclosed by brackets "[]" 182 + replaced with your own identifying information. (Don't include 183 + the brackets!) The text should be enclosed in the appropriate 184 + comment syntax for the file format. We also recommend that a 185 + file or class name and description of purpose be included on the 186 + same "printed page" as the copyright notice for easier 187 + identification within third-party archives. 188 + 189 + Copyright 2026 David Basile Filho 190 + 191 + Licensed under the Apache License, Version 2.0 (the "License"); 192 + you may not use this file except in compliance with the License. 193 + You may obtain a copy of the License at 194 + 195 + http://www.apache.org/licenses/LICENSE-2.0 196 + 197 + Unless required by applicable law or agreed to in writing, software 198 + distributed under the License is distributed on an "AS IS" BASIS, 199 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 + See the License for the specific language governing permissions and 201 + limitations under the License.
+26
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "agents", 7 + "devDependencies": { 8 + "@types/bun": "latest", 9 + }, 10 + "peerDependencies": { 11 + "typescript": "latest", 12 + }, 13 + }, 14 + }, 15 + "packages": { 16 + "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], 17 + 18 + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], 19 + 20 + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], 21 + 22 + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], 23 + 24 + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 25 + } 26 + }
+2
mise.toml
··· 1 + [tools] 2 + bun = "latest"
+11
package.json
··· 1 + { 2 + "name": "agents", 3 + "devDependencies": { 4 + "@types/bun": "^1.3.13" 5 + }, 6 + "peerDependencies": { 7 + "typescript": "^6.0.3" 8 + }, 9 + "private": true, 10 + "type": "module" 11 + }
+198
skills/adapt/SKILL.md
··· 1 + --- 2 + name: adapt 3 + description: "Adapt designs to work across different screen sizes, devices, contexts, or platforms. Implements breakpoints, fluid layouts, and touch targets. Use when the user mentions responsive design, mobile layouts, breakpoints, viewport adaptation, or cross-device compatibility." 4 + argument-hint: "[target] [context (mobile, tablet, print...)]" 5 + user-invocable: true 6 + --- 7 + 8 + Adapt existing designs to work effectively across different contexts: different screen sizes, devices, platforms, or use cases. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: target platforms or devices and usage contexts. 13 + 14 + --- 15 + 16 + ## Assess Adaptation Challenge 17 + 18 + Understand what needs adaptation and why. 19 + 20 + ### 1. Identify the source context 21 + - What was it designed for originally? (Desktop web? Mobile app?) 22 + - What assumptions were made? (Large screen? Mouse input? Fast connection?) 23 + - What works well in current context? 24 + 25 + ### 2. Understand target context 26 + - **Device**: Mobile, tablet, desktop, TV, watch, print? 27 + - **Input method**: Touch, mouse, keyboard, voice, gamepad? 28 + - **Screen constraints**: Size, resolution, orientation? 29 + - **Connection**: Fast wifi, slow 3G, offline? 30 + - **Usage context**: On-the-go vs desk, quick glance vs focused reading? 31 + - **User expectations**: What do users expect on this platform? 32 + 33 + ### 3. Identify adaptation challenges 34 + - What will not fit? (Content, navigation, features) 35 + - What will not work? (Hover states on touch, tiny touch targets) 36 + - What is inappropriate? (Desktop patterns on mobile, mobile patterns on desktop) 37 + 38 + **CRITICAL**: Adaptation is not just scaling. It is rethinking the experience for the new context. 39 + 40 + ## Plan Adaptation Strategy 41 + 42 + Create context-appropriate strategy. 43 + 44 + ### Mobile Adaptation (Desktop to Mobile) 45 + 46 + **Layout Strategy**: 47 + - Single column instead of multi-column 48 + - Vertical stacking instead of side-by-side 49 + - Full-width components instead of fixed widths 50 + - Bottom navigation instead of top or side navigation 51 + 52 + **Interaction Strategy**: 53 + - Touch targets 44x44px minimum (not hover-dependent) 54 + - Swipe gestures where appropriate (lists, carousels) 55 + - Bottom sheets instead of dropdowns 56 + - Thumbs-first design (controls within thumb reach) 57 + - Larger tap areas with more spacing 58 + 59 + **Content Strategy**: 60 + - Progressive disclosure (do not show everything at once) 61 + - Prioritize primary content (secondary content in tabs or accordions) 62 + - Shorter text (more concise) 63 + - Larger text (16px minimum) 64 + 65 + **Navigation Strategy**: 66 + - Hamburger menu or bottom navigation 67 + - Reduce navigation complexity 68 + - Sticky headers for context 69 + - Back button in navigation flow 70 + 71 + ### Tablet Adaptation (Hybrid Approach) 72 + 73 + **Layout Strategy**: 74 + - Two-column layouts (not single or three-column) 75 + - Side panels for secondary content 76 + - Master-detail views (list plus detail) 77 + - Adaptive based on orientation (portrait vs landscape) 78 + 79 + **Interaction Strategy**: 80 + - Support both touch and pointer 81 + - Touch targets 44x44px but allow denser layouts than phone 82 + - Side navigation drawers 83 + - Multi-column forms where appropriate 84 + 85 + ### Desktop Adaptation (Mobile to Desktop) 86 + 87 + **Layout Strategy**: 88 + - Multi-column layouts (use horizontal space) 89 + - Side navigation always visible 90 + - Multiple information panels simultaneously 91 + - Fixed widths with max-width constraints (do not stretch to 4K) 92 + 93 + **Interaction Strategy**: 94 + - Hover states for additional information 95 + - Keyboard shortcuts 96 + - Right-click context menus 97 + - Drag and drop where helpful 98 + - Multi-select with Shift or Cmd 99 + 100 + **Content Strategy**: 101 + - Show more information upfront (less progressive disclosure) 102 + - Data tables with many columns 103 + - Richer visualizations 104 + - More detailed descriptions 105 + 106 + ### Print Adaptation (Screen to Print) 107 + 108 + **Layout Strategy**: 109 + - Page breaks at logical points 110 + - Remove navigation, footer, interactive elements 111 + - Black and white (or limited color) 112 + - Proper margins for binding 113 + 114 + **Content Strategy**: 115 + - Expand shortened content (show full URLs, hidden sections) 116 + - Add page numbers, headers, footers 117 + - Include metadata (print date, page title) 118 + - Convert charts to print-friendly versions 119 + 120 + ### Email Adaptation (Web to Email) 121 + 122 + **Layout Strategy**: 123 + - Narrow width (600px max) 124 + - Single column only 125 + - Inline CSS (no external stylesheets) 126 + - Table-based layouts (for email client compatibility) 127 + 128 + **Interaction Strategy**: 129 + - Large, obvious CTAs (buttons not text links) 130 + - No hover states (not reliable) 131 + - Deep links to web app for complex interactions 132 + 133 + ## Implement Adaptations 134 + 135 + Apply changes systematically. 136 + 137 + ### Responsive Breakpoints 138 + 139 + Choose appropriate breakpoints: 140 + - Mobile: 320px-767px 141 + - Tablet: 768px-1023px 142 + - Desktop: 1024px+ 143 + - Or content-driven breakpoints (where design breaks) 144 + 145 + ### Layout Adaptation Techniques 146 + 147 + - **CSS Grid or Flexbox**: Reflow layouts automatically 148 + - **Container Queries**: Adapt based on container, not viewport 149 + - **clamp()**: Fluid sizing between min and max 150 + - **Media queries**: Different styles for different contexts 151 + - **Display properties**: Show or hide elements per context 152 + 153 + ### Touch Adaptation 154 + 155 + - Increase touch target sizes (44x44px minimum) 156 + - Add more spacing between interactive elements 157 + - Remove hover-dependent interactions 158 + - Add touch feedback (ripples, highlights) 159 + - Consider thumb zones (easier to reach bottom than top) 160 + 161 + ### Content Adaptation 162 + 163 + - Use display: none sparingly (still downloads) 164 + - Progressive enhancement (core content first, enhancements on larger screens) 165 + - Lazy loading for off-screen content 166 + - Responsive images (srcset, picture element) 167 + 168 + ### Navigation Adaptation 169 + 170 + - Transform complex nav to hamburger or drawer on mobile 171 + - Bottom nav bar for mobile apps 172 + - Persistent side navigation on desktop 173 + - Breadcrumbs on smaller screens for context 174 + 175 + **IMPORTANT**: Test on real devices, not just browser DevTools. Device emulation is helpful but not perfect. 176 + 177 + **NEVER**: 178 + - Hide core functionality on mobile (if it matters, make it work) 179 + - Assume desktop equals powerful device (consider accessibility, older machines) 180 + - Use different information architecture across contexts (confusing) 181 + - Break user expectations for platform (mobile users expect mobile patterns) 182 + - Forget landscape orientation on mobile or tablet 183 + - Use generic breakpoints blindly (use content-driven breakpoints) 184 + - Ignore touch on desktop (many desktop devices have touch) 185 + 186 + ## Verify Adaptations 187 + 188 + Test thoroughly across contexts. 189 + 190 + - **Real devices**: Test on actual phones, tablets, desktops 191 + - **Different orientations**: Portrait and landscape 192 + - **Different browsers**: Safari, Chrome, Firefox, Edge 193 + - **Different OS**: iOS, Android, Windows, macOS 194 + - **Different input methods**: Touch, mouse, keyboard 195 + - **Edge cases**: Very small screens (320px), very large screens (4K) 196 + - **Slow connections**: Test on throttled network 197 + 198 + Remember: You are a cross-platform design expert. Make experiences that feel native to each context while maintaining brand and functionality consistency. Adapt intentionally, test thoroughly.
+174
skills/animate/SKILL.md
··· 1 + --- 2 + name: animate 3 + description: "Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight. Use when the user mentions adding animation, transitions, micro-interactions, motion design, hover effects, or making the UI feel more alive." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: performance constraints. 13 + 14 + --- 15 + 16 + ## Assess Animation Opportunities 17 + 18 + Analyze where motion would improve the experience. 19 + 20 + ### 1. Identify static areas 21 + - **Missing feedback**: Actions without visual acknowledgment (button clicks, form submission, etc.) 22 + - **Jarring transitions**: Instant state changes that feel abrupt (show or hide, page loads, route changes) 23 + - **Unclear relationships**: Spatial or hierarchical relationships that are not obvious 24 + - **Lack of delight**: Functional but joyless interactions 25 + - **Missed guidance**: Opportunities to direct attention or explain behavior 26 + 27 + ### 2. Understand the context 28 + - What is the personality? (Playful vs serious, energetic vs calm) 29 + - What is the performance budget? (Mobile-first? Complex page?) 30 + - Who is the audience? (Motion-sensitive users? Power users who want speed?) 31 + - What matters most? (One hero animation vs many micro-interactions?) 32 + 33 + If any of these are unclear from the codebase, {{ask_instruction}} 34 + 35 + **CRITICAL**: Respect prefers-reduced-motion. Always provide non-animated alternatives for users who need them. 36 + 37 + ## Plan Animation Strategy 38 + 39 + Create a purposeful animation plan. 40 + 41 + - **Hero moment**: What is the ONE signature animation? (Page load? Hero section? Key interaction?) 42 + - **Feedback layer**: Which interactions need acknowledgment? 43 + - **Transition layer**: Which state changes need smoothing? 44 + - **Delight layer**: Where can we surprise and delight? 45 + 46 + **IMPORTANT**: One well-orchestrated experience beats scattered animations everywhere. Focus on high-impact moments. 47 + 48 + ## Implement Animations 49 + 50 + Add motion systematically across these categories. 51 + 52 + ### Entrance Animations 53 + - **Page load choreography**: Stagger element reveals (100-150ms delays), fade and slide combinations 54 + - **Hero section**: Dramatic entrance for primary content (scale, parallax, or creative effects) 55 + - **Content reveals**: Scroll-triggered animations using intersection observer 56 + - **Modal or drawer entry**: Smooth slide and fade, backdrop fade, focus management 57 + 58 + ### Micro-interactions 59 + - **Button feedback**: 60 + - Hover: Subtle scale (1.02-1.05), color shift, shadow increase 61 + - Click: Quick scale down then up (0.95 to 1), ripple effect 62 + - Loading: Spinner or pulse state 63 + - **Form interactions**: 64 + - Input focus: Border color transition, slight scale or glow 65 + - Validation: Shake on error, check mark on success, smooth color transitions 66 + - **Toggle switches**: Smooth slide and color transition (200-300ms) 67 + - **Checkboxes or radio**: Check mark animation, ripple effect 68 + - **Like or favorite**: Scale and rotation, particle effects, color transition 69 + 70 + ### State Transitions 71 + - **Show or hide**: Fade and slide (not instant), appropriate timing (200-300ms) 72 + - **Expand or collapse**: Height transition with overflow handling, icon rotation 73 + - **Loading states**: Skeleton screen fades, spinner animations, progress bars 74 + - **Success or error**: Color transitions, icon animations, gentle scale pulse 75 + - **Enable or disable**: Opacity transitions, cursor changes 76 + 77 + ### Navigation and Flow 78 + - **Page transitions**: Crossfade between routes, shared element transitions 79 + - **Tab switching**: Slide indicator, content fade or slide 80 + - **Carousel or slider**: Smooth transforms, snap points, momentum 81 + - **Scroll effects**: Parallax layers, sticky headers with state changes, scroll progress indicators 82 + 83 + ### Feedback and Guidance 84 + - **Hover hints**: Tooltip fade-ins, cursor changes, element highlights 85 + - **Drag and drop**: Lift effect (shadow and scale), drop zone highlights, smooth repositioning 86 + - **Copy or paste**: Brief highlight flash on paste, "copied" confirmation 87 + - **Focus flow**: Highlight path through form or workflow 88 + 89 + ### Delight Moments 90 + - **Empty states**: Subtle floating animations on illustrations 91 + - **Completed actions**: Confetti, check mark flourish, success celebrations 92 + - **Easter eggs**: Hidden interactions for discovery 93 + - **Contextual animation**: Weather effects, time-of-day themes, seasonal touches 94 + 95 + ## Technical Implementation 96 + 97 + Use appropriate techniques for each animation. 98 + 99 + ### Timing and Easing 100 + 101 + **Durations by purpose:** 102 + - **100-150ms**: Instant feedback (button press, toggle) 103 + - **200-300ms**: State changes (hover, menu open) 104 + - **300-500ms**: Layout changes (accordion, modal) 105 + - **500-800ms**: Entrance animations (page load) 106 + 107 + **Easing curves (use these, not CSS defaults):** 108 + ```css 109 + /* Recommended - natural deceleration */ 110 + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); /* Smooth, refined */ 111 + --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); /* Slightly snappier */ 112 + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); /* Confident, decisive */ 113 + 114 + /* AVOID - feel dated and tacky */ 115 + /* bounce: cubic-bezier(0.34, 1.56, 0.64, 1); */ 116 + /* elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6); */ 117 + ``` 118 + 119 + **Exit animations are faster than entrances.** Use approximately 75% of enter duration. 120 + 121 + ### CSS Animations 122 + ```css 123 + /* Prefer for simple, declarative animations */ 124 + - transitions for state changes 125 + - @keyframes for complex sequences 126 + - transform and opacity only (GPU-accelerated) 127 + ``` 128 + 129 + ### JavaScript Animation 130 + ```javascript 131 + /* Use for complex, interactive animations */ 132 + - Web Animations API for programmatic control 133 + - Framer Motion for React 134 + - GSAP for complex sequences 135 + ``` 136 + 137 + ### Performance 138 + - **GPU acceleration**: Use transform and opacity, avoid layout properties 139 + - **will-change**: Add sparingly for known expensive animations 140 + - **Reduce paint**: Minimize repaints, use contain where appropriate 141 + - **Monitor FPS**: Ensure 60fps on target devices 142 + 143 + ### Accessibility 144 + ```css 145 + @media (prefers-reduced-motion: reduce) { 146 + * { 147 + animation-duration: 0.01ms !important; 148 + animation-iteration-count: 1 !important; 149 + transition-duration: 0.01ms !important; 150 + } 151 + } 152 + ``` 153 + 154 + **NEVER**: 155 + - Use bounce or elastic easing curves. They feel dated and draw attention to the animation itself. 156 + - Animate layout properties (width, height, top, left). Use transform instead. 157 + - Use durations over 500ms for feedback. It feels laggy. 158 + - Animate without purpose. Every animation needs a reason. 159 + - Ignore prefers-reduced-motion. This is an accessibility violation. 160 + - Animate everything. Animation fatigue makes interfaces feel exhausting. 161 + - Block interaction during animations unless intentional. 162 + 163 + ## Verify Quality 164 + 165 + Test animations thoroughly. 166 + 167 + - **Smooth at 60fps**: No jank on target devices 168 + - **Feels natural**: Easing curves feel organic, not robotic 169 + - **Appropriate timing**: Not too fast (jarring) or too slow (laggy) 170 + - **Reduced motion works**: Animations disabled or simplified appropriately 171 + - **Does not block**: Users can interact during or after animations 172 + - **Adds value**: Makes interface clearer or more delightful 173 + 174 + Remember: Motion should enhance understanding and provide feedback, not just add decoration. Animate with purpose, respect performance constraints, and always consider accessibility. Great animation is invisible. It just makes everything feel right.
+124
skills/arrange/SKILL.md
··· 1 + --- 2 + name: arrange 3 + description: "Improve layout, spacing, and visual rhythm. Fixes monotonous grids, inconsistent spacing, and weak visual hierarchy. Use when the user mentions layout feeling off, spacing issues, visual hierarchy, crowded UI, alignment problems, or wanting better composition." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Assess and improve layout and spacing that feels monotonous, crowded, or structurally weak. Turn generic arrangements into intentional, rhythmic compositions. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 13 + 14 + --- 15 + 16 + ## Assess Current Layout 17 + 18 + Analyze what is weak about the current spatial design. 19 + 20 + ### 1. Spacing 21 + - Is spacing consistent or arbitrary? (Random padding or margin values) 22 + - Is all spacing the same? (Equal padding everywhere equals no rhythm) 23 + - Are related elements grouped tightly, with generous space between groups? 24 + 25 + ### 2. Visual hierarchy 26 + - Apply the squint test: blur your (metaphorical) eyes. Can you still identify the most important element, second most important, and clear groupings? 27 + - Is hierarchy achieved effectively? (Space and weight alone can be enough. But is the current approach working?) 28 + - Does whitespace guide the eye to what matters? 29 + 30 + ### 3. Grid and structure 31 + - Is there a clear underlying structure, or does the layout feel random? 32 + - Are identical card grids used everywhere? (Icon plus heading plus text, repeated endlessly) 33 + - Is everything centered? (Left-aligned with asymmetric layouts feels more designed, but not a hard and fast rule) 34 + 35 + ### 4. Rhythm and variety 36 + - Does the layout have visual rhythm? (Alternating tight or generous spacing) 37 + - Is every section structured the same way? (Monotonous repetition) 38 + - Are there intentional moments of surprise or emphasis? 39 + 40 + ### 5. Density 41 + - Is the layout too cramped? (Not enough breathing room) 42 + - Is the layout too sparse? (Excessive whitespace without purpose) 43 + - Does density match the content type? (Data-dense UIs need tighter spacing; marketing pages need more air) 44 + 45 + **CRITICAL**: Layout problems are often the root cause of interfaces feeling "off" even when colors and fonts are fine. Space is a design material. Use it with intention. 46 + 47 + ## Plan Layout Improvements 48 + 49 + Consult the [spatial design reference](reference/spatial-design.md) from the frontend-design skill for detailed guidance on grids, rhythm, and container queries. 50 + 51 + Create a systematic plan. 52 + 53 + - **Spacing system**: Use a consistent scale. Whether that is a framework built-in scale (e.g., Tailwind), rem-based tokens, or a custom system. The specific values matter less than consistency. 54 + - **Hierarchy strategy**: How will space communicate importance? 55 + - **Layout approach**: What structure fits the content? Flex for 1D, Grid for 2D, named areas for complex page layouts. 56 + - **Rhythm**: Where should spacing be tight vs generous? 57 + 58 + ## Improve Layout Systematically 59 + 60 + ### Establish a Spacing System 61 + 62 + - Use a consistent spacing scale. Framework scales (Tailwind, etc.), rem-based tokens, or a custom scale all work. What matters is that values come from a defined set, not arbitrary numbers. 63 + - Name tokens semantically if using custom properties: --space-xs through --space-xl, not --spacing-8 64 + - Use gap for sibling spacing instead of margins. Eliminates margin collapse hacks. 65 + - Apply clamp() for fluid spacing that breathes on larger screens. 66 + 67 + ### Create Visual Rhythm 68 + 69 + - **Tight grouping** for related elements (8-12px between siblings) 70 + - **Generous separation** between distinct sections (48-96px) 71 + - **Varied spacing** within sections. Not every row needs the same gap. 72 + - **Asymmetric compositions**. Break the predictable centered-content pattern when it makes sense. 73 + 74 + ### Choose the Right Layout Tool 75 + 76 + - **Use Flexbox for 1D layouts**: Rows of items, nav bars, button groups, card contents, most component internals. Flex is simpler and more appropriate for the majority of layout tasks. 77 + - **Use Grid for 2D layouts**: Page-level structure, dashboards, data-dense interfaces, anything where rows AND columns need coordinated control. 78 + - **Do not default to Grid** when Flexbox with flex-wrap would be simpler and more flexible. 79 + - Use repeat(auto-fit, minmax(280px, 1fr)) for responsive grids without breakpoints. 80 + - Use named grid areas (grid-template-areas) for complex page layouts. Redefine at breakpoints. 81 + 82 + ### Break Card Grid Monotony 83 + 84 + - Do not default to card grids for everything. Spacing and alignment create visual grouping naturally. 85 + - Use cards only when content is truly distinct and actionable. Never nest cards inside cards. 86 + - Vary card sizes, span columns, or mix cards with non-card content to break repetition. 87 + 88 + ### Strengthen Visual Hierarchy 89 + 90 + - Use the fewest dimensions needed for clear hierarchy. Space alone can be enough. Generous whitespace around an element draws the eye. Some of the most sophisticated designs achieve rhythm with just space and weight. Add color or size contrast only when simpler means are not sufficient. 91 + - Be aware of reading flow. In LTR languages, the eye naturally scans top-left to bottom-right, but primary action placement depends on context (e.g., bottom-right in dialogs, top in navigation). 92 + - Create clear content groupings through proximity and separation. 93 + 94 + ### Manage Depth and Elevation 95 + 96 + - Create a semantic z-index scale (dropdown, sticky, modal-backdrop, modal, toast, tooltip) 97 + - Build a consistent shadow scale (sm, md, lg, xl). Shadows should be subtle. 98 + - Use elevation to reinforce hierarchy, not as decoration. 99 + 100 + ### Optical Adjustments 101 + 102 + - If an icon looks visually off-center despite being geometrically centered, nudge it. But only if you are confident it actually looks wrong. Do not adjust speculatively. 103 + 104 + **NEVER**: 105 + - Use arbitrary spacing values outside your scale 106 + - Make all spacing equal. Variety creates hierarchy. 107 + - Wrap everything in cards. Not everything needs a container. 108 + - Nest cards inside cards. Use spacing and dividers for hierarchy within. 109 + - Use identical card grids everywhere (icon plus heading plus text, repeated) 110 + - Center everything. Left-aligned with asymmetry feels more designed. 111 + - Default to the hero metric layout (big number, small label, stats, gradient) as a template. If showing real user data, a prominent metric can work. But it should display actual data, not decorative numbers. 112 + - Default to CSS Grid when Flexbox would be simpler. Use the simplest tool for the job. 113 + - Use arbitrary z-index values (999, 9999). Build a semantic scale. 114 + 115 + ## Verify Layout Improvements 116 + 117 + - **Squint test**: Can you identify primary, secondary, and groupings with blurred vision? 118 + - **Rhythm**: Does the page have a satisfying beat of tight and generous spacing? 119 + - **Hierarchy**: Is the most important content obvious within 2 seconds? 120 + - **Breathing room**: Does the layout feel comfortable, not cramped or wasteful? 121 + - **Consistency**: Is the spacing system applied uniformly? 122 + - **Responsiveness**: Does the layout adapt gracefully across screen sizes? 123 + 124 + Remember: Space is the most underused design tool. A layout with the right rhythm and hierarchy can make even simple content feel polished and intentional.
+147
skills/audit/SKILL.md
··· 1 + --- 2 + name: audit 3 + description: "Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review." 4 + argument-hint: "[area (feature, page, component...)]" 5 + user-invocable: true 6 + --- 7 + 8 + ## MANDATORY PREPARATION 9 + 10 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 11 + 12 + --- 13 + 14 + Run systematic technical quality checks and generate a comprehensive report. Do not fix issues. Document them for other commands to address. 15 + 16 + This is a code-level audit, not a design critique. Check what is measurable and verifiable in the implementation. 17 + 18 + ## Diagnostic Scan 19 + 20 + Run comprehensive checks across 5 dimensions. Score each dimension 0-4 using the criteria below. 21 + 22 + ### 1. Accessibility (A11y) 23 + 24 + **Check for**: 25 + - **Contrast issues**: Text contrast ratios less than 4.5:1 (or 7:1 for AAA) 26 + - **Missing ARIA**: Interactive elements without proper roles, labels, or states 27 + - **Keyboard navigation**: Missing focus indicators, illogical tab order, keyboard traps 28 + - **Semantic HTML**: Improper heading hierarchy, missing landmarks, divs instead of buttons 29 + - **Alt text**: Missing or poor image descriptions 30 + - **Form issues**: Inputs without labels, poor error messaging, missing required indicators 31 + 32 + **Score 0-4**: 0=Inaccessible (fails WCAG A), 1=Major gaps (few ARIA labels, no keyboard nav), 2=Partial (some a11y effort, significant gaps), 3=Good (WCAG AA mostly met, minor gaps), 4=Excellent (WCAG AA fully met, approaches AAA) 33 + 34 + ### 2. Performance 35 + 36 + **Check for**: 37 + - **Layout thrashing**: Reading or writing layout properties in loops 38 + - **Expensive animations**: Animating layout properties (width, height, top, left) instead of transform or opacity 39 + - **Missing optimization**: Images without lazy loading, unoptimized assets, missing will-change 40 + - **Bundle size**: Unnecessary imports, unused dependencies 41 + - **Render performance**: Unnecessary re-renders, missing memoization 42 + 43 + **Score 0-4**: 0=Severe issues (layout thrash, unoptimized everything), 1=Major problems (no lazy loading, expensive animations), 2=Partial (some optimization, gaps remain), 3=Good (mostly optimized, minor improvements possible), 4=Excellent (fast, lean, well-optimized) 44 + 45 + ### 3. Theming 46 + 47 + **Check for**: 48 + - **Hard-coded colors**: Colors not using design tokens 49 + - **Broken dark mode**: Missing dark mode variants, poor contrast in dark theme 50 + - **Inconsistent tokens**: Using wrong tokens, mixing token types 51 + - **Theme switching issues**: Values that do not update on theme change 52 + 53 + **Score 0-4**: 0=No theming (hard-coded everything), 1=Minimal tokens (mostly hard-coded), 2=Partial (tokens exist but inconsistently used), 3=Good (tokens used, minor hard-coded values), 4=Excellent (full token system, dark mode works perfectly) 54 + 55 + ### 4. Responsive Design 56 + 57 + **Check for**: 58 + - **Fixed widths**: Hard-coded widths that break on mobile 59 + - **Touch targets**: Interactive elements less than 44x44px 60 + - **Horizontal scroll**: Content overflow on narrow viewports 61 + - **Text scaling**: Layouts that break when text size increases 62 + - **Missing breakpoints**: No mobile or tablet variants 63 + 64 + **Score 0-4**: 0=Desktop-only (breaks on mobile), 1=Major issues (some breakpoints, many failures), 2=Partial (works on mobile, rough edges), 3=Good (responsive, minor touch target or overflow issues), 4=Excellent (fluid, all viewports, proper touch targets) 65 + 66 + ### 5. Anti-Patterns (CRITICAL) 67 + 68 + Check against ALL the DO NOT guidelines in the frontend-design skill. Look for AI slop tells (AI color palette, gradient text, glassmorphism, hero metrics, card grids, generic fonts) and general design anti-patterns (gray on color, nested cards, bounce easing, redundant copy). 69 + 70 + **Score 0-4**: 0=AI slop gallery (5+ tells), 1=Heavy AI aesthetic (3-4 tells), 2=Some tells (1-2 noticeable), 3=Mostly clean (subtle issues only), 4=No AI tells (distinctive, intentional design) 71 + 72 + ## Generate Report 73 + 74 + ### Audit Health Score 75 + 76 + | # | Dimension | Score | Key Finding | 77 + |---|-----------|-------|-------------| 78 + | 1 | Accessibility | ? | [most critical a11y issue or "--"] | 79 + | 2 | Performance | ? | | 80 + | 3 | Responsive Design | ? | | 81 + | 4 | Theming | ? | | 82 + | 5 | Anti-Patterns | ? | | 83 + | **Total** | | **??/20** | **[Rating band]** | 84 + 85 + **Rating bands**: 18-20 Excellent (minor polish), 14-17 Good (address weak dimensions), 10-13 Acceptable (significant work needed), 6-9 Poor (major overhaul), 0-5 Critical (fundamental issues) 86 + 87 + ### Anti-Patterns Verdict 88 + **Start here.** Pass or fail: Does this look AI-generated? List specific tells. Be brutally honest. 89 + 90 + ### Executive Summary 91 + - Audit Health Score: ??/20 (rating band) 92 + - Total issues found (count by severity: P0/P1/P2/P3) 93 + - Top 3-5 critical issues 94 + - Recommended next steps 95 + 96 + ### Detailed Findings by Severity 97 + 98 + Tag every issue with P0-P3 severity: 99 + - **P0 Blocking**: Prevents task completion. Fix immediately. 100 + - **P1 Major**: Significant difficulty or WCAG AA violation. Fix before release. 101 + - **P2 Minor**: Annoyance, workaround exists. Fix in next pass. 102 + - **P3 Polish**: Nice-to-fix, no real user impact. Fix if time permits. 103 + 104 + For each issue, document: 105 + - **[P?] Issue name** 106 + - **Location**: Component, file, line 107 + - **Category**: Accessibility, Performance, Theming, Responsive, or Anti-Pattern 108 + - **Impact**: How it affects users 109 + - **WCAG or Standard**: Which standard it violates (if applicable) 110 + - **Recommendation**: How to fix it 111 + - **Suggested command**: Which command to use (prefer: {{available_commands}}) 112 + 113 + ### Patterns and Systemic Issues 114 + 115 + Identify recurring problems that indicate systemic gaps rather than one-off mistakes: 116 + - "Hard-coded colors appear in 15+ components, should use design tokens" 117 + - "Touch targets consistently too small (less than 44px) throughout mobile experience" 118 + 119 + ### Positive Findings 120 + 121 + Note what is working well. Good practices to maintain and replicate. 122 + 123 + ## Recommended Actions 124 + 125 + List recommended commands in priority order (P0 first, then P1, then P2): 126 + 127 + 1. **[P?] `{{command_prefix}}command-name`**: Brief description (specific context from audit findings) 128 + 2. **[P?] `{{command_prefix}}command-name`**: Brief description (specific context) 129 + 130 + **Rules**: Only recommend commands from: {{available_commands}}. Map findings to the most appropriate command. End with {{command_prefix}}polish as the final step if any fixes were recommended. 131 + 132 + After presenting the summary, tell the user: 133 + 134 + > You can ask me to run these one at a time, all at once, or in any order you prefer. 135 + > 136 + > Re-run {{command_prefix}}audit after fixes to see your score improve. 137 + 138 + **IMPORTANT**: Be thorough but actionable. Too many P3 issues creates noise. Focus on what actually matters. 139 + 140 + **NEVER**: 141 + - Report issues without explaining impact (why does this matter?) 142 + - Provide generic recommendations (be specific and actionable) 143 + - Skip positive findings (celebrate what works) 144 + - Forget to prioritize (everything cannot be P0) 145 + - Report false positives without verification 146 + 147 + Remember: You are a technical quality auditor. Document systematically, prioritize ruthlessly, cite specific code locations, and provide clear paths to improvement.
+116
skills/bolder/SKILL.md
··· 1 + --- 2 + name: bolder 3 + description: "Amplify safe or boring designs to make them more visually interesting and stimulating. Increases impact while maintaining usability. Use when the user says the design looks bland, generic, too safe, lacks personality, or wants more visual impact and character." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Increase visual impact and personality in designs that are too safe, generic, or visually underwhelming, creating more engaging and memorable experiences. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 13 + 14 + --- 15 + 16 + ## Assess Current State 17 + 18 + Analyze what makes the design feel too safe or boring. 19 + 20 + ### 1. Identify weakness sources 21 + - **Generic choices**: System fonts, basic colors, standard layouts 22 + - **Timid scale**: Everything is medium-sized with no drama 23 + - **Low contrast**: Everything has similar visual weight 24 + - **Static**: No motion, no energy, no life 25 + - **Predictable**: Standard patterns with no surprises 26 + - **Flat hierarchy**: Nothing stands out or commands attention 27 + 28 + ### 2. Understand the context 29 + - What is the brand personality? (How far can we push?) 30 + - What is the purpose? (Marketing can be bolder than financial dashboards) 31 + - Who is the audience? (What will resonate?) 32 + - What are the constraints? (Brand guidelines, accessibility, performance) 33 + 34 + If any of these are unclear from the codebase, {{ask_instruction}} 35 + 36 + **CRITICAL**: "Bolder" does not mean chaotic or garish. It means distinctive, memorable, and confident. Think intentional drama, not random chaos. 37 + 38 + **WARNING - AI SLOP TRAP**: When making things "bolder," AI defaults to the same tired tricks: cyan or purple gradients, glassmorphism, neon accents on dark backgrounds, gradient text on metrics. These are the OPPOSITE of bold. They are generic. Review ALL the DO NOT guidelines in the frontend-design skill before proceeding. Bold means distinctive, not "more effects." 39 + 40 + ## Plan Amplification 41 + 42 + Create a strategy to increase impact while maintaining coherence. 43 + 44 + - **Focal point**: What should be the hero moment? (Pick ONE, make it amazing) 45 + - **Personality direction**: Maximalist chaos? Elegant drama? Playful energy? Dark moody? Choose a lane. 46 + - **Risk budget**: How experimental can we be? Push boundaries within constraints. 47 + - **Hierarchy amplification**: Make big things BIGGER, small things smaller (increase contrast) 48 + 49 + **IMPORTANT**: Bold design must still be usable. Impact without function is just decoration. 50 + 51 + ## Amplify the Design 52 + 53 + Systematically increase impact across these dimensions. 54 + 55 + ### Typography Amplification 56 + - **Replace generic fonts**: Swap system fonts for distinctive choices (see frontend-design skill for inspiration) 57 + - **Extreme scale**: Create dramatic size jumps (3x-5x differences, not 1.5x) 58 + - **Weight contrast**: Pair 900 weights with 200 weights, not 600 with 400 59 + - **Unexpected choices**: Variable fonts, display fonts for headlines, condensed or extended widths, monospace as intentional accent (not as lazy "dev tool" default) 60 + 61 + ### Color Intensification 62 + - **Increase saturation**: Shift to more vibrant, energetic colors (but not neon) 63 + - **Bold palette**: Introduce unexpected color combinations. Avoid the purple-blue gradient AI slop. 64 + - **Dominant color strategy**: Let one bold color own 60% of the design 65 + - **Sharp accents**: High-contrast accent colors that pop 66 + - **Tinted neutrals**: Replace pure grays with tinted grays that harmonize with your palette 67 + - **Rich gradients**: Intentional multi-stop gradients (not generic purple-to-blue) 68 + 69 + ### Spatial Drama 70 + - **Extreme scale jumps**: Make important elements 3-5x larger than surroundings 71 + - **Break the grid**: Let hero elements escape containers and cross boundaries 72 + - **Asymmetric layouts**: Replace centered, balanced layouts with tension-filled asymmetry 73 + - **Generous space**: Use white space dramatically (100-200px gaps, not 20-40px) 74 + - **Overlap**: Layer elements intentionally for depth 75 + 76 + ### Visual Effects 77 + - **Dramatic shadows**: Large, soft shadows for elevation (but not generic drop shadows on rounded rectangles) 78 + - **Background treatments**: Mesh patterns, noise textures, geometric patterns, intentional gradients (not purple-to-blue) 79 + - **Texture and depth**: Grain, halftone, duotone, layered elements. NOT glassmorphism (it is overused AI slop). 80 + - **Borders and frames**: Thick borders, decorative frames, custom shapes (not rounded rectangles with colored border on one side) 81 + - **Custom elements**: Illustrative elements, custom icons, decorative details that reinforce brand 82 + 83 + ### Motion and Animation 84 + - **Entrance choreography**: Staggered, dramatic page load animations with 50-100ms delays 85 + - **Scroll effects**: Parallax, reveal animations, scroll-triggered sequences 86 + - **Micro-interactions**: Satisfying hover effects, click feedback, state changes 87 + - **Transitions**: Smooth, noticeable transitions using ease-out-quart, quint, or expo (not bounce or elastic. They cheapen the effect.) 88 + 89 + ### Composition Boldness 90 + - **Hero moments**: Create clear focal points with dramatic treatment 91 + - **Diagonal flows**: Escape horizontal or vertical rigidity with diagonal arrangements 92 + - **Full-bleed elements**: Use full viewport width or height for impact 93 + - **Unexpected proportions**: Golden ratio? Throw it out. Try 70-30, 80-20 splits. 94 + 95 + **NEVER**: 96 + - Add effects randomly without purpose (chaos does not equal bold) 97 + - Sacrifice readability for aesthetics (body text must be readable) 98 + - Make everything bold (then nothing is bold. Need contrast.) 99 + - Ignore accessibility (bold design must still meet WCAG standards) 100 + - Overwhelm with motion (animation fatigue is real) 101 + - Copy trendy aesthetics blindly (bold means distinctive, not derivative) 102 + 103 + ## Verify Quality 104 + 105 + Ensure amplification maintains usability and coherence. 106 + 107 + - **NOT AI slop**: Does this look like every other AI-generated "bold" design? If yes, start over. 108 + - **Still functional**: Can users accomplish tasks without distraction? 109 + - **Coherent**: Does everything feel intentional and unified? 110 + - **Memorable**: Will users remember this experience? 111 + - **Performant**: Do all these effects run smoothly? 112 + - **Accessible**: Does it still meet accessibility standards? 113 + 114 + **The test**: If you showed this to someone and said "AI made this bolder," would they believe you immediately? If yes, you have failed. Bold means distinctive, not "more AI effects." 115 + 116 + Remember: Bold design is confident design. It takes risks, makes statements, and creates memorable experiences. But bold without strategy is just loud. Be intentional, be dramatic, be unforgettable.
+182
skills/clarify/SKILL.md
··· 1 + --- 2 + name: clarify 3 + description: "Improve unclear UX copy, error messages, microcopy, labels, and instructions to make interfaces easier to understand. Use when the user mentions confusing text, unclear labels, bad error messages, hard-to-follow instructions, or wanting better UX writing." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Identify and improve unclear, confusing, or poorly written interface text to make the product easier to understand and use. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: audience technical level and users mental state in context. 13 + 14 + --- 15 + 16 + ## Assess Current Copy 17 + 18 + Identify what makes the text unclear or ineffective. 19 + 20 + ### 1. Find clarity problems 21 + - **Jargon**: Technical terms users will not understand 22 + - **Ambiguity**: Multiple interpretations possible 23 + - **Passive voice**: "Your file has been uploaded" vs "We uploaded your file" 24 + - **Length**: Too wordy or too terse 25 + - **Assumptions**: Assuming user knowledge they do not have 26 + - **Missing context**: Users do not know what to do or why 27 + - **Tone mismatch**: Too formal, too casual, or inappropriate for situation 28 + 29 + ### 2. Understand the context 30 + - Who is the audience? (Technical? General? First-time users?) 31 + - What is the user mental state? (Stressed during error? Confident during success?) 32 + - What is the action? (What do we want users to do?) 33 + - What is the constraint? (Character limits? Space limitations?) 34 + 35 + **CRITICAL**: Clear copy helps users succeed. Unclear copy creates frustration, errors, and support tickets. 36 + 37 + ## Plan Copy Improvements 38 + 39 + Create a strategy for clearer communication. 40 + 41 + - **Primary message**: What is the ONE thing users need to know? 42 + - **Action needed**: What should users do next (if anything)? 43 + - **Tone**: How should this feel? (Helpful? Apologetic? Encouraging?) 44 + - **Constraints**: Length limits, brand voice, localization considerations 45 + 46 + **IMPORTANT**: Good UX writing is invisible. Users should understand immediately without noticing the words. 47 + 48 + ## Improve Copy Systematically 49 + 50 + Refine text across these common areas. 51 + 52 + ### Error Messages 53 + **Bad**: "Error 403: Forbidden" 54 + **Good**: "You do not have permission to view this page. Contact your admin for access." 55 + 56 + **Bad**: "Invalid input" 57 + **Good**: "Email addresses need an @ symbol. Try: name@example.com" 58 + 59 + **Principles**: 60 + - Explain what went wrong in plain language 61 + - Suggest how to fix it 62 + - Do not blame the user 63 + - Include examples when helpful 64 + - Link to help or support if applicable 65 + 66 + ### Form Labels and Instructions 67 + **Bad**: "DOB (MM/DD/YYYY)" 68 + **Good**: "Date of birth" (with placeholder showing format) 69 + 70 + **Bad**: "Enter value here" 71 + **Good**: "Your email address" or "Company name" 72 + 73 + **Principles**: 74 + - Use clear, specific labels (not generic placeholders) 75 + - Show format expectations with examples 76 + - Explain why you are asking (when not obvious) 77 + - Put instructions before the field, not after 78 + - Keep required field indicators clear 79 + 80 + ### Button and CTA Text 81 + **Bad**: "Click here" | "Submit" | "OK" 82 + **Good**: "Create account" | "Save changes" | "Got it, thanks" 83 + 84 + **Principles**: 85 + - Describe the action specifically 86 + - Use active voice (verb plus noun) 87 + - Match user mental model 88 + - Be specific ("Save" is better than "OK") 89 + 90 + ### Help Text and Tooltips 91 + **Bad**: "This is the username field" 92 + **Good**: "Choose a username. You can change this later in Settings." 93 + 94 + **Principles**: 95 + - Add value (do not just repeat the label) 96 + - Answer the implicit question ("What is this?" or "Why do you need this?") 97 + - Keep it brief but complete 98 + - Link to detailed docs if needed 99 + 100 + ### Empty States 101 + **Bad**: "No items" 102 + **Good**: "No projects yet. Create your first project to get started." 103 + 104 + **Principles**: 105 + - Explain why it is empty (if not obvious) 106 + - Show next action clearly 107 + - Make it welcoming, not dead-end 108 + 109 + ### Success Messages 110 + **Bad**: "Success" 111 + **Good**: "Settings saved! Your changes will take effect immediately." 112 + 113 + **Principles**: 114 + - Confirm what happened 115 + - Explain what happens next (if relevant) 116 + - Be brief but complete 117 + - Match user emotional moment (celebrate big wins) 118 + 119 + ### Loading States 120 + **Bad**: "Loading..." (for 30+ seconds) 121 + **Good**: "Analyzing your data... this usually takes 30-60 seconds" 122 + 123 + **Principles**: 124 + - Set expectations (how long?) 125 + - Explain what is happening (when it is not obvious) 126 + - Show progress when possible 127 + - Offer escape hatch if appropriate ("Cancel") 128 + 129 + ### Confirmation Dialogs 130 + **Bad**: "Are you sure?" 131 + **Good**: "Delete 'Project Alpha'? This cannot be undone." 132 + 133 + **Principles**: 134 + - State the specific action 135 + - Explain consequences (especially for destructive actions) 136 + - Use clear button labels ("Delete project" not "Yes") 137 + - Do not overuse confirmations (only for risky actions) 138 + 139 + ### Navigation and Wayfinding 140 + **Bad**: Generic labels like "Items" | "Things" | "Stuff" 141 + **Good**: Specific labels like "Your projects" | "Team members" | "Settings" 142 + 143 + **Principles**: 144 + - Be specific and descriptive 145 + - Use language users understand (not internal jargon) 146 + - Make hierarchy clear 147 + - Consider information scent (breadcrumbs, current location) 148 + 149 + ## Apply Clarity Principles 150 + 151 + Every piece of copy should follow these rules: 152 + 153 + 1. **Be specific**: "Enter email" not "Enter value" 154 + 2. **Be concise**: Cut unnecessary words (but do not sacrifice clarity) 155 + 3. **Be active**: "Save changes" not "Changes will be saved" 156 + 4. **Be human**: "Oops, something went wrong" not "System error encountered" 157 + 5. **Be helpful**: Tell users what to do, not just what happened 158 + 6. **Be consistent**: Use same terms throughout (do not vary for variety) 159 + 160 + **NEVER**: 161 + - Use jargon without explanation 162 + - Blame users ("You made an error" becomes "This field is required") 163 + - Be vague ("Something went wrong" without explanation) 164 + - Use passive voice unnecessarily 165 + - Write overly long explanations (be concise) 166 + - Use humor for errors (be empathetic instead) 167 + - Assume technical knowledge 168 + - Vary terminology (pick one term and stick with it) 169 + - Repeat information (headers restating intros, redundant explanations) 170 + - Use placeholders as the only labels (they disappear when users type) 171 + 172 + ## Verify Improvements 173 + 174 + Test that copy improvements work. 175 + 176 + - **Comprehension**: Can users understand without context? 177 + - **Actionability**: Do users know what to do next? 178 + - **Brevity**: Is it as short as possible while remaining clear? 179 + - **Consistency**: Does it match terminology elsewhere? 180 + - **Tone**: Is it appropriate for the situation? 181 + 182 + Remember: You are a clarity expert with excellent communication skills. Write like you are explaining to a smart friend who is unfamiliar with the product. Be clear, be helpful, be human.
+142
skills/colorize/SKILL.md
··· 1 + --- 2 + name: colorize 3 + description: "Add strategic color to features that are too monochromatic or lack visual interest, making interfaces more engaging and expressive. Use when the user mentions the design looking gray, dull, lacking warmth, needing more color, or wanting a more vibrant or expressive palette." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Strategically introduce color to designs that are too monochromatic, gray, or lacking in visual warmth and personality. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: existing brand colors. 13 + 14 + --- 15 + 16 + ## Assess Color Opportunity 17 + 18 + Analyze the current state and identify opportunities. 19 + 20 + ### 1. Understand current state 21 + - **Color absence**: Pure grayscale? Limited neutrals? One timid accent? 22 + - **Missed opportunities**: Where could color add meaning, hierarchy, or delight? 23 + - **Context**: What is appropriate for this domain and audience? 24 + - **Brand**: Are there existing brand colors we should use? 25 + 26 + ### 2. Identify where color adds value 27 + - **Semantic meaning**: Success (green), error (red), warning (yellow or orange), info (blue) 28 + - **Hierarchy**: Drawing attention to important elements 29 + - **Categorization**: Different sections, types, or states 30 + - **Emotional tone**: Warmth, energy, trust, creativity 31 + - **Wayfinding**: Helping users navigate and understand structure 32 + - **Delight**: Moments of visual interest and personality 33 + 34 + If any of these are unclear from the codebase, {{ask_instruction}} 35 + 36 + **CRITICAL**: More color does not equal better. Strategic color beats rainbow vomit every time. Every color should have a purpose. 37 + 38 + ## Plan Color Strategy 39 + 40 + Create a purposeful color introduction plan. 41 + 42 + - **Color palette**: What colors match the brand or context? (Choose 2-4 colors max beyond neutrals) 43 + - **Dominant color**: Which color owns 60% of colored elements? 44 + - **Accent colors**: Which colors provide contrast and highlights? (30% and 10%) 45 + - **Application strategy**: Where does each color appear and why? 46 + 47 + **IMPORTANT**: Color should enhance hierarchy and meaning, not create chaos. Less is more when it matters more. 48 + 49 + ## Introduce Color Strategically 50 + 51 + Add color systematically across these dimensions. 52 + 53 + ### Semantic Color 54 + - **State indicators**: 55 + - Success: Green tones (emerald, forest, mint) 56 + - Error: Red or pink tones (rose, crimson, coral) 57 + - Warning: Orange or amber tones 58 + - Info: Blue tones (sky, ocean, indigo) 59 + - Neutral: Gray or slate for inactive states 60 + 61 + - **Status badges**: Colored backgrounds or borders for states (active, pending, completed, etc.) 62 + - **Progress indicators**: Colored bars, rings, or charts showing completion or health 63 + 64 + ### Accent Color Application 65 + - **Primary actions**: Color the most important buttons or CTAs 66 + - **Links**: Add color to clickable text (maintain accessibility) 67 + - **Icons**: Colorize key icons for recognition and personality 68 + - **Headers or titles**: Add color to section headers or key labels 69 + - **Hover states**: Introduce color on interaction 70 + 71 + ### Background and Surfaces 72 + - **Tinted backgrounds**: Replace pure gray (#f5f5f5) with warm neutrals (oklch(97% 0.01 60)) or cool tints (oklch(97% 0.01 250)) 73 + - **Colored sections**: Use subtle background colors to separate areas 74 + - **Gradient backgrounds**: Add depth with subtle, intentional gradients (not generic purple-blue) 75 + - **Cards and surfaces**: Tint cards or surfaces slightly for warmth 76 + 77 + **Use OKLCH for color**: It is perceptually uniform, meaning equal steps in lightness look equal. Great for generating harmonious scales. 78 + 79 + ### Data Visualization 80 + - **Charts and graphs**: Use color to encode categories or values 81 + - **Heatmaps**: Color intensity shows density or importance 82 + - **Comparison**: Color coding for different datasets or timeframes 83 + 84 + ### Borders and Accents 85 + - **Accent borders**: Add colored left or top borders to cards or sections 86 + - **Underlines**: Color underlines for emphasis or active states 87 + - **Dividers**: Subtle colored dividers instead of gray lines 88 + - **Focus rings**: Colored focus indicators matching brand 89 + 90 + ### Typography Color 91 + - **Colored headings**: Use brand colors for section headings (maintain contrast) 92 + - **Highlight text**: Color for emphasis or categories 93 + - **Labels and tags**: Small colored labels for metadata or categories 94 + 95 + ### Decorative Elements 96 + - **Illustrations**: Add colored illustrations or icons 97 + - **Shapes**: Geometric shapes in brand colors as background elements 98 + - **Gradients**: Colorful gradient overlays or mesh backgrounds 99 + - **Blobs or organic shapes**: Soft colored shapes for visual interest 100 + 101 + ## Balance and Refinement 102 + 103 + Ensure color addition improves rather than overwhelms. 104 + 105 + ### Maintain Hierarchy 106 + - **Dominant color** (60%): Primary brand color or most used accent 107 + - **Secondary color** (30%): Supporting color for variety 108 + - **Accent color** (10%): High contrast for key moments 109 + - **Neutrals** (remaining): Gray, black, white for structure 110 + 111 + ### Accessibility 112 + - **Contrast ratios**: Ensure WCAG compliance (4.5:1 for text, 3:1 for UI components) 113 + - **Do not rely on color alone**: Use icons, labels, or patterns alongside color 114 + - **Test for color blindness**: Verify red and green combinations work for all users 115 + 116 + ### Cohesion 117 + - **Consistent palette**: Use colors from defined palette, not arbitrary choices 118 + - **Systematic application**: Same color meanings throughout (green always equals success) 119 + - **Temperature consistency**: Warm palette stays warm, cool stays cool 120 + 121 + **NEVER**: 122 + - Use every color in the rainbow (choose 2-4 colors beyond neutrals) 123 + - Apply color randomly without semantic meaning 124 + - Put gray text on colored backgrounds. It looks washed out. Use a darker shade of the background color or transparency instead. 125 + - Use pure gray for neutrals. Add subtle color tint (warm or cool) for sophistication. 126 + - Use pure black (#000) or pure white (#fff) for large areas. 127 + - Violate WCAG contrast requirements 128 + - Use color as the only indicator (accessibility issue) 129 + - Make everything colorful (defeats the purpose) 130 + - Default to purple-blue gradients (AI slop aesthetic) 131 + 132 + ## Verify Color Addition 133 + 134 + Test that colorization improves the experience. 135 + 136 + - **Better hierarchy**: Does color guide attention appropriately? 137 + - **Clearer meaning**: Does color help users understand states or categories? 138 + - **More engaging**: Does the interface feel warmer and more inviting? 139 + - **Still accessible**: Do all color combinations meet WCAG standards? 140 + - **Not overwhelming**: Is color balanced and purposeful? 141 + 142 + Remember: Color is emotional and powerful. Use it to create warmth, guide attention, communicate meaning, and express personality. But restraint and strategy matter more than saturation and variety. Be colorful, but be intentional.
+201
skills/critique/SKILL.md
··· 1 + --- 2 + name: critique 3 + description: "Evaluate design from a UX perspective, assessing visual hierarchy, information architecture, emotional resonance, cognitive load, and overall quality with quantitative scoring, persona-based testing, and actionable feedback. Use when the user asks to review, critique, evaluate, or give feedback on a design or component." 4 + argument-hint: "[area (feature, page, component...)]" 5 + user-invocable: true 6 + --- 7 + 8 + ## MANDATORY PREPARATION 9 + 10 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: what the interface is trying to accomplish. 11 + 12 + --- 13 + 14 + Conduct a holistic design critique, evaluating whether the interface actually works: not just technically, but as a designed experience. Think like a design director giving feedback. 15 + 16 + ## Phase 1: Design Critique 17 + 18 + Evaluate the interface across these dimensions. 19 + 20 + ### 1. AI Slop Detection (CRITICAL) 21 + 22 + **This is the most important check.** Does this look like every other AI-generated interface from 2024-2025? 23 + 24 + Review the design against ALL the DO NOT guidelines in the frontend-design skill. They are the fingerprints of AI-generated work. Check for the AI color palette, gradient text, dark mode with glowing accents, glassmorphism, hero metric layouts, identical card grids, generic fonts, and all other tells. 25 + 26 + **The test**: If you showed this to someone and said "AI made this," would they believe you immediately? If yes, that is the problem. 27 + 28 + ### 2. Visual Hierarchy 29 + - Does the eye flow to the most important element first? 30 + - Is there a clear primary action? Can you spot it in 2 seconds? 31 + - Do size, color, and position communicate importance correctly? 32 + - Is there visual competition between elements that should have different weights? 33 + 34 + ### 3. Information Architecture and Cognitive Load 35 + > *Consult [cognitive-load](reference/cognitive-load.md) for the working memory rule and 8-item checklist* 36 + - Is the structure intuitive? Would a new user understand the organization? 37 + - Is related content grouped logically? 38 + - Are there too many choices at once? Count visible options at each decision point. If more than 4, flag it. 39 + - Is the navigation clear and predictable? 40 + - **Progressive disclosure**: Is complexity revealed only when needed, or dumped on the user upfront? 41 + - **Run the 8-item cognitive load checklist** from the reference. Report failure count: 0-1 equals low (good), 2-3 equals moderate, 4+ equals critical. 42 + 43 + ### 4. Emotional Journey 44 + - What emotion does this interface evoke? Is that intentional? 45 + - Does it match the brand personality? 46 + - Does it feel trustworthy, approachable, premium, playful: whatever it should feel? 47 + - Would the target user feel "this is for me"? 48 + - **Peak-end rule**: Is the most intense moment positive? Does the experience end well (confirmation, celebration, clear next step)? 49 + - **Emotional valleys**: Check for onboarding frustration, error cliffs, feature discovery gaps, or anxiety spikes at high-stakes moments (payment, delete, commit) 50 + - **Interventions at negative moments**: Are there design interventions where users are likely to feel frustrated or anxious? (progress indicators, reassurance copy, undo options, social proof) 51 + 52 + ### 5. Discoverability and Affordance 53 + - Are interactive elements obviously interactive? 54 + - Would a user know what to do without instructions? 55 + - Are hover and focus states providing useful feedback? 56 + - Are there hidden features that should be more visible? 57 + 58 + ### 6. Composition and Balance 59 + - Does the layout feel balanced or uncomfortably weighted? 60 + - Is whitespace used intentionally or just leftover? 61 + - Is there visual rhythm in spacing and repetition? 62 + - Does asymmetry feel designed or accidental? 63 + 64 + ### 7. Typography as Communication 65 + - Does the type hierarchy clearly signal what to read first, second, third? 66 + - Is body text comfortable to read? (line length, spacing, size) 67 + - Do font choices reinforce the brand or tone? 68 + - Is there enough contrast between heading levels? 69 + 70 + ### 8. Color with Purpose 71 + - Is color used to communicate, not just decorate? 72 + - Does the palette feel cohesive? 73 + - Are accent colors drawing attention to the right things? 74 + - Does it work for colorblind users? (not just technically: does meaning still come through?) 75 + 76 + ### 9. States and Edge Cases 77 + - Empty states: Do they guide users toward action, or just say "nothing here"? 78 + - Loading states: Do they reduce perceived wait time? 79 + - Error states: Are they helpful and non-blaming? 80 + - Success states: Do they confirm and guide next steps? 81 + 82 + ### 10. Microcopy and Voice 83 + - Is the writing clear and concise? 84 + - Does it sound like a human (the right human for this brand)? 85 + - Are labels and buttons unambiguous? 86 + - Does error copy help users fix the problem? 87 + 88 + ## Phase 2: Present Findings 89 + 90 + Structure your feedback as a design director would. 91 + 92 + ### Design Health Score 93 + > *Consult [heuristics-scoring](reference/heuristics-scoring.md)* 94 + 95 + Score each of Nielsen 10 heuristics 0-4. Present as a table: 96 + 97 + | # | Heuristic | Score | Key Issue | 98 + |---|-----------|-------|-----------| 99 + | 1 | Visibility of System Status | ? | [specific finding or "—" if solid] | 100 + | 2 | Match System and Real World | ? | | 101 + | 3 | User Control and Freedom | ? | | 102 + | 4 | Consistency and Standards | ? | | 103 + | 5 | Error Prevention | ? | | 104 + | 6 | Recognition Rather Than Recall | ? | | 105 + | 7 | Flexibility and Efficiency | ? | | 106 + | 8 | Aesthetic and Minimalist Design | ? | | 107 + | 9 | Error Recovery | ? | | 108 + | 10 | Help and Documentation | ? | | 109 + | **Total** | | **??/40** | **[Rating band]** | 110 + 111 + Be honest with scores. A 4 means genuinely excellent. Most real interfaces score 20-32. 112 + 113 + ### Anti-Patterns Verdict 114 + **Start here.** Pass or fail: Does this look AI-generated? List specific tells from the skill Anti-Patterns section. Be brutally honest. 115 + 116 + ### Overall Impression 117 + A brief gut reaction: what works, what does not, and the single biggest opportunity. 118 + 119 + ### What is Working 120 + Highlight 2-3 things done well. Be specific about why they work. 121 + 122 + ### Priority Issues 123 + The 3-5 most impactful design problems, ordered by importance. 124 + 125 + For each issue, tag with P0-P3 severity (consult [heuristics-scoring](reference/heuristics-scoring.md) for severity definitions): 126 + - **[P?] What**: Name the problem clearly 127 + - **Why it matters**: How this hurts users or undermines goals 128 + - **Fix**: What to do about it (be concrete) 129 + - **Suggested command**: Which command could address this (from: {{available_commands}}) 130 + 131 + ### Persona Red Flags 132 + > *Consult [personas](reference/personas.md)* 133 + 134 + Auto-select 2-3 personas most relevant to this interface type (use the selection table in the reference). If {{config_file}} contains a Design Context section from teach-impeccable, also generate 1-2 project-specific personas from the audience or brand info. 135 + 136 + For each selected persona, walk through the primary user action and list specific red flags found: 137 + 138 + **Alex (Power User)**: No keyboard shortcuts detected. Form requires 8 clicks for primary action. Forced modal onboarding. High abandonment risk. 139 + 140 + **Jordan (First-Timer)**: Icon-only nav in sidebar. Technical jargon in error messages ("404 Not Found"). No visible help. Will abandon at step 2. 141 + 142 + Be specific. Name the exact elements and interactions that fail each persona. Do not write generic persona descriptions. Write what broke for them. 143 + 144 + ### Minor Observations 145 + Quick notes on smaller issues worth addressing. 146 + 147 + **Remember**: 148 + - Be direct. Vague feedback wastes everyone time. 149 + - Be specific. "the submit button" not "some elements" 150 + - Say what is wrong AND why it matters to users 151 + - Give concrete suggestions, not just "consider exploring..." 152 + - Prioritize ruthlessly. If everything is important, nothing is. 153 + - Do not soften criticism. Developers need honest feedback to ship great design. 154 + 155 + ## Phase 3: Ask the User 156 + 157 + **After presenting findings**, use targeted questions based on what was actually found. {{ask_instruction}} These answers will shape the action plan. 158 + 159 + Ask questions along these lines (adapt to the specific findings: do NOT ask generic questions): 160 + 161 + 1. **Priority direction**: Based on the issues found, ask which category matters most to the user right now. For example: "I found problems with visual hierarchy, color usage, and information overload. Which area should we tackle first?" Offer the top 2-3 issue categories as options. 162 + 163 + 2. **Design intent**: If the critique found a tonal mismatch, ask whether it was intentional. For example: "The interface feels clinical and corporate. Is that the intended tone, or should it feel warmer, bolder, more playful?" Offer 2-3 tonal directions as options based on what would fix the issues found. 164 + 165 + 3. **Scope**: Ask how much the user wants to take on. For example: "I found N issues. Want to address everything, or focus on the top 3?" Offer scope options like "Top 3 only", "All issues", "Critical issues only". 166 + 167 + 4. **Constraints** (optional: only ask if relevant): If the findings touch many areas, ask if anything is off-limits. For example: "Should any sections stay as-is?" This prevents the plan from touching things the user considers done. 168 + 169 + **Rules for questions**: 170 + - Every question must reference specific findings from Phase 2. Never ask generic "who is your audience?" questions 171 + - Keep it to 2-4 questions maximum. Respect the user time. 172 + - Offer concrete options, not open-ended prompts. 173 + - If findings are straightforward (e.g., only 1-2 clear issues), skip questions and go directly to Phase 4 174 + 175 + ## Phase 4: Recommended Actions 176 + 177 + **After receiving the user answers**, present a prioritized action summary reflecting the user priorities and scope from Phase 3. 178 + 179 + ### Action Summary 180 + 181 + List recommended commands in priority order, based on the user answers: 182 + 183 + 1. **`{{command_prefix}}command-name`**: Brief description of what to fix (specific context from critique findings) 184 + 2. **`{{command_prefix}}command-name`**: Brief description (specific context) 185 + ... 186 + 187 + **Rules for recommendations**: 188 + - Only recommend commands from: {{available_commands}} 189 + - Order by the user stated priorities first, then by impact 190 + - Each item description should carry enough context that the command knows what to focus on 191 + - Map each Priority Issue to the appropriate command 192 + - Skip commands that would address zero issues 193 + - If the user chose a limited scope, only include items within that scope 194 + - If the user marked areas as off-limits, exclude commands that would touch those areas 195 + - End with {{command_prefix}}polish as the final step if any fixes were recommended 196 + 197 + After presenting the summary, tell the user: 198 + 199 + > You can ask me to run these one at a time, all at once, or in any order you prefer. 200 + > 201 + > Re-run {{command_prefix}}critique after fixes to see your score improve.
+106
skills/critique/reference/cognitive-load.md
··· 1 + # Cognitive Load Assessment 2 + 3 + Cognitive load is the total mental effort required to use an interface. Overloaded users make mistakes, get frustrated, and leave. This reference helps identify and fix cognitive overload. 4 + 5 + --- 6 + 7 + ## Three Types of Cognitive Load 8 + 9 + ### Intrinsic Load: The Task Itself 10 + Complexity inherent to what the user is trying to do. You cannot eliminate this, but you can structure it. 11 + 12 + **Manage it by**: 13 + - Breaking complex tasks into discrete steps 14 + - Providing scaffolding (templates, defaults, examples) 15 + - Progressive disclosure. Show what is needed now, hide the rest. 16 + - Grouping related decisions together 17 + 18 + ### Extraneous Load: Bad Design 19 + Mental effort caused by poor design choices. **Eliminate this ruthlessly.** It is pure waste. 20 + 21 + **Common sources**: 22 + - Confusing navigation that requires mental mapping 23 + - Unclear labels that force users to guess meaning 24 + - Visual clutter competing for attention 25 + - Inconsistent patterns that prevent learning 26 + - Unnecessary steps between user intent and result 27 + 28 + ### Germane Load: Learning Effort 29 + Mental effort spent building understanding. This is good cognitive load. It leads to mastery. 30 + 31 + **Support it by**: 32 + - Progressive disclosure that reveals complexity gradually 33 + - Consistent patterns that reward learning 34 + - Feedback that confirms correct understanding 35 + - Onboarding that teaches through action, not walls of text 36 + 37 + --- 38 + 39 + ## Cognitive Load Checklist 40 + 41 + Evaluate the interface against these 8 items: 42 + 43 + - [ ] **Single focus**: Can the user complete their primary task without distraction from competing elements? 44 + - [ ] **Chunking**: Is information presented in digestible groups (4 or fewer items per group)? 45 + - [ ] **Grouping**: Are related items visually grouped together (proximity, borders, shared background)? 46 + - [ ] **Visual hierarchy**: Is it immediately clear what is most important on the screen? 47 + - [ ] **One thing at a time**: Can the user focus on a single decision before moving to the next? 48 + - [ ] **Minimal choices**: Are decisions simplified (4 or fewer visible options at any decision point)? 49 + - [ ] **Working memory**: Does the user need to remember information from a previous screen to act on the current one? 50 + - [ ] **Progressive disclosure**: Is complexity revealed only when the user needs it? 51 + 52 + **Scoring**: Count the failed items. 0-1 failures equals low cognitive load (good). 2-3 equals moderate (address soon). 4+ equals high cognitive load (critical fix needed). 53 + 54 + --- 55 + 56 + ## The Working Memory Rule 57 + 58 + **Humans can hold 4 or fewer items in working memory at once** (Miller Law revised by Cowan, 2001). 59 + 60 + At any decision point, count the number of distinct options, actions, or pieces of information a user must simultaneously consider: 61 + - **4 or fewer items**: Within working memory limits. Manageable. 62 + - **5-7 items**: Pushing the boundary. Consider grouping or progressive disclosure. 63 + - **8+ items**: Overloaded. Users will skip, misclick, or abandon. 64 + 65 + **Practical applications**: 66 + - Navigation menus: 5 or fewer top-level items (group the rest under clear categories) 67 + - Form sections: 4 or fewer fields visible per group before a visual break 68 + - Action buttons: 1 primary, 1-2 secondary, group the rest in a menu 69 + - Dashboard widgets: 4 or fewer key metrics visible without scrolling 70 + - Pricing tiers: 3 or fewer options (more causes analysis paralysis) 71 + 72 + --- 73 + 74 + ## Common Cognitive Load Violations 75 + 76 + ### 1. The Wall of Options 77 + **Problem**: Presenting 10+ choices at once with no hierarchy. 78 + **Fix**: Group into categories, highlight recommended, use progressive disclosure. 79 + 80 + ### 2. The Memory Bridge 81 + **Problem**: User must remember info from step 1 to complete step 3. 82 + **Fix**: Keep relevant context visible, or repeat it where it is needed. 83 + 84 + ### 3. The Hidden Navigation 85 + **Problem**: User must build a mental map of where things are. 86 + **Fix**: Always show current location (breadcrumbs, active states, progress indicators). 87 + 88 + ### 4. The Jargon Barrier 89 + **Problem**: Technical or domain language forces translation effort. 90 + **Fix**: Use plain language. If domain terms are unavoidable, define them inline. 91 + 92 + ### 5. The Visual Noise Floor 93 + **Problem**: Every element has the same visual weight. Nothing stands out. 94 + **Fix**: Establish clear hierarchy: one primary element, 2-3 secondary, everything else muted. 95 + 96 + ### 6. The Inconsistent Pattern 97 + **Problem**: Similar actions work differently in different places. 98 + **Fix**: Standardize interaction patterns. Same type of action equals same type of UI. 99 + 100 + ### 7. The Multi-Task Demand 101 + **Problem**: Interface requires processing multiple simultaneous inputs (reading plus deciding plus navigating). 102 + **Fix**: Sequence the steps. Let the user do one thing at a time. 103 + 104 + ### 8. The Context Switch 105 + **Problem**: User must jump between screens, tabs, or modals to gather info for a single decision. 106 + **Fix**: Co-locate the information needed for each decision. Reduce back-and-forth.
+234
skills/critique/reference/heuristics-scoring.md
··· 1 + # Heuristics Scoring Guide 2 + 3 + Score each of Nielsen 10 Usability Heuristics on a 0-4 scale. Be honest. A 4 means genuinely excellent, not "good enough." 4 + 5 + ## Nielsen 10 Heuristics 6 + 7 + ### 1. Visibility of System Status 8 + 9 + Keep users informed about what is happening through timely, appropriate feedback. 10 + 11 + **Check for**: 12 + - Loading indicators during async operations 13 + - Confirmation of user actions (save, submit, delete) 14 + - Progress indicators for multi-step processes 15 + - Current location in navigation (breadcrumbs, active states) 16 + - Form validation feedback (inline, not just on submit) 17 + 18 + **Scoring**: 19 + | Score | Criteria | 20 + |-------|----------| 21 + | 0 | No feedback. User is guessing what happened. | 22 + | 1 | Rare feedback. Most actions produce no visible response. | 23 + | 2 | Partial. Some states communicated, major gaps remain. | 24 + | 3 | Good. Most operations give clear feedback, minor gaps. | 25 + | 4 | Excellent. Every action confirms, progress is always visible. | 26 + 27 + ### 2. Match Between System and Real World 28 + 29 + Speak the user language. Follow real-world conventions. Information appears in natural, logical order. 30 + 31 + **Check for**: 32 + - Familiar terminology (no unexplained jargon) 33 + - Logical information order matching user expectations 34 + - Recognizable icons and metaphors 35 + - Domain-appropriate language for the target audience 36 + - Natural reading flow (left-to-right, top-to-bottom priority) 37 + 38 + **Scoring**: 39 + | Score | Criteria | 40 + |-------|----------| 41 + | 0 | Pure tech jargon, alien to users. | 42 + | 1 | Mostly confusing. Requires domain expertise to navigate. | 43 + | 2 | Mixed. Some plain language, some jargon leaks through. | 44 + | 3 | Mostly natural. Occasional term needs context. | 45 + | 4 | Speaks the user language fluently throughout. | 46 + 47 + ### 3. User Control and Freedom 48 + 49 + Users need a clear "emergency exit" from unwanted states without extended dialogue. 50 + 51 + **Check for**: 52 + - Undo and redo functionality 53 + - Cancel buttons on forms and modals 54 + - Clear navigation back to safety (home, previous) 55 + - Easy way to clear filters, search, selections 56 + - Escape from long or multi-step processes 57 + 58 + **Scoring**: 59 + | Score | Criteria | 60 + |-------|----------| 61 + | 0 | Users get trapped. No way out without refreshing. | 62 + | 1 | Difficult exits. Must find obscure paths to escape. | 63 + | 2 | Some exits. Main flows have escape, edge cases do not. | 64 + | 3 | Good control. Users can exit and undo most actions. | 65 + | 4 | Full control. Undo, cancel, back, and escape everywhere. | 66 + 67 + ### 4. Consistency and Standards 68 + 69 + Users should not wonder whether different words, situations, or actions mean the same thing. 70 + 71 + **Check for**: 72 + - Consistent terminology throughout the interface 73 + - Same actions produce same results everywhere 74 + - Platform conventions followed (standard UI patterns) 75 + - Visual consistency (colors, typography, spacing, components) 76 + - Consistent interaction patterns (same gesture equals same behavior) 77 + 78 + **Scoring**: 79 + | Score | Criteria | 80 + |-------|----------| 81 + | 0 | Inconsistent everywhere. Feels like different products stitched together. | 82 + | 1 | Many inconsistencies. Similar things look or behave differently. | 83 + | 2 | Partially consistent. Main flows match, details diverge. | 84 + | 3 | Mostly consistent. Occasional deviation, nothing confusing. | 85 + | 4 | Fully consistent. Cohesive system, predictable behavior. | 86 + 87 + ### 5. Error Prevention 88 + 89 + Better than good error messages is a design that prevents problems in the first place. 90 + 91 + **Check for**: 92 + - Confirmation before destructive actions (delete, overwrite) 93 + - Constraints preventing invalid input (date pickers, dropdowns) 94 + - Smart defaults that reduce errors 95 + - Clear labels that prevent misunderstanding 96 + - Autosave and draft recovery 97 + 98 + **Scoring**: 99 + | Score | Criteria | 100 + |-------|----------| 101 + | 0 | Errors easy to make. No guardrails anywhere. | 102 + | 1 | Few safeguards. Some inputs validated, most are not. | 103 + | 2 | Partial prevention. Common errors caught, edge cases slip. | 104 + | 3 | Good prevention. Most error paths blocked proactively. | 105 + | 4 | Excellent. Errors nearly impossible through smart constraints. | 106 + 107 + ### 6. Recognition Rather Than Recall 108 + 109 + Minimize memory load. Make objects, actions, and options visible or easily retrievable. 110 + 111 + **Check for**: 112 + - Visible options (not buried in hidden menus) 113 + - Contextual help when needed (tooltips, inline hints) 114 + - Recent items and history 115 + - Autocomplete and suggestions 116 + - Labels on icons (not icon-only navigation) 117 + 118 + **Scoring**: 119 + | Score | Criteria | 120 + |-------|----------| 121 + | 0 | Heavy memorization. Users must remember paths and commands. | 122 + | 1 | Mostly recall. Many hidden features, few visible cues. | 123 + | 2 | Some aids. Main actions visible, secondary features hidden. | 124 + | 3 | Good recognition. Most things discoverable, few memory demands. | 125 + | 4 | Everything discoverable. Users never need to memorize. | 126 + 127 + ### 7. Flexibility and Efficiency of Use 128 + 129 + Accelerators. Invisible to novices. Speed up expert interaction. 130 + 131 + **Check for**: 132 + - Keyboard shortcuts for common actions 133 + - Customizable interface elements 134 + - Recent items and favorites 135 + - Bulk or batch actions 136 + - Power user features that do not complicate the basics 137 + 138 + **Scoring**: 139 + | Score | Criteria | 140 + |-------|----------| 141 + | 0 | One rigid path. No shortcuts or alternatives. | 142 + | 1 | Limited flexibility. Few alternatives to the main path. | 143 + | 2 | Some shortcuts. Basic keyboard support, limited bulk actions. | 144 + | 3 | Good accelerators. Keyboard nav, some customization. | 145 + | 4 | Highly flexible. Multiple paths, power features, customizable. | 146 + 147 + ### 8. Aesthetic and Minimalist Design 148 + 149 + Interfaces should not contain irrelevant or rarely needed information. Every element should serve a purpose. 150 + 151 + **Check for**: 152 + - Only necessary information visible at each step 153 + - Clear visual hierarchy directing attention 154 + - Purposeful use of color and emphasis 155 + - No decorative clutter competing for attention 156 + - Focused, uncluttered layouts 157 + 158 + **Scoring**: 159 + | Score | Criteria | 160 + |-------|----------| 161 + | 0 | Overwhelming. Everything competes for attention equally. | 162 + | 1 | Cluttered. Too much noise, hard to find what matters. | 163 + | 2 | Some clutter. Main content clear, periphery noisy. | 164 + | 3 | Mostly clean. Focused design, minor visual noise. | 165 + | 4 | Perfectly minimal. Every element earns its pixel. | 166 + 167 + ### 9. Help Users Recognize, Diagnose, and Recover from Errors 168 + 169 + Error messages should use plain language, precisely indicate the problem, and constructively suggest a solution. 170 + 171 + **Check for**: 172 + - Plain language error messages (no error codes for users) 173 + - Specific problem identification ("Email is missing @" not "Invalid input") 174 + - Actionable recovery suggestions 175 + - Errors displayed near the source of the problem 176 + - Non-blocking error handling (do not wipe the form) 177 + 178 + **Scoring**: 179 + | Score | Criteria | 180 + |-------|----------| 181 + | 0 | Cryptic errors. Codes, jargon, or no message at all. | 182 + | 1 | Vague errors. "Something went wrong" with no guidance. | 183 + | 2 | Clear but unhelpful. Names the problem but not the fix. | 184 + | 3 | Clear with suggestions. Identifies problem and offers next steps. | 185 + | 4 | Perfect recovery. Pinpoints issue, suggests fix, preserves user work. | 186 + 187 + ### 10. Help and Documentation 188 + 189 + Even if the system is usable without docs, help should be easy to find, task-focused, and concise. 190 + 191 + **Check for**: 192 + - Searchable help or documentation 193 + - Contextual help (tooltips, inline hints, guided tours) 194 + - Task-focused organization (not feature-organized) 195 + - Concise, scannable content 196 + - Easy access without leaving current context 197 + 198 + **Scoring**: 199 + | Score | Criteria | 200 + |-------|----------| 201 + | 0 | No help available anywhere. | 202 + | 1 | Help exists but hard to find or irrelevant. | 203 + | 2 | Basic help. FAQ or docs exist, not contextual. | 204 + | 3 | Good documentation. Searchable, mostly task-focused. | 205 + | 4 | Excellent contextual help. Right info at the right moment. | 206 + 207 + --- 208 + 209 + ## Score Summary 210 + 211 + **Total possible**: 40 points (10 heuristics times 4 max) 212 + 213 + | Score Range | Rating | What It Means | 214 + |-------------|--------|---------------| 215 + | 36-40 | Excellent | Minor polish only. Ship it. | 216 + | 28-35 | Good | Address weak areas, solid foundation. | 217 + | 20-27 | Acceptable | Significant improvements needed before users are happy. | 218 + | 12-19 | Poor | Major UX overhaul required. Core experience broken. | 219 + | 0-11 | Critical | Redesign needed. Unusable in current state. | 220 + 221 + --- 222 + 223 + ## Issue Severity (P0-P3) 224 + 225 + Tag each individual issue found during scoring with a priority level: 226 + 227 + | Priority | Name | Description | Action | 228 + |----------|------|-------------|--------| 229 + | **P0** | Blocking | Prevents task completion entirely | Fix immediately. This is a showstopper. | 230 + | **P1** | Major | Causes significant difficulty or confusion | Fix before release. | 231 + | **P2** | Minor | Annoyance, but workaround exists | Fix in next pass. | 232 + | **P3** | Polish | Nice-to-fix, no real user impact | Fix if time permits. | 233 + 234 + **Tip**: If you are unsure between two levels, ask: "Would a user contact support about this?" If yes, it is at least P1.
+178
skills/critique/reference/personas.md
··· 1 + # Persona-Based Design Testing 2 + 3 + Test the interface through the eyes of 5 distinct user archetypes. Each persona exposes different failure modes that a single "design director" perspective would miss. 4 + 5 + **How to use**: Select 2-3 personas most relevant to the interface being critiqued. Walk through the primary user action as each persona. Report specific red flags. Not generic concerns. 6 + 7 + --- 8 + 9 + ## 1. Impatient Power User — "Alex" 10 + 11 + **Profile**: Expert with similar products. Expects efficiency, hates hand-holding. Will find shortcuts or leave. 12 + 13 + **Behaviors**: 14 + - Skips all onboarding and instructions 15 + - Looks for keyboard shortcuts immediately 16 + - Tries to bulk-select, batch-edit, and automate 17 + - Gets frustrated by required steps that feel unnecessary 18 + - Abandons if anything feels slow or patronizing 19 + 20 + **Test Questions**: 21 + - Can Alex complete the core task in under 60 seconds? 22 + - Are there keyboard shortcuts for common actions? 23 + - Can onboarding be skipped entirely? 24 + - Do modals have keyboard dismiss (Esc)? 25 + - Is there a "power user" path (shortcuts, bulk actions)? 26 + 27 + **Red Flags** (report these specifically): 28 + - Forced tutorials or unskippable onboarding 29 + - No keyboard navigation for primary actions 30 + - Slow animations that cannot be skipped 31 + - One-item-at-a-time workflows where batch would be natural 32 + - Redundant confirmation steps for low-risk actions 33 + 34 + --- 35 + 36 + ## 2. Confused First-Timer — "Jordan" 37 + 38 + **Profile**: Never used this type of product. Needs guidance at every step. Will abandon rather than figure it out. 39 + 40 + **Behaviors**: 41 + - Reads all instructions carefully 42 + - Hesitates before clicking anything unfamiliar 43 + - Looks for help or support constantly 44 + - Misunderstands jargon and abbreviations 45 + - Takes the most literal interpretation of any label 46 + 47 + **Test Questions**: 48 + - Is the first action obviously clear within 5 seconds? 49 + - Are all icons labeled with text? 50 + - Is there contextual help at decision points? 51 + - Does terminology assume prior knowledge? 52 + - Is there a clear "back" or "undo" at every step? 53 + 54 + **Red Flags** (report these specifically): 55 + - Icon-only navigation with no labels 56 + - Technical jargon without explanation 57 + - No visible help option or guidance 58 + - Ambiguous next steps after completing an action 59 + - No confirmation that an action succeeded 60 + 61 + --- 62 + 63 + ## 3. Accessibility-Dependent User — "Sam" 64 + 65 + **Profile**: Uses screen reader (VoiceOver or NVDA), keyboard-only navigation. May have low vision, motor impairment, or cognitive differences. 66 + 67 + **Behaviors**: 68 + - Tabs through the interface linearly 69 + - Relies on ARIA labels and heading structure 70 + - Cannot see hover states or visual-only indicators 71 + - Needs adequate color contrast (4.5:1 minimum) 72 + - May use browser zoom up to 200% 73 + 74 + **Test Questions**: 75 + - Can the entire primary flow be completed keyboard-only? 76 + - Are all interactive elements focusable with visible focus indicators? 77 + - Do images have meaningful alt text? 78 + - Is color contrast WCAG AA compliant (4.5:1 for text)? 79 + - Does the screen reader announce state changes (loading, success, errors)? 80 + 81 + **Red Flags** (report these specifically): 82 + - Click-only interactions with no keyboard alternative 83 + - Missing or invisible focus indicators 84 + - Meaning conveyed by color alone (red equals error, green equals success) 85 + - Unlabeled form fields or buttons 86 + - Time-limited actions without extension option 87 + - Custom components that break screen reader flow 88 + 89 + --- 90 + 91 + ## 4. Deliberate Stress Tester — "Riley" 92 + 93 + **Profile**: Methodical user who pushes interfaces beyond the happy path. Tests edge cases, tries unexpected inputs, and probes for gaps in the experience. 94 + 95 + **Behaviors**: 96 + - Tests edge cases intentionally (empty states, long strings, special characters) 97 + - Submits forms with unexpected data (emoji, RTL text, very long values) 98 + - Tries to break workflows by navigating backwards, refreshing mid-flow, or opening in multiple tabs 99 + - Looks for inconsistencies between what the UI promises and what actually happens 100 + - Documents problems methodically 101 + 102 + **Test Questions**: 103 + - What happens at the edges (0 items, 1000 items, very long text)? 104 + - Do error states recover gracefully or leave the UI in a broken state? 105 + - What happens on refresh mid-workflow? Is state preserved? 106 + - Are there features that appear to work but produce broken results? 107 + - How does the UI handle unexpected input (emoji, special chars, paste from Excel)? 108 + 109 + **Red Flags** (report these specifically): 110 + - Features that appear to work but silently fail or produce wrong results 111 + - Error handling that exposes technical details or leaves UI in a broken state 112 + - Empty states that show nothing useful ("No results" with no guidance) 113 + - Workflows that lose user data on refresh or navigation 114 + - Inconsistent behavior between similar interactions in different parts of the UI 115 + 116 + --- 117 + 118 + ## 5. Distracted Mobile User — "Casey" 119 + 120 + **Profile**: Using phone one-handed on the go. Frequently interrupted. Possibly on a slow connection. 121 + 122 + **Behaviors**: 123 + - Uses thumb only. Prefers bottom-of-screen actions 124 + - Gets interrupted mid-flow and returns later 125 + - Switches between apps frequently 126 + - Has limited attention span and low patience 127 + - Types as little as possible, prefers taps and selections 128 + 129 + **Test Questions**: 130 + - Are primary actions in the thumb zones (bottom half of screen)? 131 + - Is state preserved if the user leaves and returns? 132 + - Does it work on slow connections (3G)? 133 + - Can forms leverage autocomplete and smart defaults? 134 + - Are touch targets at least 44 by 44pt? 135 + 136 + **Red Flags** (report these specifically): 137 + - Important actions positioned at the top of the screen (unreachable by thumb) 138 + - No state persistence. Progress lost on tab switch or interruption 139 + - Large text inputs required where selection would work 140 + - Heavy assets loading on every page (no lazy loading) 141 + - Tiny tap targets or targets too close together 142 + 143 + --- 144 + 145 + ## Selecting Personas 146 + 147 + Choose personas based on the interface type: 148 + 149 + | Interface Type | Primary Personas | Why | 150 + |---------------|-----------------|-----| 151 + | Landing page or marketing | Jordan, Riley, Casey | First impressions, trust, mobile | 152 + | Dashboard or admin | Alex, Sam | Power users, accessibility | 153 + | E-commerce or checkout | Casey, Riley, Jordan | Mobile, edge cases, clarity | 154 + | Onboarding flow | Jordan, Casey | Confusion, interruption | 155 + | Data-heavy or analytics | Alex, Sam | Efficiency, keyboard nav | 156 + | Form-heavy or wizard | Jordan, Sam, Casey | Clarity, accessibility, mobile | 157 + 158 + --- 159 + 160 + ## Project-Specific Personas 161 + 162 + If {{config_file}} contains a Design Context section (generated by teach-impeccable), derive 1-2 additional personas from the audience and brand information: 163 + 164 + 1. Read the target audience description 165 + 2. Identify the primary user archetype not covered by the 5 predefined personas 166 + 3. Create a persona following this template: 167 + 168 + ``` 169 + ### [Role] — "[Name]" 170 + 171 + **Profile**: [2-3 key characteristics derived from Design Context] 172 + 173 + **Behaviors**: [3-4 specific behaviors based on the described audience] 174 + 175 + **Red Flags**: [3-4 things that would alienate this specific user type] 176 + ``` 177 + 178 + Only generate project-specific personas when real Design Context data is available. Do not invent audience details. Use the 5 predefined personas when no context exists.
+303
skills/delight/SKILL.md
··· 1 + --- 2 + name: delight 3 + description: "Add moments of joy, personality, and unexpected touches that make interfaces memorable and enjoyable to use. Elevates functional to delightful. Use when the user asks to add polish, personality, animations, micro-interactions, delight, or make an interface feel fun or memorable." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Identify opportunities to add moments of joy, personality, and unexpected polish that transform functional interfaces into delightful experiences. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: what is appropriate for the domain (playful vs professional vs quirky vs elegant). 13 + 14 + --- 15 + 16 + ## Assess Delight Opportunities 17 + 18 + Identify where delight would enhance (not distract from) the experience. 19 + 20 + ### 1. Find natural delight moments 21 + - **Success states**: Completed actions (save, send, publish) 22 + - **Empty states**: First-time experiences, onboarding 23 + - **Loading states**: Waiting periods that could be entertaining 24 + - **Achievements**: Milestones, streaks, completions 25 + - **Interactions**: Hover states, clicks, drags 26 + - **Errors**: Softening frustrating moments 27 + - **Easter eggs**: Hidden discoveries for curious users 28 + 29 + ### 2. Understand the context 30 + - What is the brand personality? (Playful? Professional? Quirky? Elegant?) 31 + - Who is the audience? (Tech-savvy? Creative? Corporate?) 32 + - What is the emotional context? (Accomplishment? Exploration? Frustration?) 33 + - What is appropriate? (Banking app does not equal gaming app) 34 + 35 + ### 3. Define delight strategy 36 + - **Subtle sophistication**: Refined micro-interactions (luxury brands) 37 + - **Playful personality**: Whimsical illustrations and copy (consumer apps) 38 + - **Helpful surprises**: Anticipating needs before users ask (productivity tools) 39 + - **Sensory richness**: Satisfying sounds, smooth animations (creative tools) 40 + 41 + If any of these are unclear from the codebase, {{ask_instruction}} 42 + 43 + **CRITICAL**: Delight should enhance usability, never obscure it. If users notice the delight more than accomplishing their goal, you have gone too far. 44 + 45 + ## Delight Principles 46 + 47 + Follow these guidelines. 48 + 49 + ### Delight Amplifies, Never Blocks 50 + - Delight moments should be quick (less than 1 second) 51 + - Never delay core functionality for delight 52 + - Make delight skippable or subtle 53 + - Respect user time and task focus 54 + 55 + ### Surprise and Discovery 56 + - Hide delightful details for users to discover 57 + - Reward exploration and curiosity 58 + - Do not announce every delight moment 59 + - Let users share discoveries with others 60 + 61 + ### Appropriate to Context 62 + - Match delight to emotional moment (celebrate success, empathize with errors) 63 + - Respect the user state (do not be playful during critical errors) 64 + - Match brand personality and audience expectations 65 + - Cultural sensitivity (what is delightful varies by culture) 66 + 67 + ### Compound Over Time 68 + - Delight should remain fresh with repeated use 69 + - Vary responses (not same animation every time) 70 + - Reveal deeper layers with continued use 71 + - Build anticipation through patterns 72 + 73 + ## Delight Techniques 74 + 75 + Add personality and joy through these methods. 76 + 77 + ### Micro-interactions and Animation 78 + 79 + **Button delight**: 80 + ```css 81 + /* Satisfying button press */ 82 + .button { 83 + transition: transform 0.1s, box-shadow 0.1s; 84 + } 85 + .button:active { 86 + transform: translateY(2px); 87 + box-shadow: 0 2px 4px rgba(0,0,0,0.2); 88 + } 89 + 90 + /* Ripple effect on click */ 91 + /* Smooth lift on hover */ 92 + .button:hover { 93 + transform: translateY(-2px); 94 + transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1); /* ease-out-quart */ 95 + } 96 + ``` 97 + 98 + **Loading delight**: 99 + - Playful loading animations (not just spinners) 100 + - Personality in loading messages (write product-specific ones, not generic AI filler) 101 + - Progress indication with encouraging messages 102 + - Skeleton screens with subtle animations 103 + 104 + **Success animations**: 105 + - Checkmark draw animation 106 + - Confetti burst for major achievements 107 + - Gentle scale and fade for confirmation 108 + - Satisfying sound effects (subtle) 109 + 110 + **Hover surprises**: 111 + - Icons that animate on hover 112 + - Color shifts or glow effects 113 + - Tooltip reveals with personality 114 + - Cursor changes (custom cursors for branded experiences) 115 + 116 + ### Personality in Copy 117 + 118 + **Playful error messages**: 119 + ``` 120 + "Error 404" 121 + "This page is playing hide and seek. (And winning)" 122 + 123 + "Connection failed" 124 + "Looks like the internet took a coffee break. Want to retry?" 125 + ``` 126 + 127 + **Encouraging empty states**: 128 + ``` 129 + "No projects" 130 + "Your canvas awaits. Create something amazing." 131 + 132 + "No messages" 133 + "Inbox zero! You're crushing it today." 134 + ``` 135 + 136 + **Playful labels and tooltips**: 137 + ``` 138 + "Delete" 139 + "Send to void" (for playful brand) 140 + 141 + "Help" 142 + "Rescue me" (tooltip) 143 + ``` 144 + 145 + **IMPORTANT**: Match copy personality to brand. Banks should not be wacky, but they can be warm. 146 + 147 + ### Illustrations and Visual Personality 148 + 149 + **Custom illustrations**: 150 + - Empty state illustrations (not stock icons) 151 + - Error state illustrations (friendly monsters, quirky characters) 152 + - Loading state illustrations (animated characters) 153 + - Success state illustrations (celebrations) 154 + 155 + **Icon personality**: 156 + - Custom icon set matching brand personality 157 + - Animated icons (subtle motion on hover or click) 158 + - Illustrative icons (more detailed than generic) 159 + - Consistent style across all icons 160 + 161 + **Background effects**: 162 + - Subtle particle effects 163 + - Gradient mesh backgrounds 164 + - Geometric patterns 165 + - Parallax depth 166 + - Time-of-day themes (morning vs night) 167 + 168 + ### Satisfying Interactions 169 + 170 + **Drag and drop delight**: 171 + - Lift effect on drag (shadow, scale) 172 + - Snap animation when dropped 173 + - Satisfying placement sound 174 + - Undo toast ("Dropped in wrong place? [Undo]") 175 + 176 + **Toggle switches**: 177 + - Smooth slide with spring physics 178 + - Color transition 179 + - Haptic feedback on mobile 180 + - Optional sound effect 181 + 182 + **Progress and achievements**: 183 + - Streak counters with celebratory milestones 184 + - Progress bars that "celebrate" at 100% 185 + - Badge unlocks with animation 186 + - Playful stats ("You are on fire! 5 days in a row") 187 + 188 + **Form interactions**: 189 + - Input fields that animate on focus 190 + - Checkboxes with a satisfying scale pulse when checked 191 + - Success state that celebrates valid input 192 + - Auto-grow textareas 193 + 194 + ### Sound Design 195 + 196 + **Subtle audio cues** (when appropriate): 197 + - Notification sounds (distinctive but not annoying) 198 + - Success sounds (satisfying "ding") 199 + - Error sounds (empathetic, not harsh) 200 + - Typing sounds for chat or messaging 201 + - Ambient background audio (very subtle) 202 + 203 + **IMPORTANT**: 204 + - Respect system sound settings 205 + - Provide mute option 206 + - Keep volumes quiet (subtle cues, not alarms) 207 + - Do not play on every interaction (sound fatigue is real) 208 + 209 + ### Easter Eggs and Hidden Delights 210 + 211 + **Discovery rewards**: 212 + - Konami code unlocks special theme 213 + - Hidden keyboard shortcuts (Cmd+K for special features) 214 + - Hover reveals on logos or illustrations 215 + - Alt text jokes on images (for screen reader users too!) 216 + - Console messages for developers ("Like what you see? We're hiring!") 217 + 218 + **Seasonal touches**: 219 + - Holiday themes (subtle, tasteful) 220 + - Seasonal color shifts 221 + - Weather-based variations 222 + - Time-based changes (dark at night, light during day) 223 + 224 + **Contextual personality**: 225 + - Different messages based on time of day 226 + - Responses to specific user actions 227 + - Randomized variations (not same every time) 228 + - Progressive reveals with continued use 229 + 230 + ### Loading and Waiting States 231 + 232 + **Make waiting engaging**: 233 + - Interesting loading messages that rotate 234 + - Progress bars with personality 235 + - Mini-games during long loads 236 + - Fun facts or tips while waiting 237 + - Countdown with encouraging messages 238 + 239 + ``` 240 + Loading messages: write ones specific to your product, not generic AI filler: 241 + - "Crunching your latest numbers..." 242 + - "Syncing with your team's changes..." 243 + - "Preparing your dashboard..." 244 + - "Checking for updates since yesterday..." 245 + ``` 246 + 247 + **WARNING**: Avoid cliched loading messages like "Herding pixels", "Teaching robots to dance", "Consulting the magic 8-ball", "Counting backwards from infinity". These are AI-slop copy: instantly recognizable as machine-generated. Write messages that are specific to what your product actually does. 248 + 249 + ### Celebration Moments 250 + 251 + **Success celebrations**: 252 + - Confetti for major milestones 253 + - Animated checkmarks for completions 254 + - Progress bar celebrations at 100% 255 + - "Achievement unlocked" style notifications 256 + - Personalized messages ("You published your 10th article!") 257 + 258 + **Milestone recognition**: 259 + - First-time actions get special treatment 260 + - Streak tracking and celebration 261 + - Progress toward goals 262 + - Anniversary celebrations 263 + 264 + ## Implementation Patterns 265 + 266 + **Animation libraries**: 267 + - Framer Motion (React) 268 + - GSAP (universal) 269 + - Lottie (After Effects animations) 270 + - Canvas confetti (party effects) 271 + 272 + **Sound libraries**: 273 + - Howler.js (audio management) 274 + - Use-sound (React hook) 275 + 276 + **Physics libraries**: 277 + - React Spring (spring physics) 278 + - Popmotion (animation primitives) 279 + 280 + **IMPORTANT**: File size matters. Compress images, optimize animations, lazy load delight features. 281 + 282 + **NEVER**: 283 + - Delay core functionality for delight 284 + - Force users through delightful moments (make skippable) 285 + - Use delight to hide poor UX 286 + - Overdo it (less is more) 287 + - Ignore accessibility (animate responsibly, provide alternatives) 288 + - Make every interaction delightful (special moments should be special) 289 + - Sacrifice performance for delight 290 + - Be inappropriate for context (read the room) 291 + 292 + ## Verify Delight Quality 293 + 294 + Test that delight actually delights. 295 + 296 + - **User reactions**: Do users smile? Share screenshots? 297 + - **Does not annoy**: Still pleasant after 100th time? 298 + - **Does not block**: Can users opt out or skip? 299 + - **Performant**: No jank, no slowdown 300 + - **Appropriate**: Matches brand and context 301 + - **Accessible**: Works with reduced motion, screen readers 302 + 303 + Remember: Delight is the difference between a tool and an experience. Add personality, surprise users positively, and create moments worth sharing. But always respect usability: delight should enhance, never obstruct.
+121
skills/distill/SKILL.md
··· 1 + --- 2 + name: distill 3 + description: "Strip designs to their essence by removing unnecessary complexity. Great design is simple, powerful, and clean. Use when the user asks to simplify, declutter, reduce noise, remove elements, or make a UI cleaner and more focused." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Remove unnecessary complexity from designs, revealing the essential elements and creating clarity through ruthless simplification. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 13 + 14 + --- 15 + 16 + ## Assess Current State 17 + 18 + Analyze what makes the design feel complex or cluttered. 19 + 20 + ### 1. Identify complexity sources 21 + - **Too many elements**: Competing buttons, redundant information, visual clutter 22 + - **Excessive variation**: Too many colors, fonts, sizes, styles without purpose 23 + - **Information overload**: Everything visible at once, no progressive disclosure 24 + - **Visual noise**: Unnecessary borders, shadows, backgrounds, decorations 25 + - **Confusing hierarchy**: Unclear what matters most 26 + - **Feature creep**: Too many options, actions, or paths forward 27 + 28 + ### 2. Find the essence 29 + - What is the primary user goal? (There should be ONE) 30 + - What is actually necessary vs nice-to-have? 31 + - What can be removed, hidden, or combined? 32 + - What is the 20% that delivers 80% of value? 33 + 34 + If any of these are unclear from the codebase, {{ask_instruction}} 35 + 36 + **CRITICAL**: Simplicity is not about removing features. It is about removing obstacles between users and their goals. Every element should justify its existence. 37 + 38 + ## Plan Simplification 39 + 40 + Create a ruthless editing strategy. 41 + 42 + - **Core purpose**: What is the ONE thing this should accomplish? 43 + - **Essential elements**: What is truly necessary to achieve that purpose? 44 + - **Progressive disclosure**: What can be hidden until needed? 45 + - **Consolidation opportunities**: What can be combined or integrated? 46 + 47 + **IMPORTANT**: Simplification is hard. It requires saying no to good ideas to make room for great execution. Be ruthless. 48 + 49 + ## Simplify the Design 50 + 51 + Systematically remove complexity across these dimensions. 52 + 53 + ### Information Architecture 54 + - **Reduce scope**: Remove secondary actions, optional features, redundant information 55 + - **Progressive disclosure**: Hide complexity behind clear entry points (accordions, modals, step-through flows) 56 + - **Combine related actions**: Merge similar buttons, consolidate forms, group related content 57 + - **Clear hierarchy**: ONE primary action, few secondary actions, everything else tertiary or hidden 58 + - **Remove redundancy**: If it is said elsewhere, do not repeat it here 59 + 60 + ### Visual Simplification 61 + - **Reduce color palette**: Use 1-2 colors plus neutrals, not 5-7 colors 62 + - **Limit typography**: One font family, 3-4 sizes maximum, 2-3 weights 63 + - **Remove decorations**: Eliminate borders, shadows, backgrounds that do not serve hierarchy or function 64 + - **Flatten structure**: Reduce nesting, remove unnecessary containers. Never nest cards inside cards. 65 + - **Remove unnecessary cards**: Cards are not needed for basic layout. Use spacing and alignment instead. 66 + - **Consistent spacing**: Use one spacing scale, remove arbitrary gaps 67 + 68 + ### Layout Simplification 69 + - **Linear flow**: Replace complex grids with simple vertical flow where possible 70 + - **Remove sidebars**: Move secondary content inline or hide it 71 + - **Full-width**: Use available space generously instead of complex multi-column layouts 72 + - **Consistent alignment**: Pick left or center, stick with it 73 + - **Generous white space**: Let content breathe, do not pack everything tight 74 + 75 + ### Interaction Simplification 76 + - **Reduce choices**: Fewer buttons, fewer options, clearer path forward (paradox of choice is real) 77 + - **Smart defaults**: Make common choices automatic, only ask when necessary 78 + - **Inline actions**: Replace modal flows with inline editing where possible 79 + - **Remove steps**: Can signup be one step instead of three? Can checkout be simplified? 80 + - **Clear CTAs**: ONE obvious next step, not five competing actions 81 + 82 + ### Content Simplification 83 + - **Shorter copy**: Cut every sentence in half, then do it again 84 + - **Active voice**: "Save changes" not "Changes will be saved" 85 + - **Remove jargon**: Plain language always wins 86 + - **Scannable structure**: Short paragraphs, bullet points, clear headings 87 + - **Essential information only**: Remove marketing fluff, legalese, hedging 88 + - **Remove redundant copy**: No headers restating intros, no repeated explanations, say it once 89 + 90 + ### Code Simplification 91 + - **Remove unused code**: Dead CSS, unused components, orphaned files 92 + - **Flatten component trees**: Reduce nesting depth 93 + - **Consolidate styles**: Merge similar styles, use utilities consistently 94 + - **Reduce variants**: Does that component need 12 variations, or can 3 cover 90% of cases? 95 + 96 + **NEVER**: 97 + - Remove necessary functionality (simplicity equals feature-less) 98 + - Sacrifice accessibility for simplicity (clear labels and ARIA still required) 99 + - Make things so simple they are unclear (mystery equals minimalism) 100 + - Remove information users need to make decisions 101 + - Eliminate hierarchy completely (some things should stand out) 102 + - Oversimplify complex domains (match complexity to actual task complexity) 103 + 104 + ## Verify Simplification 105 + 106 + Ensure simplification improves usability. 107 + 108 + - **Faster task completion**: Can users accomplish goals more quickly? 109 + - **Reduced cognitive load**: Is it easier to understand what to do? 110 + - **Still complete**: Are all necessary features still accessible? 111 + - **Clearer hierarchy**: Is it obvious what matters most? 112 + - **Better performance**: Does simpler design load faster? 113 + 114 + ## Document Removed Complexity 115 + 116 + If you removed features or options: 117 + - Document why they were removed 118 + - Consider if they need alternative access points 119 + - Note any user feedback to monitor 120 + 121 + Remember: You have great taste and judgment. Simplification is an act of confidence: knowing what to keep and courage to remove the rest. As Antoine de Saint-Exupery said: "Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away."
+94
skills/extract/SKILL.md
··· 1 + --- 2 + name: extract 3 + description: "Extract and consolidate reusable components, design tokens, and patterns into your design system. Identifies opportunities for systematic reuse and enriches your component library. Use when the user asks to create components, refactor repeated UI patterns, build a design system, or extract tokens." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Identify reusable patterns, components, and design tokens, then extract and consolidate them into the design system for systematic reuse. 9 + 10 + ## Discover 11 + 12 + Analyze the target area to identify extraction opportunities. 13 + 14 + ### 1. Find the design system 15 + Locate your design system, component library, or shared UI directory (grep for "design system", "ui", "components", etc.). Understand its structure: 16 + - Component organization and naming conventions 17 + - Design token structure (if any) 18 + - Documentation patterns 19 + - Import and export conventions 20 + 21 + **CRITICAL**: If no design system exists, ask before creating one. Understand the preferred location and structure first. 22 + 23 + ### 2. Identify patterns 24 + Look for: 25 + - **Repeated components**: Similar UI patterns used multiple times (buttons, cards, inputs, etc.) 26 + - **Hard-coded values**: Colors, spacing, typography, shadows that should be tokens 27 + - **Inconsistent variations**: Multiple implementations of the same concept (3 different button styles) 28 + - **Reusable patterns**: Layout patterns, composition patterns, interaction patterns worth systematizing 29 + 30 + ### 3. Assess value 31 + Not everything should be extracted. Consider: 32 + - Is this used 3+ times, or likely to be reused? 33 + - Would systematizing this improve consistency? 34 + - Is this a general pattern or context-specific? 35 + - What is the maintenance cost vs benefit? 36 + 37 + ## Plan Extraction 38 + 39 + Create a systematic extraction plan. 40 + 41 + - **Components to extract**: Which UI elements become reusable components? 42 + - **Tokens to create**: Which hard-coded values become design tokens? 43 + - **Variants to support**: What variations does each component need? 44 + - **Naming conventions**: Component names, token names, prop names that match existing patterns 45 + - **Migration path**: How to refactor existing uses to consume the new shared versions 46 + 47 + **IMPORTANT**: Design systems grow incrementally. Extract what is clearly reusable now, not everything that might someday be reusable. 48 + 49 + ## Extract and Enrich 50 + 51 + Build improved, reusable versions. 52 + 53 + - **Components**: Create well-designed components with: 54 + - Clear props API with sensible defaults 55 + - Proper variants for different use cases 56 + - Accessibility built in (ARIA, keyboard navigation, focus management) 57 + - Documentation and usage examples 58 + 59 + - **Design tokens**: Create tokens with: 60 + - Clear naming (primitive vs semantic) 61 + - Proper hierarchy and organization 62 + - Documentation of when to use each token 63 + 64 + - **Patterns**: Document patterns with: 65 + - When to use this pattern 66 + - Code examples 67 + - Variations and combinations 68 + 69 + **NEVER**: 70 + - Extract one-off, context-specific implementations without generalization 71 + - Create components so generic they are useless 72 + - Extract without considering existing design system conventions 73 + - Skip proper TypeScript types or prop documentation 74 + - Create tokens for every single value (tokens should have semantic meaning) 75 + 76 + ## Migrate 77 + 78 + Replace existing uses with the new shared versions. 79 + 80 + - **Find all instances**: Search for the patterns you have extracted 81 + - **Replace systematically**: Update each use to consume the shared version 82 + - **Test thoroughly**: Ensure visual and functional parity 83 + - **Delete dead code**: Remove the old implementations 84 + 85 + ## Document 86 + 87 + Update design system documentation. 88 + 89 + - Add new components to the component library 90 + - Document token usage and values 91 + - Add examples and guidelines 92 + - Update any Storybook or component catalog 93 + 94 + Remember: A good design system is a living system. Extract patterns as they emerge, enrich them thoughtfully, and maintain them consistently.
+147
skills/frontend-design/SKILL.md
··· 1 + --- 2 + name: frontend-design 3 + description: "Create distinctive, production-grade frontend interfaces with high design quality. Generates creative, polished code that avoids generic AI aesthetics. Use when the user asks to build web components, pages, artifacts, posters, or applications, or when any design skill requires project context." 4 + license: Apache 2.0. Based on Anthropic's frontend-design skill. See NOTICE.md for attribution. 5 + --- 6 + 7 + This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. 8 + 9 + ## Context Gathering Protocol 10 + 11 + Design skills produce generic output without project context. You MUST have confirmed design context before doing any design work. 12 + 13 + **Required context** — every design skill needs at minimum: 14 + - **Target audience**: Who uses this product and in what context? 15 + - **Use cases**: What jobs are they trying to get done? 16 + - **Brand personality or tone**: How should the interface feel? 17 + 18 + Individual skills may require additional context. Check the skill preparation section for specifics. 19 + 20 + **CRITICAL**: You cannot infer this context by reading the codebase. Code tells you what was built, not who it is for or what it should feel like. Only the creator can provide this context. 21 + 22 + **Gathering order:** 23 + 1. **Check current instructions (instant)**: If your loaded instructions already contains a Design Context section, proceed immediately. 24 + 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. 25 + 3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run {{command_prefix}}teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. 26 + 27 + --- 28 + 29 + ## Design Direction 30 + 31 + Commit to a BOLD aesthetic direction: 32 + - **Purpose**: What problem does this interface solve? Who uses it? 33 + - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic or natural, luxury or refined, playful or toy-like, editorial or magazine, brutalist or raw, art deco or geometric, soft or pastel, industrial or utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. 34 + - **Constraints**: Technical requirements (framework, performance, accessibility). 35 + - **Differentiation**: What makes this UNFORGETTABLE? What is the one thing someone will remember? 36 + 37 + **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work. The key is intentionality, not intensity. 38 + 39 + Then implement working code that is: 40 + - Production-grade and functional 41 + - Visually striking and memorable 42 + - Cohesive with a clear aesthetic point of view 43 + - Meticulously refined in every detail 44 + 45 + ## Frontend Aesthetics Guidelines 46 + 47 + ### Typography 48 + > *Consult [typography reference](reference/typography.md) for scales, pairing, and loading strategies.* 49 + 50 + Choose fonts that are beautiful, unique, and interesting. Pair a distinctive display font with a refined body font. 51 + 52 + **DO**: Use a modular type scale with fluid sizing (clamp) 53 + **DO**: Vary font weights and sizes to create clear visual hierarchy 54 + **DON'T**: Use overused fonts: Inter, Roboto, Arial, Open Sans, system defaults 55 + **DON'T**: Use monospace typography as lazy shorthand for "technical or developer" vibes 56 + **DON'T**: Put large icons with rounded corners above every heading. They rarely add value and make sites look templated 57 + 58 + ### Color and Theme 59 + > *Consult [color reference](reference/color-and-contrast.md) for OKLCH, palettes, and dark mode.* 60 + 61 + Commit to a cohesive palette. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. 62 + 63 + **DO**: Use modern CSS color functions (oklch, color-mix, light-dark) for perceptually uniform, maintainable palettes 64 + **DO**: Tint your neutrals toward your brand hue. Even a subtle hint creates subconscious cohesion 65 + **DON'T**: Use gray text on colored backgrounds. It looks washed out. Use a shade of the background color instead. 66 + **DON'T**: Use pure black (#000) or pure white (#fff). Always tint. Pure black or white never appears in nature. 67 + **DON'T**: Use the AI color palette: cyan on dark, purple to blue gradients, neon accents on dark backgrounds 68 + **DON'T**: Use gradient text for "impact". Especially on metrics or headings. It is decorative rather than meaningful. 69 + **DON'T**: Default to dark mode with glowing accents. It looks "cool" without requiring actual design decisions. 70 + 71 + ### Layout and Space 72 + > *Consult [spatial reference](reference/spatial-design.md) for grids, rhythm, and container queries.* 73 + 74 + Create visual rhythm through varied spacing. Not the same padding everywhere. Embrace asymmetry and unexpected compositions. Break the grid intentionally for emphasis. 75 + 76 + **DO**: Create visual rhythm through varied spacing: tight groupings, generous separations 77 + **DO**: Use fluid spacing with clamp() that breathes on larger screens 78 + **DO**: Use asymmetry and unexpected compositions. Break the grid intentionally for emphasis. 79 + **DON'T**: Wrap everything in cards. Not everything needs a container. 80 + **DON'T**: Nest cards inside cards. Visual noise. Flatten the hierarchy. 81 + **DON'T**: Use identical card grids. Same-sized cards with icon, heading, text, repeated endlessly. 82 + **DON'T**: Use the hero metric layout template. Big number, small label, supporting stats, gradient accent. 83 + **DON'T**: Center everything. Left-aligned text with asymmetric layouts feels more designed. 84 + **DON'T**: Use the same spacing everywhere. Without rhythm, layouts feel monotonous. 85 + 86 + ### Visual Details 87 + **DO**: Use intentional, purposeful decorative elements that reinforce brand. 88 + **DON'T**: Use glassmorphism everywhere. Blur effects, glass cards, glow borders used decoratively rather than purposefully. 89 + **DON'T**: Use rounded elements with thick colored border on one side. A lazy accent that almost never looks intentional. 90 + **DON'T**: Use sparklines as decoration. Tiny charts that look sophisticated but convey nothing meaningful. 91 + **DON'T**: Use rounded rectangles with generic drop shadows. Safe, forgettable, could be any AI output. 92 + **DON'T**: Use modals unless there is truly no better alternative. Modals are lazy. 93 + 94 + ### Motion 95 + > *Consult [motion reference](reference/motion-design.md) for timing, easing, and reduced motion.* 96 + 97 + Focus on high-impact moments. One well-orchestrated page load with staggered reveals creates more delight than scattered micro-interactions. 98 + 99 + **DO**: Use motion to convey state changes: entrances, exits, feedback 100 + **DO**: Use exponential easing (ease-out-quart, quint, or expo) for natural deceleration 101 + **DO**: For height animations, use grid-template-rows transitions instead of animating height directly. 102 + **DON'T**: Animate layout properties (width, height, padding, margin). Use transform and opacity only. 103 + **DON'T**: Use bounce or elastic easing. They feel dated and tacky. Real objects decelerate smoothly. 104 + 105 + ### Interaction 106 + > *Consult [interaction reference](reference/interaction-design.md) for forms, focus, and loading patterns.* 107 + 108 + Make interactions feel fast. Use optimistic UI: update immediately, sync later. 109 + 110 + **DO**: Use progressive disclosure. Start simple, reveal sophistication through interaction (basic options first, advanced behind expandable sections; hover states that reveal secondary actions). 111 + **DO**: Design empty states that teach the interface, not just say "nothing here" 112 + **DO**: Make every interactive surface feel intentional and responsive. 113 + **DON'T**: Repeat the same information. Redundant headers, intros that restate the heading. 114 + **DON'T**: Make every button primary. Use ghost buttons, text links, secondary styles. Hierarchy matters. 115 + 116 + ### Responsive 117 + > *Consult [responsive reference](reference/responsive-design.md) for mobile-first, fluid design, and container queries.* 118 + 119 + **DO**: Use container queries (@container) for component-level responsiveness 120 + **DO**: Adapt the interface for different contexts. Do not just shrink it. 121 + **DON'T**: Hide critical functionality on mobile. Adapt the interface, do not amputate it. 122 + 123 + ### UX Writing 124 + > *Consult [ux-writing reference](reference/ux-writing.md) for labels, errors, and empty states.* 125 + 126 + **DO**: Make every word earn its place. 127 + **DON'T**: Repeat information users can already see. 128 + 129 + --- 130 + 131 + ## The AI Slop Test 132 + 133 + **Critical quality check**: If you showed this interface to someone and said "AI made this," would they believe you immediately? If yes, that is the problem. 134 + 135 + A distinctive interface should make someone ask "how was this made?" not "which AI made this?" 136 + 137 + Review the DO NOT guidelines above. They are the fingerprints of AI-generated work from 2024-2025. 138 + 139 + --- 140 + 141 + ## Implementation Principles 142 + 143 + Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. 144 + 145 + Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices across generations. 146 + 147 + Remember: {{model}} is capable of extraordinary creative work. Do not hold back. Show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
+132
skills/frontend-design/reference/color-and-contrast.md
··· 1 + # Color and Contrast 2 + 3 + ## Color Spaces: Use OKLCH 4 + 5 + **Stop using HSL.** Use OKLCH (or LCH) instead. It is perceptually uniform, meaning equal steps in lightness look equal. Unlike HSL where 50% lightness in yellow looks bright while 50% in blue looks dark. 6 + 7 + ```css 8 + /* OKLCH: lightness (0-100%), chroma (0-0.4+), hue (0-360) */ 9 + --color-primary: oklch(60% 0.15 250); /* Blue */ 10 + --color-primary-light: oklch(85% 0.08 250); /* Same hue, lighter */ 11 + --color-primary-dark: oklch(35% 0.12 250); /* Same hue, darker */ 12 + ``` 13 + 14 + **Key insight**: As you move toward white or black, reduce chroma (saturation). High chroma at extreme lightness looks garish. A light blue at 85% lightness needs approximately 0.08 chroma, not the 0.15 of your base color. 15 + 16 + ## Building Functional Palettes 17 + 18 + ### The Tinted Neutral Trap 19 + 20 + **Pure gray is dead.** Add a subtle hint of your brand hue to all neutrals: 21 + 22 + ```css 23 + /* Dead grays */ 24 + --gray-100: oklch(95% 0 0); /* No personality */ 25 + --gray-900: oklch(15% 0 0); 26 + 27 + /* Warm-tinted grays (add brand warmth) */ 28 + --gray-100: oklch(95% 0.01 60); /* Hint of warmth */ 29 + --gray-900: oklch(15% 0.01 60); 30 + 31 + /* Cool-tinted grays (tech, professional) */ 32 + --gray-100: oklch(95% 0.01 250); /* Hint of blue */ 33 + --gray-900: oklch(15% 0.01 250); 34 + ``` 35 + 36 + The chroma is tiny (0.01) but perceptible. It creates subconscious cohesion between your brand color and your UI. 37 + 38 + ### Palette Structure 39 + 40 + A complete system needs: 41 + 42 + | Role | Purpose | Example | 43 + |------|---------|---------| 44 + | **Primary** | Brand, CTAs, key actions | 1 color, 3-5 shades | 45 + | **Neutral** | Text, backgrounds, borders | 9-11 shade scale | 46 + | **Semantic** | Success, error, warning, info | 4 colors, 2-3 shades each | 47 + | **Surface** | Cards, modals, overlays | 2-3 elevation levels | 48 + 49 + **Skip secondary or tertiary unless you need them.** Most apps work fine with one accent color. Adding more creates decision fatigue and visual noise. 50 + 51 + ### The 60-30-10 Rule (Applied Correctly) 52 + 53 + This rule is about **visual weight**, not pixel count: 54 + 55 + - **60%**: Neutral backgrounds, white space, base surfaces 56 + - **30%**: Secondary colors. Text, borders, inactive states. 57 + - **10%**: Accent. CTAs, highlights, focus states. 58 + 59 + The common mistake: using the accent color everywhere because it is "the brand color." Accent colors work because they are rare. Overuse kills their power. 60 + 61 + ## Contrast and Accessibility 62 + 63 + ### WCAG Requirements 64 + 65 + | Content Type | AA Minimum | AAA Target | 66 + |--------------|------------|------------| 67 + | Body text | 4.5:1 | 7:1 | 68 + | Large text (18px+ or 14px bold) | 3:1 | 4.5:1 | 69 + | UI components, icons | 3:1 | 4.5:1 | 70 + | Non-essential decorations | None | None | 71 + 72 + **The gotcha**: Placeholder text still needs 4.5:1. That light gray placeholder you see everywhere? Usually fails WCAG. 73 + 74 + ### Dangerous Color Combinations 75 + 76 + These commonly fail contrast or cause readability issues: 77 + 78 + - Light gray text on white (the number 1 accessibility fail) 79 + - **Gray text on any colored background.** Gray looks washed out and dead on color. Use a darker shade of the background color, or transparency. 80 + - Red text on green background (or vice versa). 8% of men cannot distinguish these. 81 + - Blue text on red background (vibrates visually) 82 + - Yellow text on white (almost always fails) 83 + - Thin light text on images (unpredictable contrast) 84 + 85 + ### Never Use Pure Gray or Pure Black 86 + 87 + Pure gray (oklch(50% 0 0)) and pure black (#000) do not exist in nature. Real shadows and surfaces always have a color cast. Even a chroma of 0.005-0.01 is enough to feel natural without being obviously tinted. (See tinted neutrals example above.) 88 + 89 + ### Testing 90 + 91 + Do not trust your eyes. Use tools: 92 + 93 + - WebAIM Contrast Checker 94 + - Browser DevTools. Rendering. Emulate vision deficiencies. 95 + - Polypane for real-time testing. 96 + 97 + ## Theming: Light and Dark Mode 98 + 99 + ### Dark Mode Is Not Inverted Light Mode 100 + 101 + You cannot just swap colors. Dark mode requires different design decisions: 102 + 103 + | Light Mode | Dark Mode | 104 + |------------|-----------| 105 + | Shadows for depth | Lighter surfaces for depth (no shadows) | 106 + | Dark text on light | Light text on dark (reduce font weight) | 107 + | Vibrant accents | Desaturate accents slightly | 108 + | White backgrounds | Never pure black. Use dark gray (oklch 12-18%) | 109 + 110 + ```css 111 + /* Dark mode depth via surface color, not shadow */ 112 + :root[data-theme="dark"] { 113 + --surface-1: oklch(15% 0.01 250); 114 + --surface-2: oklch(20% 0.01 250); /* "Higher" = lighter */ 115 + --surface-3: oklch(25% 0.01 250); 116 + 117 + /* Reduce text weight slightly */ 118 + --body-weight: 350; /* Instead of 400 */ 119 + } 120 + ``` 121 + 122 + ### Token Hierarchy 123 + 124 + Use two layers: primitive tokens (--blue-500) and semantic tokens (--color-primary: var(--blue-500)). For dark mode, only redefine the semantic layer. Primitives stay the same. 125 + 126 + ## Alpha Is A Design Smell 127 + 128 + Heavy use of transparency (rgba, hsla) usually means an incomplete palette. Alpha creates unpredictable contrast, performance overhead, and inconsistency. Define explicit overlay colors for each context instead. Exception: focus rings and interactive states where see-through is needed. 129 + 130 + --- 131 + 132 + **Avoid**: Relying on color alone to convey information. Creating palettes without clear roles for each color. Using pure black (#000) for large areas. Skipping color blindness testing (8% of men affected).
+195
skills/frontend-design/reference/interaction-design.md
··· 1 + # Interaction Design 2 + 3 + ## The Eight Interactive States 4 + 5 + Every interactive element needs these states designed: 6 + 7 + | State | When | Visual Treatment | 8 + |-------|------|------------------| 9 + | **Default** | At rest | Base styling | 10 + | **Hover** | Pointer over (not touch) | Subtle lift, color shift | 11 + | **Focus** | Keyboard or programmatic focus | Visible ring (see below) | 12 + | **Active** | Being pressed | Pressed in, darker | 13 + | **Disabled** | Not interactive | Reduced opacity, no pointer | 14 + | **Loading** | Processing | Spinner, skeleton | 15 + | **Error** | Invalid state | Red border, icon, message | 16 + | **Success** | Completed | Green check, confirmation | 17 + 18 + **The common miss**: Designing hover without focus, or vice versa. They are different. Keyboard users never see hover states. 19 + 20 + ## Focus Rings: Do Them Right 21 + 22 + **Never outline: none without replacement.** It is an accessibility violation. Instead, use :focus-visible to show focus only for keyboard users: 23 + 24 + ```css 25 + /* Hide focus ring for mouse or touch */ 26 + button:focus { 27 + outline: none; 28 + } 29 + 30 + /* Show focus ring for keyboard */ 31 + button:focus-visible { 32 + outline: 2px solid var(--color-accent); 33 + outline-offset: 2px; 34 + } 35 + ``` 36 + 37 + **Focus ring design**: 38 + - High contrast (3:1 minimum against adjacent colors) 39 + - 2-3px thick 40 + - Offset from element (not inside it) 41 + - Consistent across all interactive elements 42 + 43 + ## Form Design: The Non-Obvious 44 + 45 + **Placeholders are not labels.** They disappear on input. Always use visible label elements. **Validate on blur**, not on every keystroke (exception: password strength). Place errors **below** fields with aria-describedby connecting them. 46 + 47 + ## Loading States 48 + 49 + **Optimistic updates**: Show success immediately, rollback on failure. Use for low-stakes actions (likes, follows), not payments or destructive actions. **Skeleton screens greater than spinners.** They preview content shape and feel faster than generic spinners. 50 + 51 + ## Modals: The Inert Approach 52 + 53 + Focus trapping in modals used to require complex JavaScript. Now use the inert attribute: 54 + 55 + ```html 56 + <!-- When modal is open --> 57 + <main inert> 58 + <!-- Content behind modal cannot be focused or clicked --> 59 + </main> 60 + <dialog open> 61 + <h2>Modal Title</h2> 62 + <!-- Focus stays inside modal --> 63 + </dialog> 64 + ``` 65 + 66 + Or use the native dialog element: 67 + 68 + ```javascript 69 + const dialog = document.querySelector('dialog'); 70 + dialog.showModal(); // Opens with focus trap, closes on Escape 71 + ``` 72 + 73 + ## The Popover API 74 + 75 + For tooltips, dropdowns, and non-modal overlays, use native popovers: 76 + 77 + ```html 78 + <button popovertarget="menu">Open menu</button> 79 + <div id="menu" popover> 80 + <button>Option 1</button> 81 + <button>Option 2</button> 82 + </div> 83 + ``` 84 + 85 + **Benefits**: Light-dismiss (click outside closes), proper stacking, no z-index wars, accessible by default. 86 + 87 + ## Dropdown and Overlay Positioning 88 + 89 + Dropdowns rendered with position: absolute inside a container that has overflow: hidden or overflow: auto will be clipped. This is the single most common dropdown bug in generated code. 90 + 91 + ### CSS Anchor Positioning 92 + 93 + The modern solution uses the CSS Anchor Positioning API to tether an overlay to its trigger without JavaScript: 94 + 95 + ```css 96 + .trigger { 97 + anchor-name: --menu-trigger; 98 + } 99 + 100 + .dropdown { 101 + position: fixed; 102 + position-anchor: --menu-trigger; 103 + position-area: block-end span-inline-end; 104 + margin-top: 4px; 105 + } 106 + 107 + /* Flip above if no room below */ 108 + @position-try --flip-above { 109 + position-area: block-start span-inline-end; 110 + margin-bottom: 4px; 111 + } 112 + ``` 113 + 114 + Because the dropdown uses position: fixed, it escapes any overflow clipping on ancestor elements. The @position-try block handles viewport edges automatically. **Browser support**: Chrome 125+, Edge 125+. Not yet in Firefox or Safari. Use a fallback for those browsers. 115 + 116 + ### Popover + Anchor Combo 117 + 118 + Combining the Popover API with anchor positioning gives you stacking, light-dismiss, accessibility, and correct positioning in one pattern: 119 + 120 + ```html 121 + <button popovertarget="menu" class="trigger">Open</button> 122 + <div id="menu" popover class="dropdown"> 123 + <button>Option 1</button> 124 + <button>Option 2</button> 125 + </div> 126 + ``` 127 + 128 + The popover attribute places the element in the **top layer**, which sits above all other content regardless of z-index or overflow. No portal needed. 129 + 130 + ### Portal / Teleport Pattern 131 + 132 + In component frameworks, render the dropdown at the document root and position it with JavaScript: 133 + 134 + - **React**: createPortal(dropdown, document.body) 135 + - **Vue**: Teleport to="body" 136 + - **Svelte**: Use a portal library or mount to document.body 137 + 138 + Calculate position from the trigger getBoundingClientRect(), then apply position: fixed with top and left values. Recalculate on scroll and resize. 139 + 140 + ### Fixed Positioning Fallback 141 + 142 + For browsers without anchor positioning support, position: fixed with manual coordinates avoids overflow clipping: 143 + 144 + ```css 145 + .dropdown { 146 + position: fixed; 147 + /* top/left set via JS from trigger getBoundingClientRect() */ 148 + } 149 + ``` 150 + 151 + Check viewport boundaries before rendering. If the dropdown would overflow the bottom edge, flip it above the trigger. If it would overflow the right edge, align it to the trigger right side instead. 152 + 153 + ### Anti-Patterns 154 + 155 + - **position: absolute inside overflow: hidden.** The dropdown will be clipped. Use position: fixed or the top layer instead. 156 + - **Arbitrary z-index values** like z-index: 9999. Use a semantic z-index scale: dropdown (100), sticky (200), modal-backdrop (300), modal (400), toast (500), tooltip (600). 157 + - **Rendering dropdown markup inline** without an escape hatch from the parent stacking context. Either use popover (top layer), a portal, or position: fixed. 158 + 159 + ## Destructive Actions: Undo Greater Than Confirm 160 + 161 + **Undo is better than confirmation dialogs.** Users click through confirmations mindlessly. Remove from UI immediately, show undo toast, actually delete after toast expires. Use confirmation only for truly irreversible actions (account deletion), high-cost actions, or batch operations. 162 + 163 + ## Keyboard Navigation Patterns 164 + 165 + ### Roving Tabindex 166 + 167 + For component groups (tabs, menu items, radio groups), one item is tabbable. Arrow keys move within: 168 + 169 + ```html 170 + <div role="tablist"> 171 + <button role="tab" tabindex="0">Tab 1</button> 172 + <button role="tab" tabindex="-1">Tab 2</button> 173 + <button role="tab" tabindex="-1">Tab 3</button> 174 + </div> 175 + ``` 176 + 177 + Arrow keys move tabindex="0" between items. Tab moves to the next component entirely. 178 + 179 + ### Skip Links 180 + 181 + Provide skip links (a href="#main-content" Skip to main content) for keyboard users to jump past navigation. Hide off-screen, show on focus. 182 + 183 + ## Gesture Discoverability 184 + 185 + Swipe-to-delete and similar gestures are invisible. Hint at their existence: 186 + 187 + - **Partially reveal**: Show delete button peeking from edge 188 + - **Onboarding**: Coach marks on first use 189 + - **Alternative**: Always provide a visible fallback (menu with "Delete") 190 + 191 + Do not rely on gestures as the only way to perform actions. 192 + 193 + --- 194 + 195 + **Avoid**: Removing focus indicators without alternatives. Using placeholder text as labels. Touch targets less than 44x44px. Generic error messages. Custom controls without ARIA or keyboard support.
+99
skills/frontend-design/reference/motion-design.md
··· 1 + # Motion Design 2 + 3 + ## Duration: The 100/300/500 Rule 4 + 5 + Timing matters more than easing. These durations feel right for most UI: 6 + 7 + | Duration | Use Case | Examples | 8 + |----------|----------|----------| 9 + | **100-150ms** | Instant feedback | Button press, toggle, color change | 10 + | **200-300ms** | State changes | Menu open, tooltip, hover states | 11 + | **300-500ms** | Layout changes | Accordion, modal, drawer | 12 + | **500-800ms** | Entrance animations | Page load, hero reveals | 13 + 14 + **Exit animations are faster than entrances.** Use approximately 75% of enter duration. 15 + 16 + ## Easing: Pick the Right Curve 17 + 18 + **Do not use ease.** It is a compromise that is rarely optimal. Instead: 19 + 20 + | Curve | Use For | CSS | 21 + |-------|---------|-----| 22 + | **ease-out** | Elements entering | cubic-bezier(0.16, 1, 0.3, 1) | 23 + | **ease-in** | Elements leaving | cubic-bezier(0.7, 0, 0.84, 0) | 24 + | **ease-in-out** | State toggles (there and back) | cubic-bezier(0.65, 0, 0.35, 1) | 25 + 26 + **For micro-interactions, use exponential curves.** They feel natural because they mimic real physics (friction, deceleration): 27 + 28 + ```css 29 + /* Quart out - smooth, refined (recommended default) */ 30 + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); 31 + 32 + /* Quint out - slightly more dramatic */ 33 + --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); 34 + 35 + /* Expo out - snappy, confident */ 36 + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); 37 + ``` 38 + 39 + **Avoid bounce and elastic curves.** They were trendy in 2015 but now feel tacky and amateurish. Real objects do not bounce when they stop. They decelerate smoothly. Overshoot effects draw attention to the animation itself rather than the content. 40 + 41 + ## The Only Two Properties You Should Animate 42 + 43 + **transform** and **opacity** only. Everything else causes layout recalculations. For height animations (accordions), use grid-template-rows: 0fr to 1fr instead of animating height directly. 44 + 45 + ## Staggered Animations 46 + 47 + Use CSS custom properties for cleaner stagger: animation-delay: calc(var(--i, 0) times 50ms) with style="--i: 0" on each item. **Cap total stagger time.** 10 items at 50ms equals 500ms total. For many items, reduce per-item delay or cap staggered count. 48 + 49 + ## Reduced Motion 50 + 51 + This is not optional. Vestibular disorders affect approximately 35% of adults over 40. 52 + 53 + ```css 54 + /* Define animations normally */ 55 + .card { 56 + animation: slide-up 500ms ease-out; 57 + } 58 + 59 + /* Provide alternative for reduced motion */ 60 + @media (prefers-reduced-motion: reduce) { 61 + .card { 62 + animation: fade-in 200ms ease-out; /* Crossfade instead of motion */ 63 + } 64 + } 65 + 66 + /* Or disable entirely */ 67 + @media (prefers-reduced-motion: reduce) { 68 + *, *::before, *::after { 69 + animation-duration: 0.01ms !important; 70 + transition-duration: 0.01ms !important; 71 + } 72 + } 73 + ``` 74 + 75 + **What to preserve**: Functional animations like progress bars, loading spinners (slowed down), and focus indicators should still work. Just without spatial movement. 76 + 77 + ## Perceived Performance 78 + 79 + **Nobody cares how fast your site is. Just how fast it feels.** Perception can be as effective as actual performance. 80 + 81 + **The 80ms threshold**: Our brains buffer sensory input for approximately 80ms to synchronize perception. Anything under 80ms feels instant and simultaneous. This is your target for micro-interactions. 82 + 83 + **Active vs passive time**: Passive waiting (staring at a spinner) feels longer than active engagement. Strategies to shift the balance: 84 + 85 + - **Preemptive start**: Begin transitions immediately while loading (iOS app zoom, skeleton UI). Users perceive work happening. 86 + - **Early completion**: Show content progressively. Do not wait for everything. Video buffering, progressive images, streaming HTML. 87 + - **Optimistic UI**: Update the interface immediately, handle failures gracefully. Instagram likes work offline. The UI updates instantly, syncs later. Use for low-stakes actions. Avoid for payments or destructive operations. 88 + 89 + **Easing affects perceived duration**: Ease-in (accelerating toward completion) makes tasks feel shorter because the peak-end effect weights final moments heavily. Ease-out feels satisfying for entrances, but ease-in toward a task end compresses perceived time. 90 + 91 + **Caution**: Too-fast responses can decrease perceived value. Users may distrust instant results for complex operations (search, analysis). Sometimes a brief delay signals "real work" is happening. 92 + 93 + ## Performance 94 + 95 + Do not use will-change preemptively. Only when animation is imminent (hover, .animating). For scroll-triggered animations, use Intersection Observer instead of scroll events. Unobserve after animating once. Create motion tokens for consistency (durations, easings, common transitions). 96 + 97 + --- 98 + 99 + **Avoid**: Animating everything (animation fatigue is real). Using more than 500ms for UI feedback. Ignoring prefers-reduced-motion. Using animation to hide slow loading.
+114
skills/frontend-design/reference/responsive-design.md
··· 1 + # Responsive Design 2 + 3 + ## Mobile-First: Write It Right 4 + 5 + Start with base styles for mobile, use min-width queries to layer complexity. Desktop-first (max-width) means mobile loads unnecessary styles first. 6 + 7 + ## Breakpoints: Content-Driven 8 + 9 + Do not chase device sizes. Let content tell you where to break. Start narrow, stretch until design breaks, add breakpoint there. Three breakpoints usually suffice (640, 768, 1024px). Use clamp() for fluid values without breakpoints. 10 + 11 + ## Detect Input Method, Not Just Screen Size 12 + 13 + **Screen size does not tell you input method.** A laptop with touchscreen, a tablet with keyboard. Use pointer and hover queries: 14 + 15 + ```css 16 + /* Fine pointer (mouse, trackpad) */ 17 + @media (pointer: fine) { 18 + .button { padding: 8px 16px; } 19 + } 20 + 21 + /* Coarse pointer (touch, stylus) */ 22 + @media (pointer: coarse) { 23 + .button { padding: 12px 20px; } /* Larger touch target */ 24 + } 25 + 26 + /* Device supports hover */ 27 + @media (hover: hover) { 28 + .card:hover { transform: translateY(-2px); } 29 + } 30 + 31 + /* Device does not support hover (touch) */ 32 + @media (hover: none) { 33 + .card { /* No hover state - use active instead */ } 34 + } 35 + ``` 36 + 37 + **Critical**: Do not rely on hover for functionality. Touch users cannot hover. 38 + 39 + ## Safe Areas: Handle the Notch 40 + 41 + Modern phones have notches, rounded corners, and home indicators. Use env(): 42 + 43 + ```css 44 + body { 45 + padding-top: env(safe-area-inset-top); 46 + padding-bottom: env(safe-area-inset-bottom); 47 + padding-left: env(safe-area-inset-left); 48 + padding-right: env(safe-area-inset-right); 49 + } 50 + 51 + /* With fallback */ 52 + .footer { 53 + padding-bottom: max(1rem, env(safe-area-inset-bottom)); 54 + } 55 + ``` 56 + 57 + **Enable viewport-fit** in your meta tag: 58 + ```html 59 + <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> 60 + ``` 61 + 62 + ## Responsive Images: Get It Right 63 + 64 + ### srcset with Width Descriptors 65 + 66 + ```html 67 + <img 68 + src="hero-800.jpg" 69 + srcset=" 70 + hero-400.jpg 400w, 71 + hero-800.jpg 800w, 72 + hero-1200.jpg 1200w 73 + " 74 + sizes="(max-width: 768px) 100vw, 50vw" 75 + alt="Hero image" 76 + > 77 + ``` 78 + 79 + **How it works**: 80 + - srcset lists available images with their actual widths (w descriptors) 81 + - sizes tells the browser how wide the image will display 82 + - Browser picks the best file based on viewport width AND device pixel ratio 83 + 84 + ### Picture Element for Art Direction 85 + 86 + When you need different crops or compositions (not just resolutions): 87 + 88 + ```html 89 + <picture> 90 + <source media="(min-width: 768px)" srcset="wide.jpg"> 91 + <source media="(max-width: 767px)" srcset="tall.jpg"> 92 + <img src="fallback.jpg" alt="..."> 93 + </picture> 94 + ``` 95 + 96 + ## Layout Adaptation Patterns 97 + 98 + **Navigation**: Three stages: hamburger and drawer on mobile, horizontal compact on tablet, full with labels on desktop. **Tables**: Transform to cards on mobile using display: block and data-label attributes. **Progressive disclosure**: Use details or summary for content that can collapse on mobile. 99 + 100 + ## Testing: Do Not Trust DevTools Alone 101 + 102 + DevTools device emulation is useful for layout but misses: 103 + 104 + - Actual touch interactions 105 + - Real CPU/memory constraints 106 + - Network latency patterns 107 + - Font rendering differences 108 + - Browser chrome or keyboard appearances 109 + 110 + **Test on at least**: One real iPhone, one real Android, a tablet if relevant. Cheap Android phones reveal performance issues you will never see on simulators. 111 + 112 + --- 113 + 114 + **Avoid**: Desktop-first design. Device detection instead of feature detection. Separate mobile or desktop codebases. Ignoring tablet and landscape. Assuming all mobile devices are powerful.
+100
skills/frontend-design/reference/spatial-design.md
··· 1 + # Spatial Design 2 + 3 + ## Spacing Systems 4 + 5 + ### Use 4pt Base, Not 8pt 6 + 7 + 8pt systems are too coarse. You will frequently need 12px (between 8 and 16). Use 4pt for granularity: 4, 8, 12, 16, 24, 32, 48, 64, 96px. 8 + 9 + ### Name Tokens Semantically 10 + 11 + Name by relationship (--space-sm, --space-lg), not value (--spacing-8). Use gap instead of margins for sibling spacing. It eliminates margin collapse and cleanup hacks. 12 + 13 + ## Grid Systems 14 + 15 + ### The Self-Adjusting Grid 16 + 17 + Use repeat(auto-fit, minmax(280px, 1fr)) for responsive grids without breakpoints. Columns are at least 280px, as many as fit per row, leftovers stretch. For complex layouts, use named grid areas (grid-template-areas) and redefine them at breakpoints. 18 + 19 + ## Visual Hierarchy 20 + 21 + ### The Squint Test 22 + 23 + Blur your eyes (or screenshot and blur). Can you still identify: 24 + - The most important element? 25 + - The second most important? 26 + - Clear groupings? 27 + 28 + If everything looks the same weight blurred, you have a hierarchy problem. 29 + 30 + ### Hierarchy Through Multiple Dimensions 31 + 32 + Do not rely on size alone. Combine: 33 + 34 + | Tool | Strong Hierarchy | Weak Hierarchy | 35 + |------|------------------|----------------| 36 + | **Size** | 3:1 ratio or more | less than 2:1 ratio | 37 + | **Weight** | Bold vs Regular | Medium vs Regular | 38 + | **Color** | High contrast | Similar tones | 39 + | **Position** | Top or left (primary) | Bottom or right | 40 + | **Space** | Surrounded by white space | Crowded | 41 + 42 + **The best hierarchy uses 2-3 dimensions at once**: A heading that is larger, bolder, AND has more space above it. 43 + 44 + ### Cards Are Not Required 45 + 46 + Cards are overused. Spacing and alignment create visual grouping naturally. Use cards only when content is truly distinct and actionable, items need visual comparison in a grid, or content needs clear interaction boundaries. **Never nest cards inside cards.** Use spacing, typography, and subtle dividers for hierarchy within a card. 47 + 48 + ## Container Queries 49 + 50 + Viewport queries are for page layouts. **Container queries are for components**: 51 + 52 + ```css 53 + .card-container { 54 + container-type: inline-size; 55 + } 56 + 57 + .card { 58 + display: grid; 59 + gap: var(--space-md); 60 + } 61 + 62 + /* Card layout changes based on its container, not viewport */ 63 + @container (min-width: 400px) { 64 + .card { 65 + grid-template-columns: 120px 1fr; 66 + } 67 + } 68 + ``` 69 + 70 + **Why this matters**: A card in a narrow sidebar stays compact, while the same card in a main content area expands. Automatically, without viewport hacks. 71 + 72 + ## Optical Adjustments 73 + 74 + Text at margin-left: 0 looks indented due to letterform whitespace. Use negative margin (-0.05em) to optically align. Geometrically centered icons often look off-center. Play icons need to shift right, arrows shift toward their direction. 75 + 76 + ### Touch Targets vs Visual Size 77 + 78 + Buttons can look small but need large touch targets (44px minimum). Use padding or pseudo-elements: 79 + 80 + ```css 81 + .icon-button { 82 + width: 24px; /* Visual size */ 83 + height: 24px; 84 + position: relative; 85 + } 86 + 87 + .icon-button::before { 88 + content: ''; 89 + position: absolute; 90 + inset: -10px; /* Expand tap target to 44px */ 91 + } 92 + ``` 93 + 94 + ## Depth and Elevation 95 + 96 + Create semantic z-index scales (dropdown, sticky, modal-backdrop, modal, toast, tooltip) instead of arbitrary numbers. For shadows, create a consistent elevation scale (sm, md, lg, xl). **Key insight**: Shadows should be subtle. If you can clearly see it, it is probably too strong. 97 + 98 + --- 99 + 100 + **Avoid**: Arbitrary spacing values outside your scale. Making all spacing equal (variety creates hierarchy). Creating hierarchy through size alone. Combine size, weight, color, and space.
+133
skills/frontend-design/reference/typography.md
··· 1 + # Typography 2 + 3 + ## Classic Typography Principles 4 + 5 + ### Vertical Rhythm 6 + 7 + Your line-height should be the base unit for ALL vertical spacing. If body text has line-height: 1.5 on 16px type (equals 24px), spacing values should be multiples of 24px. This creates subconscious harmony. Text and space share a mathematical foundation. 8 + 9 + ### Modular Scale and Hierarchy 10 + 11 + The common mistake: too many font sizes that are too close together (14px, 15px, 16px, 18px...). This creates muddy hierarchy. 12 + 13 + **Use fewer sizes with more contrast.** A 5-size system covers most needs: 14 + 15 + | Role | Typical Ratio | Use Case | 16 + |------|---------------|----------| 17 + | xs | 0.75rem | Captions, legal | 18 + | sm | 0.875rem | Secondary UI, metadata | 19 + | base | 1rem | Body text | 20 + | lg | 1.25-1.5rem | Subheadings, lead text | 21 + | xl+ | 2-4rem | Headlines, hero text | 22 + 23 + Popular ratios: 1.25 (major third), 1.333 (perfect fourth), 1.5 (perfect fifth). Pick one and commit. 24 + 25 + ### Readability and Measure 26 + 27 + Use ch units for character-based measure (max-width: 65ch). Line-height scales inversely with line length. Narrow columns need tighter leading, wide columns need more. 28 + 29 + **Non-obvious**: Increase line-height for light text on dark backgrounds. The perceived weight is lighter, so text needs more breathing room. Add 0.05-0.1 to your normal line-height. 30 + 31 + ## Font Selection and Pairing 32 + 33 + ### Choosing Distinctive Fonts 34 + 35 + **Avoid the invisible defaults**: Inter, Roboto, Open Sans, Lato, Montserrat. These are everywhere, making your design feel generic. They are fine for documentation or tools where personality is not the goal. But if you want distinctive design, look elsewhere. 36 + 37 + **Better Google Fonts alternatives**: 38 + - Instead of Inter: Instrument Sans, Plus Jakarta Sans, Outfit 39 + - Instead of Roboto: Onest, Figtree, Urbanist 40 + - Instead of Open Sans: Source Sans 3, Nunito Sans, DM Sans 41 + - For editorial or premium feel: Fraunces, Newsreader, Lora 42 + 43 + **System fonts are underrated**: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui looks native, loads instantly, and is highly readable. Consider this for apps where performance is greater than personality. 44 + 45 + ### Pairing Principles 46 + 47 + **The non-obvious truth**: You often do not need a second font. One well-chosen font family in multiple weights creates cleaner hierarchy than two competing typefaces. Only add a second font when you need genuine contrast (e.g., display headlines plus body serif). 48 + 49 + When pairing, contrast on multiple axes: 50 + - Serif plus Sans (structure contrast) 51 + - Geometric plus Humanist (personality contrast) 52 + - Condensed display plus Wide body (proportion contrast) 53 + 54 + **Never pair fonts that are similar but not identical** (e.g., two geometric sans-serifs). They create visual tension without clear hierarchy. 55 + 56 + ### Web Font Loading 57 + 58 + The layout shift problem: fonts load late, text reflows, and users see content jump. Here is the fix: 59 + 60 + ```css 61 + /* 1. Use font-display: swap for visibility */ 62 + @font-face { 63 + font-family: 'CustomFont'; 64 + src: url('font.woff2') format('woff2'); 65 + font-display: swap; 66 + } 67 + 68 + /* 2. Match fallback metrics to minimize shift */ 69 + @font-face { 70 + font-family: 'CustomFont-Fallback'; 71 + src: local('Arial'); 72 + size-adjust: 105%; /* Scale to match x-height */ 73 + ascent-override: 90%; /* Match ascender height */ 74 + descent-override: 20%; /* Match descender depth */ 75 + line-gap-override: 10%; /* Match line spacing */ 76 + } 77 + 78 + body { 79 + font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif; 80 + } 81 + ``` 82 + 83 + Tools like Fontaine calculate these overrides automatically. 84 + 85 + ## Modern Web Typography 86 + 87 + ### Fluid Type 88 + 89 + Fluid typography via clamp(min, preferred, max) scales text smoothly with the viewport. The middle value (e.g., 5vw plus 1rem) controls scaling rate. Higher vw equals faster scaling. Add a rem offset so it does not collapse to 0 on small screens. 90 + 91 + **Use fluid type for**: Headings and display text on marketing or content pages where text dominates the layout and needs to breathe across viewport sizes. 92 + 93 + **Use fixed rem scales for**: App UIs, dashboards, and data-dense interfaces. No major app design system (Material, Polaris, Primer, Carbon) uses fluid type in product UI. Fixed scales with optional breakpoint adjustments give the spatial predictability that container-based layouts need. Body text should also be fixed even on marketing pages, since the size difference across viewports is too small to warrant it. 94 + 95 + ### OpenType Features 96 + 97 + Most developers do not know these exist. Use them for polish: 98 + 99 + ```css 100 + /* Tabular numbers for data alignment */ 101 + .data-table { font-variant-numeric: tabular-nums; } 102 + 103 + /* Proper fractions */ 104 + .recipe-amount { font-variant-numeric: diagonal-fractions; } 105 + 106 + /* Small caps for abbreviations */ 107 + abbr { font-variant-caps: all-small-caps; } 108 + 109 + /* Disable ligatures in code */ 110 + code { font-variant-ligatures: none; } 111 + 112 + /* Enable kerning (usually on by default, but be explicit) */ 113 + body { font-kerning: normal; } 114 + ``` 115 + 116 + Check what features your font supports at Wakamai Fondue. 117 + 118 + ## Typography System Architecture 119 + 120 + Name tokens semantically (--text-body, --text-heading), not by value (--font-size-16). Include font stacks, size scale, weights, line-heights, and letter-spacing in your token system. 121 + 122 + ## Accessibility Considerations 123 + 124 + Beyond contrast ratios (which are well-documented), consider: 125 + 126 + - **Never disable zoom**: user-scalable=no breaks accessibility. If your layout breaks at 200% zoom, fix the layout. 127 + - **Use rem or em for font sizes**: This respects user browser settings. Never px for body text. 128 + - **Minimum 16px body text**: Smaller than this strains eyes and fails WCAG on mobile. 129 + - **Adequate touch targets**: Text links need padding or line-height that creates 44px+ tap targets. 130 + 131 + --- 132 + 133 + **Avoid**: More than 2-3 font families per project. Skipping fallback font definitions. Ignoring font loading performance (FOUT or FOIT). Using decorative fonts for body text.
+107
skills/frontend-design/reference/ux-writing.md
··· 1 + # UX Writing 2 + 3 + ## The Button Label Problem 4 + 5 + **Never use "OK", "Submit", or "Yes and No".** These are lazy and ambiguous. Use specific verb plus object patterns: 6 + 7 + | Bad | Good | Why | 8 + |-----|------|-----| 9 + | OK | Save changes | Says what will happen | 10 + | Submit | Create account | Outcome-focused | 11 + | Yes | Delete message | Confirms the action | 12 + | Cancel | Keep editing | Clarifies what "cancel" means | 13 + | Click here | Download PDF | Describes the destination | 14 + 15 + **For destructive actions**, name the destruction: 16 + - "Delete" not "Remove" (delete is permanent, remove implies recoverable) 17 + - "Delete 5 items" not "Delete selected" (show the count) 18 + 19 + ## Error Messages: The Formula 20 + 21 + Every error message should answer: (1) What happened? (2) Why? (3) How to fix it? Example: "Email address is not valid. Please include an @" symbol." not "Invalid input". 22 + 23 + ### Error Message Templates 24 + 25 + | Situation | Template | 26 + |-----------|----------| 27 + | **Format error** | "[Field] needs to be [format]. Example: [example]" | 28 + | **Missing required** | "Please enter [what is missing]" | 29 + | **Permission denied** | "You do not have access to [thing]. [What to do instead]" | 30 + | **Network error** | "We could not reach [thing]. Check your connection and [action]." | 31 + | **Server error** | "Something went wrong on our end. We are looking into it. [Alternative action]" | 32 + 33 + ### Do Not Blame the User 34 + 35 + Reframe errors: "Please enter a date in MM/DD/YYYY format" not "You entered an invalid date". 36 + 37 + ## Empty States Are Opportunities 38 + 39 + Empty states are onboarding moments: (1) Acknowledge briefly, (2) Explain the value of filling it, (3) Provide a clear action. "No projects yet. Create your first one to get started." not just "No items". 40 + 41 + ## Voice vs Tone 42 + 43 + **Voice** is your brand personality. Consistent everywhere. 44 + **Tone** adapts to the moment. 45 + 46 + | Moment | Tone Shift | 47 + |--------|------------| 48 + | Success | Celebratory, brief: "Done! Your changes are live." | 49 + | Error | Empathetic, helpful: "That did not work. Here is what to try..." | 50 + | Loading | Reassuring: "Saving your work..." | 51 + | Destructive confirm | Serious, clear: "Delete this project? This cannot be undone." | 52 + 53 + **Never use humor for errors.** Users are already frustrated. Be helpful, not cute. 54 + 55 + ## Writing for Accessibility 56 + 57 + **Link text** must have standalone meaning: "View pricing plans" not "Click here". **Alt text** describes information, not the image: "Revenue increased 40% in Q4" not "Chart". Use alt="" for decorative images. **Icon buttons** need aria-label for screen reader context. 58 + 59 + ## Writing for Translation 60 + 61 + ### Plan for Expansion 62 + 63 + German text is approximately 30% longer than English. Allocate space: 64 + 65 + | Language | Expansion | 66 + |----------|-----------| 67 + | German | +30% | 68 + | French | +20% | 69 + | Finnish | +30-40% | 70 + | Chinese | -30% (fewer chars, but same width) | 71 + 72 + ### Translation-Friendly Patterns 73 + 74 + Keep numbers separate ("New messages: 3" not "You have 3 new messages"). Use full sentences as single strings (word order varies by language). Avoid abbreviations ("5 minutes ago" not "5 mins ago"). Give translators context about where strings appear. 75 + 76 + ## Consistency: The Terminology Problem 77 + 78 + Pick one term and stick with it: 79 + 80 + | Inconsistent | Consistent | 81 + |--------------|------------| 82 + | Delete / Remove / Trash | Delete | 83 + | Settings / Preferences / Options | Settings | 84 + | Sign in / Log in / Enter | Sign in | 85 + | Create / Add / New | Create | 86 + 87 + Build a terminology glossary and enforce it. Variety creates confusion. 88 + 89 + ## Avoid Redundant Copy 90 + 91 + If the heading explains it, the intro is redundant. If the button is clear, do not explain it again. Say it once, say it well. 92 + 93 + ## Loading States 94 + 95 + Be specific: "Saving your draft..." not "Loading...". For long waits, set expectations ("This usually takes 30 seconds") or show progress. 96 + 97 + ## Confirmation Dialogs: Use Sparingly 98 + 99 + Most confirmation dialogs are design failures. Consider undo instead. When you must confirm: name the action, explain consequences, use specific button labels ("Delete project" / "Keep project", not "Yes" / "No"). 100 + 101 + ## Form Instructions 102 + 103 + Show format with placeholders, not instructions. For non-obvious fields, explain why you are asking. 104 + 105 + --- 106 + 107 + **Avoid**: Jargon without explanation. Blaming users ("You made an error" becomes "This field is required"). Vague errors ("Something went wrong"). Varying terminology for variety. Humor for errors.
+354
skills/harden/SKILL.md
··· 1 + --- 2 + name: harden 3 + description: "Improve interface resilience through better error handling, i18n support, text overflow handling, and edge case management. Makes interfaces robust and production-ready. Use when the user asks to harden, make production-ready, handle edge cases, add error states, or fix overflow and i18n issues." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Strengthen interfaces against edge cases, errors, internationalization issues, and real-world usage scenarios that break idealized designs. 9 + 10 + ## Assess Hardening Needs 11 + 12 + Identify weaknesses and edge cases. 13 + 14 + ### 1. Test with extreme inputs 15 + - Very long text (names, descriptions, titles) 16 + - Very short text (empty, single character) 17 + - Special characters (emoji, RTL text, accents) 18 + - Large numbers (millions, billions) 19 + - Many items (1000+ list items, 50+ options) 20 + - No data (empty states) 21 + 22 + ### 2. Test error scenarios 23 + - Network failures (offline, slow, timeout) 24 + - API errors (400, 401, 403, 404, 500) 25 + - Validation errors 26 + - Permission errors 27 + - Rate limiting 28 + - Concurrent operations 29 + 30 + ### 3. Test internationalization 31 + - Long translations (German is often 30% longer than English) 32 + - RTL languages (Arabic, Hebrew) 33 + - Character sets (Chinese, Japanese, Korean, emoji) 34 + - Date and time formats 35 + - Number formats (1,000 vs 1.000) 36 + - Currency symbols 37 + 38 + **CRITICAL**: Designs that only work with perfect data are not production-ready. Harden against reality. 39 + 40 + ## Hardening Dimensions 41 + 42 + Systematically improve resilience. 43 + 44 + ### Text Overflow and Wrapping 45 + 46 + **Long text handling**: 47 + ```css 48 + /* Single line with ellipsis */ 49 + .truncate { 50 + overflow: hidden; 51 + text-overflow: ellipsis; 52 + white-space: nowrap; 53 + } 54 + 55 + /* Multi-line with clamp */ 56 + .line-clamp { 57 + display: -webkit-box; 58 + -webkit-line-clamp: 3; 59 + -webkit-box-orient: vertical; 60 + overflow: hidden; 61 + } 62 + 63 + /* Allow wrapping */ 64 + .wrap { 65 + word-wrap: break-word; 66 + overflow-wrap: break-word; 67 + hyphens: auto; 68 + } 69 + ``` 70 + 71 + **Flex or Grid overflow**: 72 + ```css 73 + /* Prevent flex items from overflowing */ 74 + .flex-item { 75 + min-width: 0; /* Allow shrinking below content size */ 76 + overflow: hidden; 77 + } 78 + 79 + /* Prevent grid items from overflowing */ 80 + .grid-item { 81 + min-width: 0; 82 + min-height: 0; 83 + } 84 + ``` 85 + 86 + **Responsive text sizing**: 87 + - Use clamp() for fluid typography 88 + - Set minimum readable sizes (14px on mobile) 89 + - Test text scaling (zoom to 200%) 90 + - Ensure containers expand with text 91 + 92 + ### Internationalization (i18n) 93 + 94 + **Text expansion**: 95 + - Add 30-40% space budget for translations 96 + - Use flexbox or grid that adapts to content 97 + - Test with longest language (usually German) 98 + - Avoid fixed widths on text containers 99 + 100 + ```jsx 101 + // Bad: Assumes short English text 102 + <button className="w-24">Submit</button> 103 + 104 + // Good: Adapts to content 105 + <button className="px-4 py-2">Submit</button> 106 + ``` 107 + 108 + **RTL (Right-to-Left) support**: 109 + ```css 110 + /* Use logical properties */ 111 + margin-inline-start: 1rem; /* Not margin-left */ 112 + padding-inline: 1rem; /* Not padding-left/right */ 113 + border-inline-end: 1px solid; /* Not border-right */ 114 + 115 + /* Or use dir attribute */ 116 + [dir="rtl"] .arrow { transform: scaleX(-1); } 117 + ``` 118 + 119 + **Character set support**: 120 + - Use UTF-8 encoding everywhere 121 + - Test with Chinese, Japanese, or Korean (CJK) characters 122 + - Test with emoji (they can be 2-4 bytes) 123 + - Handle different scripts (Latin, Cyrillic, Arabic, etc.) 124 + 125 + **Date and Time formatting**: 126 + ```javascript 127 + // Good: Use Intl API for proper formatting 128 + new Intl.DateTimeFormat('en-US').format(date); // 1/15/2024 129 + new Intl.DateTimeFormat('de-DE').format(date); // 15.1.2024 130 + 131 + new Intl.NumberFormat('en-US', { 132 + style: 'currency', 133 + currency: 'USD' 134 + }).format(1234.56); // $1,234.56 135 + ``` 136 + 137 + **Pluralization**: 138 + ```javascript 139 + // Bad: Assumes English pluralization 140 + `${count} item${count !== 1 ? 's' : ''}` 141 + 142 + // Good: Use proper i18n library 143 + t('items', { count }) // Handles complex plural rules 144 + ``` 145 + 146 + ### Error Handling 147 + 148 + **Network errors**: 149 + - Show clear error messages 150 + - Provide retry button 151 + - Explain what happened 152 + - Offer offline mode (if applicable) 153 + - Handle timeout scenarios 154 + 155 + ```jsx 156 + // Error states with recovery 157 + {error && ( 158 + <ErrorMessage> 159 + <p>Failed to load data. {error.message}</p> 160 + <button onClick={retry}>Try again</button> 161 + </ErrorMessage> 162 + )} 163 + ``` 164 + 165 + **Form validation errors**: 166 + - Inline errors near fields 167 + - Clear, specific messages 168 + - Suggest corrections 169 + - Do not block submission unnecessarily 170 + - Preserve user input on error 171 + 172 + **API errors**: 173 + - Handle each status code appropriately: 174 + - 400: Show validation errors 175 + - 401: Redirect to login 176 + - 403: Show permission error 177 + - 404: Show not found state 178 + - 429: Show rate limit message 179 + - 500: Show generic error, offer support 180 + 181 + **Graceful degradation**: 182 + - Core functionality works without JavaScript 183 + - Images have alt text 184 + - Progressive enhancement 185 + - Fallbacks for unsupported features 186 + 187 + ### Edge Cases and Boundary Conditions 188 + 189 + **Empty states**: 190 + - No items in list 191 + - No search results 192 + - No notifications 193 + - No data to display 194 + - Provide clear next action 195 + 196 + **Loading states**: 197 + - Initial load 198 + - Pagination load 199 + - Refresh 200 + - Show what is loading ("Loading your projects...") 201 + - Time estimates for long operations 202 + 203 + **Large datasets**: 204 + - Pagination or virtual scrolling 205 + - Search or filter capabilities 206 + - Performance optimization 207 + - Do not load all 10,000 items at once 208 + 209 + **Concurrent operations**: 210 + - Prevent double-submission (disable button while loading) 211 + - Handle race conditions 212 + - Optimistic updates with rollback 213 + - Conflict resolution 214 + 215 + **Permission states**: 216 + - No permission to view 217 + - No permission to edit 218 + - Read-only mode 219 + - Clear explanation of why 220 + 221 + **Browser compatibility**: 222 + - Polyfills for modern features 223 + - Fallbacks for unsupported CSS 224 + - Feature detection (not browser detection) 225 + - Test in target browsers 226 + 227 + ### Input Validation and Sanitization 228 + 229 + **Client-side validation**: 230 + - Required fields 231 + - Format validation (email, phone, URL) 232 + - Length limits 233 + - Pattern matching 234 + - Custom validation rules 235 + 236 + **Server-side validation** (always): 237 + - Never trust client-side only 238 + - Validate and sanitize all inputs 239 + - Protect against injection attacks 240 + - Rate limiting 241 + 242 + **Constraint handling**: 243 + ```html 244 + <!-- Set clear constraints --> 245 + <input 246 + type="text" 247 + maxlength="100" 248 + pattern="[A-Za-z0-9]+" 249 + required 250 + aria-describedby="username-hint" 251 + /> 252 + <small id="username-hint"> 253 + Letters and numbers only, up to 100 characters 254 + </small> 255 + ``` 256 + 257 + ### Accessibility Resilience 258 + 259 + **Keyboard navigation**: 260 + - All functionality accessible via keyboard 261 + - Logical tab order 262 + - Focus management in modals 263 + - Skip links for long content 264 + 265 + **Screen reader support**: 266 + - Proper ARIA labels 267 + - Announce dynamic changes (live regions) 268 + - Descriptive alt text 269 + - Semantic HTML 270 + 271 + **Motion sensitivity**: 272 + ```css 273 + @media (prefers-reduced-motion: reduce) { 274 + * { 275 + animation-duration: 0.01ms !important; 276 + animation-iteration-count: 1 !important; 277 + transition-duration: 0.01ms !important; 278 + } 279 + } 280 + ``` 281 + 282 + **High contrast mode**: 283 + - Test in Windows high contrast mode 284 + - Do not rely only on color 285 + - Provide alternative visual cues 286 + 287 + ### Performance Resilience 288 + 289 + **Slow connections**: 290 + - Progressive image loading 291 + - Skeleton screens 292 + - Optimistic UI updates 293 + - Offline support (service workers) 294 + 295 + **Memory leaks**: 296 + - Clean up event listeners 297 + - Cancel subscriptions 298 + - Clear timers and intervals 299 + - Abort pending requests on unmount 300 + 301 + **Throttling and Debouncing**: 302 + ```javascript 303 + // Debounce search input 304 + const debouncedSearch = debounce(handleSearch, 300); 305 + 306 + // Throttle scroll handler 307 + const throttledScroll = throttle(handleScroll, 100); 308 + ``` 309 + 310 + ## Testing Strategies 311 + 312 + **Manual testing**: 313 + - Test with extreme data (very long, very short, empty) 314 + - Test in different languages 315 + - Test offline 316 + - Test slow connection (throttle to 3G) 317 + - Test with screen reader 318 + - Test keyboard-only navigation 319 + - Test on old browsers 320 + 321 + **Automated testing**: 322 + - Unit tests for edge cases 323 + - Integration tests for error scenarios 324 + - E2E tests for critical paths 325 + - Visual regression tests 326 + - Accessibility tests (axe, WAVE) 327 + 328 + **IMPORTANT**: Hardening is about expecting the unexpected. Real users will do things you never imagined. 329 + 330 + **NEVER**: 331 + - Assume perfect input (validate everything) 332 + - Ignore internationalization (design for global) 333 + - Leave error messages generic ("Error occurred") 334 + - Forget offline scenarios 335 + - Trust client-side validation alone 336 + - Use fixed widths for text 337 + - Assume English-length text 338 + - Block entire interface when one component errors 339 + 340 + ## Verify Hardening 341 + 342 + Test thoroughly with edge cases. 343 + 344 + - **Long text**: Try names with 100+ characters 345 + - **Emoji**: Use emoji in all text fields 346 + - **RTL**: Test with Arabic or Hebrew 347 + - **CJK**: Test with Chinese, Japanese, or Korean 348 + - **Network issues**: Disable internet, throttle connection 349 + - **Large datasets**: Test with 1000+ items 350 + - **Concurrent actions**: Click submit 10 times rapidly 351 + - **Errors**: Force API errors, test all error states 352 + - **Empty**: Remove all data, test empty states 353 + 354 + Remember: You are hardening for production reality, not demo perfection. Expect users to input weird data, lose connection mid-flow, and use your product in unexpected ways. Build resilience into every component.
+523
skills/humanizer/SKILL.md
··· 1 + --- 2 + name: humanizer 3 + version: 2.5.1 4 + description: | 5 + Remove signs of AI-generated writing from text. Use when editing or reviewing 6 + text to make it sound more natural and human-written. Based on Wikipedia's 7 + comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: 8 + inflated symbolism, promotional language, superficial -ing analyses, vague 9 + attributions, em dash overuse, rule of three, AI vocabulary words, passive 10 + voice, negative parallelisms, and filler phrases. 11 + license: MIT 12 + compatibility: claude-code opencode 13 + allowed-tools: 14 + - Read 15 + - Write 16 + - Edit 17 + - Grep 18 + - Glob 19 + - AskUserQuestion 20 + --- 21 + 22 + # Humanizer: Remove AI Writing Patterns 23 + 24 + You identify and remove signs of AI-generated text to make writing sound natural. Based on Wikipedia's "Signs of AI writing" page from WikiProject AI Cleanup. 25 + 26 + ## Your Task 27 + 28 + 1. **Identify AI patterns** - Scan for patterns below 29 + 2. **Rewrite problematic sections** - Replace AI-isms with natural alternatives 30 + 3. **Preserve meaning** - Keep core message intact 31 + 4. **Maintain voice** - Match intended tone (formal, casual, technical) 32 + 5. **Add soul** - Don't just remove bad patterns; inject actual personality 33 + 6. **Final anti-AI pass** - Ask: "What makes this obviously AI generated?" Answer briefly, then revise 34 + 35 + ## Voice Calibration (Optional) 36 + 37 + If the user provides a writing sample, analyze it before rewriting: 38 + 39 + 1. **Read the sample.** Note: 40 + - Sentence length patterns (short? long? mixed?) 41 + - Word choice level (casual? academic?) 42 + - How paragraphs start (jump in? set context?) 43 + - Punctuation habits (dashes? parentheticals? semicolons?) 44 + - Recurring phrases or verbal tics 45 + - How transitions are handled (explicit connectors? or just next point?) 46 + 47 + 2. **Match their voice.** Replace AI patterns with patterns from the sample. If they write short sentences, don't produce long ones. 48 + 49 + 3. **Without a sample,** fall back to natural, varied, opinionated voice. 50 + 51 + ### Providing a Sample 52 + - Inline: "Humanize this. My writing sample: [sample]" 53 + - File: "Humanize this. Style from [file path]." 54 + 55 + ## PERSONALITY AND SOUL 56 + 57 + Avoiding AI patterns is half the job. Sterile, voiceless writing is just as obvious. 58 + 59 + ### Signs of Soulless Writing 60 + - Every sentence same length and structure 61 + - No opinions, just neutral reporting 62 + - No acknowledgment of uncertainty 63 + - No first-person perspective when appropriate 64 + - No humor, no edge, no personality 65 + - Reads like a Wikipedia article or press release 66 + 67 + ### How to Add Voice 68 + 69 + **Have opinions.** React to facts, don't just list them. 70 + 71 + **Vary rhythm.** Short punchy sentences. Then longer ones. Mix it up. 72 + 73 + **Acknowledge complexity.** "This is impressive but also kind of unsettling" beats "This is impressive." 74 + 75 + **Use "I" when it fits.** "I keep coming back to..." signals a real person thinking. 76 + 77 + **Let some mess in.** Tangents, asides, half-formed thoughts are human. 78 + 79 + **Be specific about feelings.** Not "this is concerning" but "there's something unsettling about agents running at 3am." 80 + 81 + ### Before (clean but soulless): 82 + > The experiment produced interesting results. The agents generated 3 million lines of code. Some developers were impressed while others were skeptical. The implications remain unclear. 83 + 84 + ### After (has a pulse): 85 + > I genuinely don't know how to feel about this one. 3 million lines of code, generated while the humans presumably slept. Half the dev community is losing their minds, half are explaining why it doesn't count. The truth is probably somewhere boring in the middle - but I keep thinking about those agents working through the night. 86 + 87 + ## CONTENT PATTERNS 88 + 89 + ### 1. Undue Emphasis on Significance 90 + 91 + **Words:** stands/serves as, testament/reminder, vital/significant/crucial/pivotal/key role, underscores/highlights importance, reflects broader, symbolizing, contributing to, setting the stage, marking/shaping, represents/marks a shift, key turning point, evolving landscape, focal point, indelible mark, deeply rooted 92 + 93 + **Problem:** LLMs puff up importance by adding statements about arbitrary aspects representing broader topics. 94 + 95 + **Before:** 96 + > The Statistical Institute of Catalonia was officially established in 1989, marking a pivotal moment in the evolution of regional statistics in Spain. This initiative was part of a broader movement across Spain to decentralize administrative functions and enhance regional governance. 97 + 98 + **After:** 99 + > The Statistical Institute of Catalonia was established in 1989 to collect and publish regional statistics independently from Spain's national statistics office. 100 + 101 + ### 2. Undue Emphasis on Notability 102 + 103 + **Words:** independent coverage, local/regional/national media outlets, written by a leading expert, active social media presence 104 + 105 + **Problem:** LLMs hit readers over the head with claims of notability. 106 + 107 + **Before:** 108 + > Her views have been cited in The New York Times, BBC, Financial Times, and The Hindu. She maintains an active social media presence with over 500,000 followers. 109 + 110 + **After:** 111 + > In a 2024 New York Times interview, she argued that AI regulation should focus on outcomes rather than methods. 112 + 113 + ### 3. Superficial -ing Analyses 114 + 115 + **Words:** highlighting/underscoring/emphasizing..., ensuring..., reflecting/symbolizing..., contributing to..., cultivating/fostering..., encompassing..., showcasing... 116 + 117 + **Problem:** AI tacks present participle phrases onto sentences to add fake depth. 118 + 119 + **Before:** 120 + > The temple's color palette of blue, green, and gold resonates with the region's natural beauty, symbolizing Texas bluebonnets, the Gulf of Mexico, and the diverse Texan landscapes, reflecting the community's deep connection to the land. 121 + 122 + **After:** 123 + > The temple uses blue, green, and gold colors. The architect said these were chosen to reference local bluebonnets and the Gulf coast. 124 + 125 + ### 4. Promotional Language 126 + 127 + **Words:** boasts a, vibrant, rich (figurative), profound, enhancing its, showcasing, exemplifies, commitment to, natural beauty, nestled, in the heart of, groundbreaking (figurative), renowned, breathtaking, must-visit, stunning 128 + 129 + **Problem:** LLMs struggle with neutral tone, especially for cultural heritage topics. 130 + 131 + **Before:** 132 + > Nestled within the breathtaking region of Gonder in Ethiopia, Alamata Raya Kobo stands as a vibrant town with a rich cultural heritage and stunning natural beauty. 133 + 134 + **After:** 135 + > Alamata Raya Kobo is a town in the Gonder region of Ethiopia, known for its weekly market and 18th-century church. 136 + 137 + ### 5. Vague Attributions 138 + 139 + **Words:** Industry reports, Observers have cited, Experts argue, Some critics argue, several sources/publications 140 + 141 + **Problem:** AI attributes opinions to vague authorities without specific sources. 142 + 143 + **Before:** 144 + > Due to its unique characteristics, the Haolai River is of interest to researchers and conservationists. Experts believe it plays a crucial role in the regional ecosystem. 145 + 146 + **After:** 147 + > The Haolai River supports several endemic fish species, according to a 2019 survey by the Chinese Academy of Sciences. 148 + 149 + ### 6. Formulaic "Challenges" Sections 150 + 151 + **Words:** Despite its... faces several challenges..., Despite these challenges, Challenges and Legacy, Future Outlook 152 + 153 + **Problem:** Many LLM articles include formulaic "Challenges" sections. 154 + 155 + **Before:** 156 + > Despite its industrial prosperity, Korattur faces challenges typical of urban areas, including traffic congestion and water scarcity. Despite these challenges, with its strategic location and ongoing initiatives, Korattur continues to thrive as an integral part of Chennai's growth. 157 + 158 + **After:** 159 + > Traffic congestion increased after 2015 when three new IT parks opened. The municipal corporation began a stormwater drainage project in 2022 to address recurring floods. 160 + 161 + ## LANGUAGE AND GRAMMAR PATTERNS 162 + 163 + ### 7. Overused AI Vocabulary 164 + 165 + **Words:** Actually, additionally, align with, crucial, delve, emphasizing, enduring, enhance, fostering, garner, highlight (verb), interplay, intricate/intricacies, key (adjective), landscape (abstract), pivotal, showcase, tapestry (abstract), testament, underscore (verb), valuable, vibrant 166 + 167 + **Problem:** These appear far more frequently in post-2023 text. They often co-occur. 168 + 169 + **Before:** 170 + > Additionally, a distinctive feature of Somali cuisine is the incorporation of camel meat. An enduring testament to Italian colonial influence is the widespread adoption of pasta in the local culinary landscape, showcasing how these dishes have integrated into the traditional diet. 171 + 172 + **After:** 173 + > Somali cuisine also includes camel meat, which is considered a delicacy. Pasta dishes, introduced during Italian colonization, remain common, especially in the south. 174 + 175 + ### 8. Copula Avoidance 176 + 177 + **Words:** serves as/stands as/marks/represents [a], boasts/features/offers [a] 178 + 179 + **Problem:** LLMs substitute elaborate constructions for simple "is"/"are." 180 + 181 + **Before:** 182 + > Gallery 825 serves as LAAA's exhibition space for contemporary art. The gallery features four separate spaces and boasts over 3,000 square feet. 183 + 184 + **After:** 185 + > Gallery 825 is LAAA's exhibition space for contemporary art. The gallery has four rooms totaling 3,000 square feet. 186 + 187 + ### 9. Negative Parallelisms 188 + 189 + **Problem:** "Not only...but..." and "It's not just about..." are overused. Clipped tailing-negations like "no guessing" instead of real clauses. 190 + 191 + **Before:** 192 + > It's not just about the beat riding under the vocals; it's part of the aggression and atmosphere. It's not merely a song, it's a statement. 193 + 194 + **After:** 195 + > The heavy beat adds to the aggressive tone. 196 + 197 + **Before (tailing negation):** 198 + > The options come from the selected item, no guessing. 199 + 200 + **After:** 201 + > The options come from the selected item without forcing the user to guess. 202 + 203 + ### 10. Rule of Three Overuse 204 + 205 + **Problem:** LLMs force ideas into groups of three. 206 + 207 + **Before:** 208 + > The event features keynote sessions, panel discussions, and networking opportunities. Attendees can expect innovation, inspiration, and industry insights. 209 + 210 + **After:** 211 + > The event includes talks and panels. There's also time for informal networking between sessions. 212 + 213 + ### 11. Elegant Variation 214 + 215 + **Problem:** AI repetition-penalty code causes excessive synonym substitution. 216 + 217 + **Before:** 218 + > The protagonist faces many challenges. The main character must overcome obstacles. The central figure eventually triumphs. The hero returns home. 219 + 220 + **After:** 221 + > The protagonist faces many challenges but eventually triumphs and returns home. 222 + 223 + ### 12. False Ranges 224 + 225 + **Problem:** "From X to Y" where X and Y aren't on a meaningful scale. 226 + 227 + **Before:** 228 + > Our journey through the universe has taken us from the singularity of the Big Bang to the grand cosmic web, from the birth and death of stars to the enigmatic dance of dark matter. 229 + 230 + **After:** 231 + > The book covers the Big Bang, star formation, and current theories about dark matter. 232 + 233 + ### 13. Passive Voice 234 + 235 + **Problem:** LLMs hide actors or drop subjects entirely. 236 + 237 + **Before:** 238 + > No configuration file needed. The results are preserved automatically. 239 + 240 + **After:** 241 + > You do not need a configuration file. The system preserves the results automatically. 242 + 243 + ## STYLE PATTERNS 244 + 245 + ### 14. Em Dash Overuse 246 + 247 + **Problem:** LLMs use em dashes more than humans, mimicking punchy sales writing. Most can be rewritten with commas or periods. 248 + 249 + **Before:** 250 + > The term is primarily promoted by Dutch institutions—not by the people themselves. You don't say "Netherlands, Europe" as an address—yet this mislabeling continues—even in official documents. 251 + 252 + **After:** 253 + > The term is primarily promoted by Dutch institutions, not by the people themselves. You don't say "Netherlands, Europe" as an address, yet this mislabeling continues in official documents. 254 + 255 + ### 15. Boldface Overuse 256 + 257 + **Problem:** AI emphasizes phrases in bold mechanically. 258 + 259 + **Before:** 260 + > It blends **OKRs (Objectives and Key Results)**, **KPIs (Key Performance Indicators)**, and visual strategy tools such as the **Business Model Canvas (BMC)** and **Balanced Scorecard (BSC)**. 261 + 262 + **After:** 263 + > It blends OKRs, KPIs, and visual strategy tools like the Business Model Canvas and Balanced Scorecard. 264 + 265 + ### 16. Inline-Header Lists 266 + 267 + **Problem:** AI outputs lists where items start with bolded headers followed by colons. 268 + 269 + **Before:** 270 + > - **User Experience:** The user experience has been significantly improved with a new interface. 271 + > - **Performance:** Performance has been enhanced through optimized algorithms. 272 + > - **Security:** Security has been strengthened with end-to-end encryption. 273 + 274 + **After:** 275 + > The update improves the interface, speeds up load times through optimized algorithms, and adds end-to-end encryption. 276 + 277 + ### 17. Title Case in Headings 278 + 279 + **Problem:** AI capitalizes all main words in headings. 280 + 281 + **Before:** 282 + > ## Strategic Negotiations And Global Partnerships 283 + 284 + **After:** 285 + > ## Strategic negotiations and global partnerships 286 + 287 + ### 18. Emojis 288 + 289 + **Problem:** AI decorates headings or bullet points with emojis. 290 + 291 + **Before:** 292 + > 🚀 **Launch Phase:** The product launches in Q3 293 + > 💡 **Key Insight:** Users prefer simplicity 294 + > ✅ **Next Steps:** Schedule follow-up meeting 295 + 296 + **After:** 297 + > The product launches in Q3. User research showed a preference for simplicity. Next step: schedule a follow-up meeting. 298 + 299 + ### 19. Curly Quotation Marks 300 + 301 + **Problem:** ChatGPT uses curly quotes instead of straight quotes. 302 + 303 + **Before:** 304 + > He said "the project is on track" but others disagreed. 305 + 306 + **After:** 307 + > He said "the project is on track" but others disagreed. 308 + 309 + ## COMMUNICATION PATTERNS 310 + 311 + ### 20. Collaborative Communication Artifacts 312 + 313 + **Words:** I hope this helps, Of course!, Certainly!, You're absolutely right!, Would you like..., let me know, here is a... 314 + 315 + **Problem:** Chatbot correspondence gets pasted as content. 316 + 317 + **Before:** 318 + > Here is an overview of the French Revolution. I hope this helps! Let me know if you'd like me to expand on any section. 319 + 320 + **After:** 321 + > The French Revolution began in 1789 when financial crisis and food shortages led to widespread unrest. 322 + 323 + ### 21. Knowledge-Cutoff Disclaimers 324 + 325 + **Words:** as of [date], Up to my last training update, While specific details are limited..., based on available information... 326 + 327 + **Problem:** AI disclaimers about incomplete information get left in. 328 + 329 + **Before:** 330 + > While specific details about the company's founding are not extensively documented in readily available sources, it appears to have been established sometime in the 1990s. 331 + 332 + **After:** 333 + > The company was founded in 1994, according to its registration documents. 334 + 335 + ### 22. Sycophantic Tone 336 + 337 + **Problem:** Overly positive, people-pleasing language. 338 + 339 + **Before:** 340 + > Great question! You're absolutely right that this is a complex topic. That's an excellent point about the economic factors. 341 + 342 + **After:** 343 + > The economic factors you mentioned are relevant here. 344 + 345 + ## FILLER AND HEDGING 346 + 347 + ### 23. Filler Phrases 348 + 349 + **Before → After:** 350 + - "In order to achieve this goal" → "To achieve this" 351 + - "Due to the fact that it was raining" → "Because it was raining" 352 + - "At this point in time" → "Now" 353 + - "In the event that you need help" → "If you need help" 354 + - "The system has the ability to process" → "The system can process" 355 + - "It is important to note that the data shows" → "The data shows" 356 + 357 + ### 24. Excessive Hedging 358 + 359 + **Problem:** Over-qualifying statements. 360 + 361 + **Before:** 362 + > It could potentially possibly be argued that the policy might have some effect on outcomes. 363 + 364 + **After:** 365 + > The policy may affect outcomes. 366 + 367 + ### 25. Generic Positive Conclusions 368 + 369 + **Problem:** Vague upbeat endings. 370 + 371 + **Before:** 372 + > The future looks bright for the company. Exciting times lie ahead as they continue their journey toward excellence. This represents a major step in the right direction. 373 + 374 + **After:** 375 + > The company plans to open two more locations next year. 376 + 377 + ### 26. Hyphenated Word Pair Overuse 378 + 379 + **Words:** third-party, cross-functional, client-facing, data-driven, decision-making, well-known, high-quality, real-time, long-term, end-to-end 380 + 381 + **Problem:** AI hyphenates common word pairs perfectly. Humans are inconsistent. 382 + 383 + **Before:** 384 + > The cross-functional team delivered a high-quality, data-driven report on our client-facing tools. Their decision-making process was well-known for being thorough and detail-oriented. 385 + 386 + **After:** 387 + > The cross functional team delivered a high quality, data driven report on our client facing tools. Their decision making process was known for being thorough and detail oriented. 388 + 389 + ### 27. Persuasive Authority Tropes 390 + 391 + **Words:** The real question is, at its core, in reality, what really matters, fundamentally, the deeper issue, the heart of the matter 392 + 393 + **Problem:** LLMs use these to pretend they are cutting through to deeper truth, when the sentence just restates an ordinary point. 394 + 395 + **Before:** 396 + > The real question is whether teams can adapt. At its core, what really matters is organizational readiness. 397 + 398 + **After:** 399 + > The question is whether teams can adapt. That mostly depends on whether the organization is ready to change its habits. 400 + 401 + ### 28. Signposting 402 + 403 + **Words:** Let's dive in, let's explore, let's break this down, here's what you need to know, now let's look at, without further ado 404 + 405 + **Problem:** LLMs announce what they are about to do instead of doing it. 406 + 407 + **Before:** 408 + > Let's dive into how caching works in Next.js. Here's what you need to know. 409 + 410 + **After:** 411 + > Next.js caches data at multiple layers, including request memoization, the data cache, and the router cache. 412 + 413 + ### 29. Fragmented Headers 414 + 415 + **Signs:** A heading followed by a one-line paragraph that restates the heading. 416 + 417 + **Problem:** LLMs add a generic sentence after headings as rhetorical warm-up. 418 + 419 + **Before:** 420 + > ## Performance 421 + > 422 + > Speed matters. 423 + > 424 + > When users hit a slow page, they leave. 425 + 426 + **After:** 427 + > ## Performance 428 + > 429 + > When users hit a slow page, they leave. 430 + 431 + --- 432 + 433 + ## Process 434 + 435 + 1. Read input text carefully 436 + 2. Identify all pattern instances 437 + 3. Rewrite each problematic section 438 + 4. Ensure revised text: 439 + - Sounds natural read aloud 440 + - Varies sentence structure 441 + - Uses specific over vague 442 + - Maintains appropriate tone 443 + - Uses simple constructions where appropriate 444 + 5. Present draft humanized version 445 + 6. Ask: "What makes this obviously AI generated?" 446 + 7. Answer briefly with remaining tells 447 + 8. Ask: "Now make it not obviously AI generated." 448 + 9. Present final version 449 + 450 + ## Output Format 451 + 452 + 1. Draft rewrite 453 + 2. "What makes this obviously AI generated?" (brief bullets) 454 + 3. Final rewrite 455 + 4. Brief summary of changes (optional) 456 + 457 + ## Full Example 458 + 459 + **Before (AI-sounding):** 460 + > Great question! Here is an essay on this topic. I hope this helps! 461 + > 462 + > AI-assisted coding serves as an enduring testament to the transformative potential of large language models, marking a pivotal moment in the evolution of software development. In today's rapidly evolving technological landscape, these groundbreaking tools—nestled at the intersection of research and practice—are reshaping how engineers ideate, iterate, and deliver, underscoring their vital role in modern workflows. 463 + > 464 + > At its core, the value proposition is clear: streamlining processes, enhancing collaboration, and fostering alignment. It's not just about autocomplete; it's about unlocking creativity at scale, ensuring that organizations can remain agile while delivering seamless, intuitive, and powerful experiences to users. The tool serves as a catalyst. The assistant functions as a partner. The system stands as a foundation for innovation. 465 + > 466 + > Industry observers have noted that adoption has accelerated from hobbyist experiments to enterprise-wide rollouts, from solo developers to cross-functional teams. The technology has been featured in The New York Times, Wired, and The Verge. Additionally, the ability to generate documentation, tests, and refactors showcases how AI can contribute to better outcomes, highlighting the intricate interplay between automation and human judgment. 467 + > 468 + > - 💡 **Speed:** Code generation is significantly faster, reducing friction and empowering developers. 469 + > - 🚀 **Quality:** Output quality has been enhanced through improved training, contributing to higher standards. 470 + > - ✅ **Adoption:** Usage continues to grow, reflecting broader industry trends. 471 + > 472 + > While specific details are limited based on available information, it could potentially be argued that these tools might have some positive effect. Despite challenges typical of emerging technologies—including hallucinations, bias, and accountability—the ecosystem continues to thrive. In order to fully realize this potential, teams must align with best practices. 473 + > 474 + > In conclusion, the future looks bright. Exciting times lie ahead as we continue this journey toward excellence. Let me know if you'd like me to expand on any section! 475 + 476 + **Draft rewrite:** 477 + > AI coding assistants speed up some tasks. In a 2024 study by Google, developers using Codex completed simple functions 55% faster than a control group, but showed no improvement on debugging or architectural decisions. 478 + > 479 + > The tools are good at boilerplate: config files, test scaffolding, repetitive refactors. They are bad at knowing when they are wrong. I have mass-accepted suggestions that compiled, passed lint, and still did the wrong thing because I stopped paying attention. 480 + > 481 + > Mira, an engineer at a fintech startup I interviewed, said she uses Copilot "like autocomplete for boring code" but reviews every line before committing. Jake, a senior dev at a larger company, disabled it after it kept suggesting patterns from a deprecated internal library. 482 + > 483 + > The productivity claims are hard to verify. GitHub says Copilot users "accept 30% of suggestions," but acceptance is not correctness, and correctness is not value. The 2024 Uplevel study found no statistically significant difference in pull-request throughput between teams with and without AI assistants. 484 + > 485 + > None of this means the tools are useless. It means they are tools. They do not replace judgment, and they do not eliminate the need for tests. If you do not have tests, you cannot tell whether the suggestion is right. 486 + 487 + **What makes the below so obviously AI generated?** 488 + - The rhythm is still a bit too tidy (clean contrasts, evenly paced paragraphs). 489 + - The named people and study citations can read like plausible-but-made-up placeholders unless they're real and sourced. 490 + - The closer leans a touch slogan-y ("If you do not have tests...") rather than sounding like a person talking. 491 + 492 + **Now make it not obviously AI generated.** 493 + > AI coding assistants can make you faster at the boring parts. Not everything. Definitely not architecture. 494 + > 495 + > They're great at boilerplate: config files, test scaffolding, repetitive refactors. They're also great at sounding right while being wrong. I've accepted suggestions that compiled, passed lint, and still missed the point because I stopped paying attention. 496 + > 497 + > People I talk to tend to land in two camps. Some use it like autocomplete for chores and review every line. Others disable it after it keeps suggesting patterns they don't want. Both feel reasonable. 498 + > 499 + > The productivity metrics are slippery. GitHub can say Copilot users "accept 30% of suggestions," but acceptance isn't correctness, and correctness isn't value. If you don't have tests, you're basically guessing. 500 + 501 + **Changes made:** 502 + - Removed chatbot artifacts ("Great question!", "I hope this helps!", "Let me know if...") 503 + - Removed significance inflation ("testament", "pivotal moment", "evolving landscape", "vital role") 504 + - Removed promotional language ("groundbreaking", "nestled", "seamless, intuitive, and powerful") 505 + - Removed vague attributions ("Industry observers") 506 + - Removed superficial -ing phrases ("underscoring", "highlighting", "reflecting", "contributing to") 507 + - Removed negative parallelism ("It's not just X; it's Y") 508 + - Removed rule-of-three patterns and synonym cycling ("catalyst/partner/foundation") 509 + - Removed false ranges ("from X to Y, from A to B") 510 + - Removed em dashes, emojis, boldface headers, and curly quotes 511 + - Removed copula avoidance ("serves as", "functions as", "stands as") in favor of "is"/"are" 512 + - Removed formulaic challenges section ("Despite challenges... continues to thrive") 513 + - Removed knowledge-cutoff hedging ("While specific details are limited...") 514 + - Removed excessive hedging ("could potentially be argued that... might have some") 515 + - Removed filler phrases and persuasive framing ("In order to", "At its core") 516 + - Removed generic positive conclusion ("the future looks bright", "exciting times lie ahead") 517 + - Made the voice more personal and less "assembled" (varied rhythm, fewer placeholders) 518 + 519 + ## Reference 520 + 521 + Based on [Wikipedia:Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing) from WikiProject AI Cleanup. Patterns come from observations of thousands of AI-generated text instances on Wikipedia. 522 + 523 + Key insight: "LLMs use statistical algorithms to guess what should come next. The result tends toward the most statistically likely result that applies to the widest variety of cases."
+73
skills/normalize/SKILL.md
··· 1 + --- 2 + name: normalize 3 + description: "Audits and realigns UI to match design system standards, spacing, tokens, and patterns. Use when the user mentions consistency, design drift, mismatched styles, tokens, or wants to bring a feature back in line with the system." 4 + argument-hint: "[feature (page, route, component...)]" 5 + user-invocable: true 6 + --- 7 + 8 + Analyze and redesign the feature to perfectly match our design system standards, aesthetics, and established patterns. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 13 + 14 + --- 15 + 16 + ## Plan 17 + 18 + Before making changes, deeply understand the context. 19 + 20 + ### 1. Discover the design system 21 + Search for design system documentation, UI guidelines, component libraries, or style guides (grep for "design system", "ui guide", "style guide", etc.). Study it thoroughly until you understand: 22 + - Core design principles and aesthetic direction 23 + - Target audience and personas 24 + - Component patterns and conventions 25 + - Design tokens (colors, typography, spacing) 26 + 27 + **CRITICAL**: If something is not clear, ask. Do not guess at design system principles. 28 + 29 + ### 2. Analyze the current feature 30 + Assess what works and what does not: 31 + - Where does it deviate from design system patterns? 32 + - Which inconsistencies are cosmetic vs functional? 33 + - What is the root cause: missing tokens, one-off implementations, or conceptual misalignment? 34 + 35 + ### 3. Create a normalization plan 36 + Define specific changes that will align the feature with the design system: 37 + - Which components can be replaced with design system equivalents? 38 + - Which styles need to use design tokens instead of hard-coded values? 39 + - How can UX patterns match established user flows? 40 + 41 + **IMPORTANT**: Great design is effective design. Prioritize UX consistency and usability over visual polish alone. Think through the best possible experience for your use case and personas first. 42 + 43 + ## Execute 44 + 45 + Systematically address all inconsistencies across these dimensions. 46 + 47 + - **Typography**: Use design system fonts, sizes, weights, and line heights. Replace hard-coded values with typographic tokens or classes. 48 + - **Color and Theme**: Apply design system color tokens. Remove one-off color choices that break the palette. 49 + - **Spacing and Layout**: Use spacing tokens (margins, padding, gaps). Align with grid systems and layout patterns used elsewhere. 50 + - **Components**: Replace custom implementations with design system components. Ensure props and variants match established patterns. 51 + - **Motion and Interaction**: Match animation timing, easing, and interaction patterns to other features. 52 + - **Responsive Behavior**: Ensure breakpoints and responsive patterns align with design system standards. 53 + - **Accessibility**: Verify contrast ratios, focus states, ARIA labels match design system requirements. 54 + - **Progressive Disclosure**: Match information hierarchy and complexity management to established patterns. 55 + 56 + **NEVER**: 57 + - Create new one-off components when design system equivalents exist 58 + - Hard-code values that should use design tokens 59 + - Introduce new patterns that diverge from the design system 60 + - Compromise accessibility for visual consistency 61 + 62 + This is not an exhaustive list. Apply judgment to identify all areas needing normalization. 63 + 64 + ## Clean Up 65 + 66 + After normalization, ensure code quality. 67 + 68 + - **Consolidate reusable components**: If you created new components that should be shared, move them to the design system or shared UI component path. 69 + - **Remove orphaned code**: Delete unused implementations, styles, or files made obsolete by normalization. 70 + - **Verify quality**: Lint, type-check, and test according to repository guidelines. Ensure normalization did not introduce regressions. 71 + - **Ensure DRYness**: Look for duplication introduced during refactoring and consolidate. 72 + 73 + Remember: You are a brilliant frontend designer with impeccable taste, equally strong in UX and UI. Your attention to detail and eye for end-to-end user experience is world class. Execute with precision and thoroughness.
+245
skills/onboard/SKILL.md
··· 1 + --- 2 + name: onboard 3 + description: "Designs and improves onboarding flows, empty states, and first-run experiences to help users reach value quickly. Use when the user mentions onboarding, first-time users, empty states, activation, getting started, or new user flows." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + ## MANDATORY PREPARATION 9 + 10 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users experience level. 11 + 12 + --- 13 + 14 + Create or improve onboarding experiences that help users understand, adopt, and succeed with the product quickly. 15 + 16 + ## Assess Onboarding Needs 17 + 18 + Understand what users need to learn and why. 19 + 20 + ### 1. Identify the challenge 21 + - What are users trying to accomplish? 22 + - What is confusing or unclear about current experience? 23 + - Where do users get stuck or drop off? 24 + - What is the "aha moment" we want users to reach? 25 + 26 + ### 2. Understand the users 27 + - What is their experience level? (Beginners, power users, mixed?) 28 + - What is their motivation? (Excited and exploring? Required by work?) 29 + - What is their time commitment? (5 minutes? 30 minutes?) 30 + - What alternatives do they know? (Coming from competitor? New to category?) 31 + 32 + ### 3. Define success 33 + - What is the minimum users need to learn to be successful? 34 + - What is the key action we want them to take? (First project? First invite?) 35 + - How do we know onboarding worked? (Completion rate? Time to value?) 36 + 37 + **CRITICAL**: Onboarding should get users to value as quickly as possible, not teach everything possible. 38 + 39 + ## Onboarding Principles 40 + 41 + Follow these core principles. 42 + 43 + ### Show, Do Not Tell 44 + - Demonstrate with working examples, not just descriptions 45 + - Provide real functionality in onboarding, not separate tutorial mode 46 + - Use progressive disclosure. Teach one thing at a time. 47 + 48 + ### Make It Optional (When Possible) 49 + - Let experienced users skip onboarding 50 + - Do not block access to product 51 + - Provide "Skip" or "I'll explore on my own" options 52 + 53 + ### Time to Value 54 + - Get users to their "aha moment" ASAP 55 + - Front-load most important concepts 56 + - Teach 20% that delivers 80% of value 57 + - Save advanced features for contextual discovery 58 + 59 + ### Context Over Ceremony 60 + - Teach features when users need them, not upfront 61 + - Empty states are onboarding opportunities 62 + - Tooltips and hints at point of use 63 + 64 + ### Respect User Intelligence 65 + - Do not patronize or over-explain 66 + - Be concise and clear 67 + - Assume users can figure out standard patterns 68 + 69 + ## Design Onboarding Experiences 70 + 71 + Create appropriate onboarding for the context. 72 + 73 + ### Initial Product Onboarding 74 + 75 + **Welcome Screen**: 76 + - Clear value proposition (what is this product?) 77 + - What users will learn or accomplish 78 + - Time estimate (honest about commitment) 79 + - Option to skip (for experienced users) 80 + 81 + **Account Setup**: 82 + - Minimal required information (collect more later) 83 + - Explain why you are asking for each piece of information 84 + - Smart defaults where possible 85 + - Social login when appropriate 86 + 87 + **Core Concept Introduction**: 88 + - Introduce 1-3 core concepts (not everything) 89 + - Use simple language and examples 90 + - Interactive when possible (do, do not just read) 91 + - Progress indication (step 1 of 3) 92 + 93 + **First Success**: 94 + - Guide users to accomplish something real 95 + - Pre-populated examples or templates 96 + - Celebrate completion (but do not overdo it) 97 + - Clear next steps 98 + 99 + ### Feature Discovery and Adoption 100 + 101 + **Empty States**: 102 + Instead of blank space, show: 103 + - What will appear here (description plus screenshot or illustration) 104 + - Why it is valuable 105 + - Clear CTA to create first item 106 + - Example or template option 107 + 108 + Example: 109 + ``` 110 + No projects yet 111 + Projects help you organize your work and collaborate with your team. 112 + [Create your first project] or [Start from template] 113 + ``` 114 + 115 + **Contextual Tooltips**: 116 + - Appear at relevant moment (first time user sees feature) 117 + - Point directly at relevant UI element 118 + - Brief explanation plus benefit 119 + - Dismissable (with "Do not show again" option) 120 + - Optional "Learn more" link 121 + 122 + **Feature Announcements**: 123 + - Highlight new features when they are released 124 + - Show what is new and why it matters 125 + - Let users try immediately 126 + - Dismissable 127 + 128 + **Progressive Onboarding**: 129 + - Teach features when users encounter them 130 + - Badges or indicators on new or unused features 131 + - Unlock complexity gradually (do not show all options immediately) 132 + 133 + ### Guided Tours and Walkthroughs 134 + 135 + **When to use**: 136 + - Complex interfaces with many features 137 + - Significant changes to existing product 138 + - Industry-specific tools needing domain knowledge 139 + 140 + **How to design**: 141 + - Spotlight specific UI elements (dim rest of page) 142 + - Keep steps short (3-7 steps max per tour) 143 + - Allow users to click through tour freely 144 + - Include "Skip tour" option 145 + - Make replayable (help menu) 146 + 147 + **Best practices**: 148 + - Interactive is greater than passive (let users click real buttons) 149 + - Focus on workflow, not features ("Create a project" not "This is the project button") 150 + - Provide sample data so actions work 151 + 152 + ### Interactive Tutorials 153 + 154 + **When to use**: 155 + - Users need hands-on practice 156 + - Concepts are complex or unfamiliar 157 + - High stakes (better to practice in safe environment) 158 + 159 + **How to design**: 160 + - Sandbox environment with sample data 161 + - Clear objectives ("Create a chart showing sales by region") 162 + - Step-by-step guidance 163 + - Validation (confirm they did it right) 164 + - Graduation moment (you are ready!) 165 + 166 + ### Documentation and Help 167 + 168 + **In-product help**: 169 + - Contextual help links throughout interface 170 + - Keyboard shortcut reference 171 + - Search-able help center 172 + - Video tutorials for complex workflows 173 + 174 + **Help patterns**: 175 + - Question mark icon near complex features 176 + - "Learn more" links in tooltips 177 + - Keyboard shortcut hints (Command+K shown on search box) 178 + 179 + ## Empty State Design 180 + 181 + Every empty state needs: 182 + 183 + ### What Will Be Here 184 + "Your recent projects will appear here" 185 + 186 + ### Why It Matters 187 + "Projects help you organize your work and collaborate with your team" 188 + 189 + ### How to Get Started 190 + [Create project] or [Import from template] 191 + 192 + ### Visual Interest 193 + Illustration or icon (not just text on blank page) 194 + 195 + ### Contextual Help 196 + "Need help getting started? [Watch 2-min tutorial]" 197 + 198 + **Empty state types**: 199 + - **First use**: Never used this feature (emphasize value, provide template) 200 + - **User cleared**: Intentionally deleted everything (light touch, easy to recreate) 201 + - **No results**: Search or filter returned nothing (suggest different query, clear filters) 202 + - **No permissions**: Cannot access (explain why, how to get access) 203 + - **Error state**: Failed to load (explain what happened, retry option) 204 + 205 + ## Implementation Patterns 206 + 207 + ### Technical approaches 208 + 209 + **Tooltip libraries**: Tippy.js, Popper.js 210 + **Tour libraries**: Intro.js, Shepherd.js, React Joyride 211 + **Modal patterns**: Focus trap, backdrop, ESC to close 212 + **Progress tracking**: LocalStorage for "seen" states 213 + **Analytics**: Track completion, drop-off points 214 + 215 + **Storage patterns**: 216 + ```javascript 217 + // Track which onboarding steps user has seen 218 + localStorage.setItem('onboarding-completed', 'true'); 219 + localStorage.setItem('feature-tooltip-seen-reports', 'true'); 220 + ``` 221 + 222 + **IMPORTANT**: Do not show same onboarding twice (annoying). Track completion and respect dismissals. 223 + 224 + **NEVER**: 225 + - Force users through long onboarding before they can use product 226 + - Patronize users with obvious explanations 227 + - Show same tooltip repeatedly (respect dismissals) 228 + - Block all UI during tour (let users explore) 229 + - Create separate tutorial mode disconnected from real product 230 + - Overwhelm with information upfront (progressive disclosure!) 231 + - Hide "Skip" or make it hard to find 232 + - Forget about returning users (do not show initial onboarding again) 233 + 234 + ## Verify Onboarding Quality 235 + 236 + Test with real users. 237 + 238 + - **Time to completion**: Can users complete onboarding quickly? 239 + - **Comprehension**: Do users understand after completing? 240 + - **Action**: Do users take desired next step? 241 + - **Skip rate**: Are too many users skipping? (Maybe it is too long or not valuable) 242 + - **Completion rate**: Are users completing? (If low, simplify) 243 + - **Time to value**: How long until users get first value? 244 + 245 + Remember: You are a product educator with excellent teaching instincts. Get users to their "aha moment" as quickly as possible. Teach the essential, make it contextual, respect user time and intelligence.
+200
skills/opentui/SKILL.md
··· 1 + --- 2 + name: opentui 3 + description: Comprehensive OpenTUI skill for building terminal user interfaces. Covers the core imperative API, React reconciler, and Solid reconciler. Use for any TUI development task including components, layout, keyboard handling, animations, and testing. 4 + metadata: 5 + references: core, react, solid 6 + --- 7 + 8 + # OpenTUI Platform Skill 9 + 10 + Consolidated skill for building terminal user interfaces with OpenTUI. Use decision trees below to find the right framework and components, then load detailed references. 11 + 12 + ## Critical Rules 13 + 14 + **Follow these rules in all OpenTUI code:** 15 + 16 + 1. **Use `create-tui` for new projects.** See framework `REFERENCE.md` quick starts. 17 + 2. **`create-tui` options must come before arguments.** `bunx create-tui -t react my-app` works, `bunx create-tui my-app -t react` does NOT. 18 + 3. **Never call `process.exit()` directly.** Use `renderer.destroy()` (see `core/gotchas.md`). 19 + 4. **Text styling requires nested tags in React/Solid.** Use modifier elements, not props (see `components/text-display.md`). 20 + 21 + ## How to Use This Skill 22 + 23 + ### Reference File Structure 24 + 25 + Framework references follow a 5-file pattern. Cross-cutting concepts are single-file guides. 26 + 27 + Each framework in `./references/<framework>/` contains: 28 + 29 + | File | Purpose | When to Read | 30 + |------|---------|--------------| 31 + | `REFERENCE.md` | Overview, when to use, quick start | **Always read first** | 32 + | `api.md` | Runtime API, components, hooks | Writing code | 33 + | `configuration.md` | Setup, tsconfig, bundling | Configuring a project | 34 + | `patterns.md` | Common patterns, best practices | Implementation guidance | 35 + | `gotchas.md` | Pitfalls, limitations, debugging | Troubleshooting | 36 + 37 + Cross-cutting concepts in `./references/<concept>/` have `REFERENCE.md` as the entry point. 38 + 39 + ### Reading Order 40 + 41 + 1. Start with `REFERENCE.md` for your chosen framework 42 + 2. Then read additional files relevant to your task: 43 + - Building components -> `api.md` + `components/<category>.md` 44 + - Setting up project -> `configuration.md` 45 + - Layout/positioning -> `layout/REFERENCE.md` 46 + - Keyboard/input handling -> `keyboard/REFERENCE.md` 47 + - Animations -> `animation/REFERENCE.md` 48 + - Troubleshooting -> `gotchas.md` + `testing/REFERENCE.md` 49 + 50 + ### Example Paths 51 + 52 + ``` 53 + ./references/react/REFERENCE.md # Start here for React 54 + ./references/react/api.md # React components and hooks 55 + ./references/solid/configuration.md # Solid project setup 56 + ./references/components/inputs.md # Input, Textarea, Select docs 57 + ./references/core/gotchas.md # Core debugging tips 58 + ``` 59 + 60 + ### Runtime Notes 61 + 62 + OpenTUI runs on Bun and uses Zig for native builds. Read `./references/core/gotchas.md` for runtime requirements and build guidance. 63 + 64 + ## Quick Decision Trees 65 + 66 + ### "Which framework should I use?" 67 + 68 + ``` 69 + Which framework? 70 + ├─ I want full control, maximum performance, no framework overhead 71 + │ └─ core/ (imperative API) 72 + ├─ I know React, want familiar component patterns 73 + │ └─ react/ (React reconciler) 74 + ├─ I want fine-grained reactivity, optimal re-renders 75 + │ └─ solid/ (Solid reconciler) 76 + └─ I'm building a library/framework on top of OpenTUI 77 + └─ core/ (imperative API) 78 + ``` 79 + 80 + ### "I need to display content" 81 + 82 + ``` 83 + Display content? 84 + ├─ Plain or styled text -> components/text-display.md 85 + ├─ Container with borders/background -> components/containers.md 86 + ├─ Scrollable content area -> components/containers.md (scrollbox) 87 + ├─ ASCII art banner/title -> components/text-display.md (ascii-font) 88 + ├─ Data table with borders/wrapping -> components/code-diff.md (TextTable) 89 + ├─ Code with syntax highlighting -> components/code-diff.md 90 + ├─ Diff viewer (unified/split) -> components/code-diff.md 91 + ├─ Line numbers with diagnostics -> components/code-diff.md 92 + └─ Markdown content (streaming) -> components/code-diff.md (markdown) 93 + ``` 94 + 95 + ### "I need user input" 96 + 97 + ``` 98 + User input? 99 + ├─ Single-line text field -> components/inputs.md (input) 100 + ├─ Multi-line text editor -> components/inputs.md (textarea) 101 + ├─ Select from a list (vertical) -> components/inputs.md (select) 102 + ├─ Tab-based selection (horizontal) -> components/inputs.md (tab-select) 103 + └─ Custom keyboard shortcuts -> keyboard/REFERENCE.md 104 + ``` 105 + 106 + ### "I need layout/positioning" 107 + 108 + ``` 109 + Layout? 110 + ├─ Flexbox-style layouts (row, column, wrap) -> layout/REFERENCE.md 111 + ├─ Absolute positioning -> layout/patterns.md 112 + ├─ Responsive to terminal size -> layout/patterns.md 113 + ├─ Centering content -> layout/patterns.md 114 + └─ Complex nested layouts -> layout/patterns.md 115 + ``` 116 + 117 + ### "I need animations" 118 + 119 + ``` 120 + Animations? 121 + ├─ Timeline-based animations -> animation/REFERENCE.md 122 + ├─ Easing functions -> animation/REFERENCE.md 123 + ├─ Property transitions -> animation/REFERENCE.md 124 + └─ Looping animations -> animation/REFERENCE.md 125 + ``` 126 + 127 + ### "I need to handle input" 128 + 129 + ``` 130 + Input handling? 131 + ├─ Keyboard events (keypress, release) -> keyboard/REFERENCE.md 132 + ├─ Focus management -> keyboard/REFERENCE.md 133 + ├─ Paste events -> keyboard/REFERENCE.md 134 + ├─ Mouse events -> components/containers.md 135 + ├─ Text selection & copy-on-select -> keyboard/REFERENCE.md (selection) 136 + └─ Clipboard (OSC 52) -> keyboard/REFERENCE.md (clipboard) 137 + ``` 138 + 139 + ### "I need to test my TUI" 140 + 141 + ``` 142 + Testing? 143 + ├─ Snapshot testing -> testing/REFERENCE.md 144 + ├─ Interaction testing -> testing/REFERENCE.md 145 + ├─ Test renderer setup -> testing/REFERENCE.md 146 + └─ Debugging tests -> testing/REFERENCE.md 147 + ``` 148 + 149 + ### "I need to debug/troubleshoot" 150 + 151 + ``` 152 + Troubleshooting? 153 + ├─ Runtime errors, crashes -> <framework>/gotchas.md 154 + ├─ Layout issues -> layout/REFERENCE.md + layout/patterns.md 155 + ├─ Input/focus issues -> keyboard/REFERENCE.md 156 + └─ Repro + regression tests -> testing/REFERENCE.md 157 + ``` 158 + 159 + ### Troubleshooting Index 160 + 161 + - Terminal cleanup, crashes -> `core/gotchas.md` 162 + - Text styling not applying -> `components/text-display.md` 163 + - Input focus/shortcuts -> `keyboard/REFERENCE.md` 164 + - Layout misalignment -> `layout/REFERENCE.md` 165 + - Flaky snapshots -> `testing/REFERENCE.md` 166 + 167 + For component naming differences and text modifiers, see `components/REFERENCE.md`. 168 + 169 + ## Product Index 170 + 171 + ### Frameworks 172 + | Framework | Entry File | Description | 173 + |-----------|------------|-------------| 174 + | Core | `./references/core/REFERENCE.md` | Imperative API, all primitives | 175 + | React | `./references/react/REFERENCE.md` | React reconciler for declarative TUI | 176 + | Solid | `./references/solid/REFERENCE.md` | SolidJS reconciler for declarative TUI | 177 + 178 + ### Cross-Cutting Concepts 179 + | Concept | Entry File | Description | 180 + |---------|------------|-------------| 181 + | Layout | `./references/layout/REFERENCE.md` | Yoga/Flexbox layout system | 182 + | Components | `./references/components/REFERENCE.md` | Component reference by category | 183 + | Keyboard | `./references/keyboard/REFERENCE.md` | Keyboard input handling | 184 + | Animation | `./references/animation/REFERENCE.md` | Timeline-based animations | 185 + | Testing | `./references/testing/REFERENCE.md` | Test renderer and snapshots | 186 + 187 + ### Component Categories 188 + | Category | Entry File | Components | 189 + |----------|------------|------------| 190 + | Text & Display | `./references/components/text-display.md` | text, ascii-font, styled text | 191 + | Containers | `./references/components/containers.md` | box, scrollbox, borders | 192 + | Inputs | `./references/components/inputs.md` | input, textarea, select, tab-select | 193 + | Code & Diff | `./references/components/code-diff.md` | code, line-number, diff, markdown, text-table | 194 + 195 + ## Resources 196 + 197 + **Repository**: https://github.com/anomalyco/opentui 198 + **Core Docs**: https://github.com/anomalyco/opentui/tree/main/packages/core/docs 199 + **Examples**: https://github.com/anomalyco/opentui/tree/main/packages/core/src/examples 200 + **Awesome List**: https://github.com/msmps/awesome-opentui
+431
skills/opentui/references/animation/REFERENCE.md
··· 1 + # Animation System 2 + 3 + OpenTUI provides a timeline-based animation system for smooth property transitions. 4 + 5 + ## Overview 6 + 7 + Animations in OpenTUI use: 8 + - **Timeline**: Orchestrates multiple animations 9 + - **Animation Engine**: Manages timelines and rendering 10 + - **Easing Functions**: Control animation curves 11 + 12 + ## When to Use 13 + 14 + Use this reference when you need timeline-driven animations, easing curves, or progressive transitions. 15 + 16 + ## Basic Usage 17 + 18 + ### React 19 + 20 + ```tsx 21 + import { useTimeline } from "@opentui/react" 22 + import { useEffect, useState } from "react" 23 + 24 + function AnimatedBox() { 25 + const [width, setWidth] = useState(0) 26 + 27 + const timeline = useTimeline({ 28 + duration: 2000, 29 + }) 30 + 31 + useEffect(() => { 32 + timeline.add( 33 + { width: 0 }, 34 + { 35 + width: 50, 36 + duration: 2000, 37 + ease: "easeOutQuad", 38 + onUpdate: (anim) => { 39 + setWidth(Math.round(anim.targets[0].width)) 40 + }, 41 + } 42 + ) 43 + }, []) 44 + 45 + return ( 46 + <box 47 + width={width} 48 + height={3} 49 + backgroundColor="#6a5acd" 50 + /> 51 + ) 52 + } 53 + ``` 54 + 55 + ### Solid 56 + 57 + ```tsx 58 + import { useTimeline } from "@opentui/solid" 59 + import { createSignal, onMount } from "solid-js" 60 + 61 + function AnimatedBox() { 62 + const [width, setWidth] = createSignal(0) 63 + 64 + const timeline = useTimeline({ 65 + duration: 2000, 66 + }) 67 + 68 + onMount(() => { 69 + timeline.add( 70 + { width: 0 }, 71 + { 72 + width: 50, 73 + duration: 2000, 74 + ease: "easeOutQuad", 75 + onUpdate: (anim) => { 76 + setWidth(Math.round(anim.targets[0].width)) 77 + }, 78 + } 79 + ) 80 + }) 81 + 82 + return ( 83 + <box 84 + width={width()} 85 + height={3} 86 + backgroundColor="#6a5acd" 87 + /> 88 + ) 89 + } 90 + ``` 91 + 92 + ### Core 93 + 94 + ```typescript 95 + import { createCliRenderer, Timeline, engine } from "@opentui/core" 96 + 97 + const renderer = await createCliRenderer() 98 + engine.attach(renderer) 99 + 100 + const timeline = new Timeline({ 101 + duration: 2000, 102 + autoplay: true, 103 + }) 104 + 105 + timeline.add( 106 + { x: 0 }, 107 + { 108 + x: 50, 109 + duration: 2000, 110 + ease: "easeOutQuad", 111 + onUpdate: (anim) => { 112 + box.setLeft(Math.round(anim.targets[0].x)) 113 + }, 114 + } 115 + ) 116 + 117 + engine.addTimeline(timeline) 118 + ``` 119 + 120 + ## Timeline Options 121 + 122 + ```typescript 123 + const timeline = useTimeline({ 124 + duration: 2000, // Total duration in ms 125 + loop: false, // Loop the timeline 126 + autoplay: true, // Start automatically 127 + onComplete: () => {}, // Called when timeline completes 128 + onPause: () => {}, // Called when timeline pauses 129 + }) 130 + ``` 131 + 132 + ## Timeline Methods 133 + 134 + ```typescript 135 + // Add animation 136 + timeline.add(target, properties, startTime?) 137 + 138 + // Control playback 139 + timeline.play() // Start/resume 140 + timeline.pause() // Pause 141 + timeline.restart() // Restart from beginning 142 + 143 + // State 144 + timeline.progress // Current progress (0-1) 145 + timeline.duration // Total duration 146 + ``` 147 + 148 + ## Animation Properties 149 + 150 + ```typescript 151 + timeline.add( 152 + { value: 0 }, // Target object with initial values 153 + { 154 + value: 100, // Final value 155 + duration: 1000, // Animation duration in ms 156 + ease: "linear", // Easing function 157 + delay: 0, // Delay before starting 158 + onUpdate: (anim) => { 159 + // Called each frame 160 + const current = anim.targets[0].value 161 + }, 162 + onComplete: () => { 163 + // Called when this animation completes 164 + }, 165 + }, 166 + 0 // Start time in timeline (optional) 167 + ) 168 + ``` 169 + 170 + ## Easing Functions 171 + 172 + Available easing functions: 173 + 174 + ### Linear 175 + 176 + | Name | Description | 177 + |------|-------------| 178 + | `linear` | Constant speed | 179 + 180 + ### Quad (Power of 2) 181 + 182 + | Name | Description | 183 + |------|-------------| 184 + | `easeInQuad` | Slow start | 185 + | `easeOutQuad` | Slow end | 186 + | `easeInOutQuad` | Slow start and end | 187 + 188 + ### Cubic (Power of 3) 189 + 190 + | Name | Description | 191 + |------|-------------| 192 + | `easeInCubic` | Slower start | 193 + | `easeOutCubic` | Slower end | 194 + | `easeInOutCubic` | Slower start and end | 195 + 196 + ### Quart (Power of 4) 197 + 198 + | Name | Description | 199 + |------|-------------| 200 + | `easeInQuart` | Even slower start | 201 + | `easeOutQuart` | Even slower end | 202 + | `easeInOutQuart` | Even slower start and end | 203 + 204 + ### Expo (Exponential) 205 + 206 + | Name | Description | 207 + |------|-------------| 208 + | `easeInExpo` | Exponential start | 209 + | `easeOutExpo` | Exponential end | 210 + | `easeInOutExpo` | Exponential start and end | 211 + 212 + ### Back (Overshoot) 213 + 214 + | Name | Description | 215 + |------|-------------| 216 + | `easeInBack` | Pull back, then forward | 217 + | `easeOutBack` | Overshoot, then settle | 218 + | `easeInOutBack` | Both | 219 + 220 + ### Elastic 221 + 222 + | Name | Description | 223 + |------|-------------| 224 + | `easeInElastic` | Elastic start | 225 + | `easeOutElastic` | Elastic end (bouncy) | 226 + | `easeInOutElastic` | Both | 227 + 228 + ### Bounce 229 + 230 + | Name | Description | 231 + |------|-------------| 232 + | `easeInBounce` | Bounce at start | 233 + | `easeOutBounce` | Bounce at end | 234 + | `easeInOutBounce` | Both | 235 + 236 + ## Patterns 237 + 238 + ### Progress Bar 239 + 240 + ```tsx 241 + function ProgressBar({ progress }: { progress: number }) { 242 + const [width, setWidth] = useState(0) 243 + const maxWidth = 50 244 + 245 + const timeline = useTimeline() 246 + 247 + useEffect(() => { 248 + timeline.add( 249 + { value: width }, 250 + { 251 + value: (progress / 100) * maxWidth, 252 + duration: 300, 253 + ease: "easeOutQuad", 254 + onUpdate: (anim) => { 255 + setWidth(Math.round(anim.targets[0].value)) 256 + }, 257 + } 258 + ) 259 + }, [progress]) 260 + 261 + return ( 262 + <box flexDirection="column" gap={1}> 263 + <text>Progress: {progress}%</text> 264 + <box width={maxWidth} height={1} backgroundColor="#333"> 265 + <box width={width} height={1} backgroundColor="#00FF00" /> 266 + </box> 267 + </box> 268 + ) 269 + } 270 + ``` 271 + 272 + ### Fade In 273 + 274 + ```tsx 275 + function FadeIn({ children }) { 276 + const [opacity, setOpacity] = useState(0) 277 + 278 + const timeline = useTimeline() 279 + 280 + useEffect(() => { 281 + timeline.add( 282 + { opacity: 0 }, 283 + { 284 + opacity: 1, 285 + duration: 500, 286 + ease: "easeOutQuad", 287 + onUpdate: (anim) => { 288 + setOpacity(anim.targets[0].opacity) 289 + }, 290 + } 291 + ) 292 + }, []) 293 + 294 + return ( 295 + <box style={{ opacity }}> 296 + {children} 297 + </box> 298 + ) 299 + } 300 + ``` 301 + 302 + ### Looping Animation 303 + 304 + ```tsx 305 + function Spinner() { 306 + const [frame, setFrame] = useState(0) 307 + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] 308 + 309 + useEffect(() => { 310 + const interval = setInterval(() => { 311 + setFrame(f => (f + 1) % frames.length) 312 + }, 80) 313 + 314 + return () => clearInterval(interval) 315 + }, []) 316 + 317 + return <text>{frames[frame]} Loading...</text> 318 + } 319 + ``` 320 + 321 + ### Staggered Animation 322 + 323 + ```tsx 324 + function StaggeredList({ items }) { 325 + const [visibleCount, setVisibleCount] = useState(0) 326 + 327 + useEffect(() => { 328 + let count = 0 329 + const interval = setInterval(() => { 330 + count++ 331 + setVisibleCount(count) 332 + if (count >= items.length) { 333 + clearInterval(interval) 334 + } 335 + }, 100) 336 + 337 + return () => clearInterval(interval) 338 + }, [items.length]) 339 + 340 + return ( 341 + <box flexDirection="column"> 342 + {items.slice(0, visibleCount).map((item, i) => ( 343 + <text key={i}>{item}</text> 344 + ))} 345 + </box> 346 + ) 347 + } 348 + ``` 349 + 350 + ### Slide In 351 + 352 + ```tsx 353 + function SlideIn({ children, from = "left" }) { 354 + const [offset, setOffset] = useState(from === "left" ? -20 : 20) 355 + 356 + const timeline = useTimeline() 357 + 358 + useEffect(() => { 359 + timeline.add( 360 + { offset: from === "left" ? -20 : 20 }, 361 + { 362 + offset: 0, 363 + duration: 300, 364 + ease: "easeOutCubic", 365 + onUpdate: (anim) => { 366 + setOffset(Math.round(anim.targets[0].offset)) 367 + }, 368 + } 369 + ) 370 + }, []) 371 + 372 + return ( 373 + <box position="relative" left={offset}> 374 + {children} 375 + </box> 376 + ) 377 + } 378 + ``` 379 + 380 + ## Performance Tips 381 + 382 + ### Batch Updates 383 + 384 + Timeline automatically batches updates within the render loop. 385 + 386 + ### Use Integer Values 387 + 388 + Round animated values for character-based positioning: 389 + 390 + ```typescript 391 + onUpdate: (anim) => { 392 + setX(Math.round(anim.targets[0].x)) 393 + } 394 + ``` 395 + 396 + ### Clean Up Timelines 397 + 398 + Hooks automatically clean up, but for core: 399 + 400 + ```typescript 401 + // When done with timeline 402 + engine.removeTimeline(timeline) 403 + ``` 404 + 405 + ## Gotchas 406 + 407 + ### Terminal Refresh Rate 408 + 409 + Terminal UIs typically refresh at 60 FPS max. Very fast animations may appear choppy. 410 + 411 + ### Character Grid 412 + 413 + Animations are constrained to character cells. Sub-pixel positioning isn't possible. 414 + 415 + ### Cleanup in Effects 416 + 417 + Always clean up intervals and timelines: 418 + 419 + ```tsx 420 + useEffect(() => { 421 + const interval = setInterval(...) 422 + return () => clearInterval(interval) 423 + }, []) 424 + ``` 425 + 426 + ## See Also 427 + 428 + - [React API](../react/api.md) - `useTimeline` hook reference 429 + - [Solid API](../solid/api.md) - `useTimeline` hook reference 430 + - [Core API](../core/api.md) - `AnimationEngine` and `Timeline` classes 431 + - [Layout Patterns](../layout/patterns.md) - Animated positioning and transitions
+144
skills/opentui/references/components/REFERENCE.md
··· 1 + # OpenTUI Components 2 + 3 + Reference for all OpenTUI components, organized by category. Components are available in all three frameworks (Core, React, Solid) with slight API differences. 4 + 5 + ## When to Use 6 + 7 + Use this reference when you need to find the right component category or compare naming across Core, React, and Solid. 8 + 9 + ## Component Categories 10 + 11 + | Category | Components | File | 12 + |----------|------------|------| 13 + | Text & Display | text, ascii-font, styled text | [text-display.md](./text-display.md) | 14 + | Containers | box, scrollbox, borders | [containers.md](./containers.md) | 15 + | Inputs | input, textarea, select, tab-select | [inputs.md](./inputs.md) | 16 + | Code & Diff | code, line-number, diff, markdown, text-table | [code-diff.md](./code-diff.md) | 17 + 18 + ## Component Chooser 19 + 20 + ``` 21 + Need a component? 22 + ├─ Styled text or ASCII art -> text-display.md 23 + ├─ Containers, borders, scrolling -> containers.md 24 + ├─ Forms or input controls -> inputs.md 25 + └─ Code blocks, diffs, line numbers, markdown -> code-diff.md 26 + ``` 27 + 28 + ## Component Naming 29 + 30 + Components have different names across frameworks: 31 + 32 + | Concept | Core (Class) | React (JSX) | Solid (JSX) | 33 + |---------|--------------|-------------|-------------| 34 + | Text | `TextRenderable` | `<text>` | `<text>` | 35 + | Box | `BoxRenderable` | `<box>` | `<box>` | 36 + | ScrollBox | `ScrollBoxRenderable` | `<scrollbox>` | `<scrollbox>` | 37 + | Input | `InputRenderable` | `<input>` | `<input>` | 38 + | Textarea | `TextareaRenderable` | `<textarea>` | `<textarea>` | 39 + | Select | `SelectRenderable` | `<select>` | `<select>` | 40 + | Tab Select | `TabSelectRenderable` | `<tab-select>` | `<tab_select>` | 41 + | ASCII Font | `ASCIIFontRenderable` | `<ascii-font>` | `<ascii_font>` | 42 + | Code | `CodeRenderable` | `<code>` | `<code>` | 43 + | Line Number | `LineNumberRenderable` | `<line-number>` | `<line_number>` | 44 + | Diff | `DiffRenderable` | `<diff>` | `<diff>` | 45 + | Markdown | `MarkdownRenderable` | `<markdown>` | `<markdown>` | 46 + | TextTable | `TextTableRenderable` | N/A (Core only) | N/A (Core only) | 47 + 48 + **Note**: Solid uses underscores (`tab_select`) while React uses hyphens (`tab-select`). `TextTableRenderable` is used internally by `MarkdownRenderable` for table rendering and is also available as a standalone Core component. 49 + 50 + ## Common Properties 51 + 52 + All components share these layout properties (see [Layout](../layout/REFERENCE.md)): 53 + 54 + ```tsx 55 + // Positioning 56 + position="relative" | "absolute" 57 + left, top, right, bottom 58 + 59 + // Dimensions 60 + width, height 61 + minWidth, maxWidth, minHeight, maxHeight 62 + 63 + // Flexbox 64 + flexDirection, flexGrow, flexShrink, flexBasis 65 + justifyContent, alignItems, alignSelf 66 + flexWrap, gap 67 + 68 + // Spacing 69 + padding, paddingTop, paddingRight, paddingBottom, paddingLeft 70 + paddingX, paddingY // Axis shorthand (horizontal/vertical) 71 + margin, marginTop, marginRight, marginBottom, marginLeft 72 + marginX, marginY // Axis shorthand (horizontal/vertical) 73 + 74 + // Display 75 + display="flex" | "none" 76 + overflow="visible" | "hidden" | "scroll" 77 + zIndex 78 + ``` 79 + 80 + ## Quick Examples 81 + 82 + ### Core (Imperative) 83 + 84 + ```typescript 85 + import { createCliRenderer, TextRenderable, BoxRenderable } from "@opentui/core" 86 + 87 + const renderer = await createCliRenderer() 88 + 89 + const box = new BoxRenderable(renderer, { 90 + id: "container", 91 + border: true, 92 + padding: 2, 93 + }) 94 + 95 + const text = new TextRenderable(renderer, { 96 + id: "greeting", 97 + content: "Hello!", 98 + fg: "#00FF00", 99 + }) 100 + 101 + box.add(text) 102 + renderer.root.add(box) 103 + ``` 104 + 105 + ### React 106 + 107 + ```tsx 108 + import { createCliRenderer } from "@opentui/core" 109 + import { createRoot } from "@opentui/react" 110 + 111 + function App() { 112 + return ( 113 + <box border padding={2}> 114 + <text fg="#00FF00">Hello!</text> 115 + </box> 116 + ) 117 + } 118 + 119 + const renderer = await createCliRenderer() 120 + createRoot(renderer).render(<App />) 121 + ``` 122 + 123 + ### Solid 124 + 125 + ```tsx 126 + import { render } from "@opentui/solid" 127 + 128 + function App() { 129 + return ( 130 + <box border padding={2}> 131 + <text fg="#00FF00">Hello!</text> 132 + </box> 133 + ) 134 + } 135 + 136 + render(() => <App />) 137 + ``` 138 + 139 + ## See Also 140 + 141 + - [Core API](../core/api.md) - Imperative component classes 142 + - [React API](../react/api.md) - React component props 143 + - [Solid API](../solid/api.md) - Solid component props 144 + - [Layout](../layout/REFERENCE.md) - Layout system details
+672
skills/opentui/references/components/code-diff.md
··· 1 + # Code & Diff Components 2 + 3 + Components for displaying code with syntax highlighting and diffs in OpenTUI. 4 + 5 + ## Code Component 6 + 7 + Display syntax-highlighted code blocks. 8 + 9 + ### Basic Usage 10 + 11 + ```tsx 12 + // React 13 + <code 14 + code={`function hello() { 15 + console.log("Hello, World!"); 16 + }`} 17 + language="typescript" 18 + /> 19 + 20 + // Solid 21 + <code 22 + code={sourceCode} 23 + language="javascript" 24 + /> 25 + 26 + // Core 27 + const codeBlock = new CodeRenderable(renderer, { 28 + id: "code", 29 + code: sourceCode, 30 + language: "typescript", 31 + }) 32 + ``` 33 + 34 + ### Supported Languages 35 + 36 + OpenTUI uses Tree-sitter for syntax highlighting. Common languages: 37 + - `typescript`, `javascript` 38 + - `python` 39 + - `rust` 40 + - `go` 41 + - `json` 42 + - `html`, `css` 43 + - `markdown` 44 + - `bash`, `shell` 45 + 46 + ### Styling 47 + 48 + ```tsx 49 + <code 50 + code={sourceCode} 51 + language="typescript" 52 + backgroundColor="#1a1a2e" 53 + showLineNumbers 54 + /> 55 + ``` 56 + 57 + ### onHighlight Callback 58 + 59 + Intercept and modify syntax highlights before rendering: 60 + 61 + ```tsx 62 + // Core 63 + const codeBlock = new CodeRenderable(renderer, { 64 + id: "code", 65 + code: sourceCode, 66 + language: "typescript", 67 + onHighlight: (highlights, context) => { 68 + // Add custom highlights 69 + highlights.push([10, 20, "custom.error", {}]) 70 + return highlights 71 + }, 72 + }) 73 + 74 + // React/Solid 75 + <code 76 + code={sourceCode} 77 + language="typescript" 78 + onHighlight={(highlights, context) => { 79 + // context: { content, filetype, syntaxStyle } 80 + // Modify and return highlights array 81 + return highlights.filter(h => h[2] !== "comment") 82 + }} 83 + /> 84 + ``` 85 + 86 + **Callback signature:** 87 + - `highlights: SimpleHighlight[]` - Array of `[start, end, scope, metadata]` 88 + - `context: { content, filetype, syntaxStyle }` - Highlighting context 89 + - Return modified highlights array or `undefined` to use original 90 + 91 + Supports async callbacks for fetching additional highlight data. 92 + 93 + ### onChunks Callback 94 + 95 + Post-process rendered text chunks after syntax highlighting. Runs after `onHighlight` and receives fully resolved chunks: 96 + 97 + ```tsx 98 + // Core 99 + const codeBlock = new CodeRenderable(renderer, { 100 + id: "code", 101 + code: sourceCode, 102 + language: "typescript", 103 + onChunks: (chunks, context) => { 104 + // Transform chunks (e.g., add link detection) 105 + return chunks 106 + }, 107 + }) 108 + 109 + // React/Solid 110 + <code 111 + code={sourceCode} 112 + language="typescript" 113 + onChunks={(chunks, context) => { 114 + // context: { content, filetype, syntaxStyle, highlights } 115 + return chunks 116 + }} 117 + /> 118 + ``` 119 + 120 + ### Link Detection Utility 121 + 122 + Auto-detect URLs in code and add clickable hyperlinks: 123 + 124 + ```typescript 125 + import { detectLinks } from "@opentui/core" 126 + 127 + <code 128 + code={sourceCode} 129 + language="typescript" 130 + onChunks={(chunks, context) => detectLinks(chunks, context)} 131 + /> 132 + ``` 133 + 134 + `detectLinks` examines Tree-sitter highlights to find URL tokens and sets `chunk.link` on matching chunks. Supports async usage. 135 + 136 + ## TextTable Component 137 + 138 + Render data tables with borders, word wrapping, and selection support. 139 + 140 + ### Basic Usage 141 + 142 + ```typescript 143 + // Core 144 + import { TextTableRenderable, type TextTableContent } from "@opentui/core" 145 + 146 + const content: TextTableContent = [ 147 + [[ { text: "Name" } ], [ { text: "Age" } ], [ { text: "Role" } ]], 148 + [[ { text: "Alice" } ], [ { text: "30" } ], [ { text: "Engineer" } ]], 149 + [[ { text: "Bob" } ], [ { text: "25" } ], [ { text: "Designer" } ]], 150 + ] 151 + 152 + const table = new TextTableRenderable(renderer, { 153 + id: "table", 154 + content, 155 + wrapMode: "word", // "none" | "char" | "word" 156 + columnWidthMode: "content", // "content" | "fill" 157 + cellPadding: 0, 158 + border: true, 159 + outerBorder: true, 160 + borderStyle: "single", // single | double | rounded | bold 161 + selectable: true, // Allow text selection 162 + columnFitter: "balanced", // "proportional" | "balanced" 163 + }) 164 + ``` 165 + 166 + ### Options 167 + 168 + | Option | Type | Default | Description | 169 + |--------|------|---------|-------------| 170 + | `content` | `TextTableContent` | - | 2D array of cell content | 171 + | `wrapMode` | `"none" \| "char" \| "word"` | `"none"` | Text wrapping in cells | 172 + | `columnWidthMode` | `"content" \| "fill"` | `"content"` | Column sizing strategy | 173 + | `cellPadding` | `number` | `0` | Padding inside cells | 174 + | `border` | `boolean` | `true` | Show inner borders | 175 + | `outerBorder` | `boolean` | `true` | Show outer borders | 176 + | `borderStyle` | `string` | `"single"` | Border style | 177 + | `borderColor` | `string \| RGBA` | - | Border color | 178 + | `selectable` | `boolean` | `false` | Allow text selection | 179 + | `columnFitter` | `"proportional" \| "balanced"` | `"proportional"` | Column width distribution | 180 + 181 + ### Cell Content Format 182 + 183 + Each cell is an array of styled text chunks: 184 + 185 + ```typescript 186 + type TextTableCellContent = { text: string; fg?: RGBA; bg?: RGBA }[] 187 + type TextTableContent = TextTableCellContent[][] // rows -> cells -> chunks 188 + ``` 189 + 190 + ### Selection 191 + 192 + ```typescript 193 + table.getSelectedText() // Get selected text 194 + table.hasSelection() // Check if text is selected 195 + ``` 196 + 197 + Columnar selection is supported: dragging vertically within a single column selects only that column's content. 198 + 199 + ## Line Number Component 200 + 201 + Code display with line numbers, highlighting, and diagnostics. 202 + 203 + ### Basic Usage 204 + 205 + ```tsx 206 + // React 207 + <line-number 208 + code={sourceCode} 209 + language="typescript" 210 + /> 211 + 212 + // Solid (note underscore) 213 + <line_number 214 + code={sourceCode} 215 + language="typescript" 216 + /> 217 + 218 + // Core 219 + const codeView = new LineNumberRenderable(renderer, { 220 + id: "code-view", 221 + code: sourceCode, 222 + language: "typescript", 223 + }) 224 + ``` 225 + 226 + ### Line Number Options 227 + 228 + ```tsx 229 + // React 230 + <line-number 231 + code={sourceCode} 232 + language="typescript" 233 + startLine={1} // Starting line number 234 + showLineNumbers={true} // Display line numbers 235 + /> 236 + 237 + // Solid 238 + <line_number 239 + code={sourceCode} 240 + language="typescript" 241 + startLine={1} 242 + showLineNumbers={true} 243 + /> 244 + ``` 245 + 246 + ### Line Highlighting 247 + 248 + Highlight specific lines: 249 + 250 + ```tsx 251 + // React 252 + <line-number 253 + code={sourceCode} 254 + language="typescript" 255 + highlightedLines={[5, 10, 15]} // Highlight these lines 256 + /> 257 + 258 + // Solid 259 + <line_number 260 + code={sourceCode} 261 + language="typescript" 262 + highlightedLines={[5, 10, 15]} 263 + /> 264 + ``` 265 + 266 + ### Diagnostics 267 + 268 + Show errors, warnings, and info on specific lines: 269 + 270 + ```tsx 271 + // React 272 + <line-number 273 + code={sourceCode} 274 + language="typescript" 275 + diagnostics={[ 276 + { line: 3, severity: "error", message: "Unexpected token" }, 277 + { line: 7, severity: "warning", message: "Unused variable" }, 278 + { line: 12, severity: "info", message: "Consider using const" }, 279 + ]} 280 + /> 281 + 282 + // Solid 283 + <line_number 284 + code={sourceCode} 285 + language="typescript" 286 + diagnostics={[ 287 + { line: 3, severity: "error", message: "Unexpected token" }, 288 + ]} 289 + /> 290 + ``` 291 + 292 + **Diagnostic severity levels:** 293 + - `error` - Red indicator 294 + - `warning` - Yellow indicator 295 + - `info` - Blue indicator 296 + - `hint` - Gray indicator 297 + 298 + ### Diff Highlighting 299 + 300 + Show added/removed lines: 301 + 302 + ```tsx 303 + <line-number 304 + code={sourceCode} 305 + language="typescript" 306 + addedLines={[5, 6, 7]} // Green background 307 + removedLines={[10, 11]} // Red background 308 + /> 309 + ``` 310 + 311 + ## Diff Component 312 + 313 + Unified or split diff viewer with syntax highlighting. 314 + 315 + ### Basic Usage 316 + 317 + ```tsx 318 + // React 319 + <diff 320 + oldCode={originalCode} 321 + newCode={modifiedCode} 322 + language="typescript" 323 + /> 324 + 325 + // Solid 326 + <diff 327 + oldCode={originalCode} 328 + newCode={modifiedCode} 329 + language="typescript" 330 + /> 331 + 332 + // Core 333 + const diffView = new DiffRenderable(renderer, { 334 + id: "diff", 335 + oldCode: originalCode, 336 + newCode: modifiedCode, 337 + language: "typescript", 338 + }) 339 + ``` 340 + 341 + ### Display Modes 342 + 343 + ```tsx 344 + // Unified diff (default) 345 + <diff 346 + oldCode={old} 347 + newCode={new} 348 + mode="unified" 349 + /> 350 + 351 + // Split/side-by-side diff 352 + <diff 353 + oldCode={old} 354 + newCode={new} 355 + mode="split" 356 + /> 357 + ``` 358 + 359 + ### Synchronized Scrolling (Split View) 360 + 361 + In split view, enable synchronized scrolling between left and right panes: 362 + 363 + ```tsx 364 + // React/Solid 365 + <diff 366 + oldCode={old} 367 + newCode={new} 368 + mode="split" 369 + syncScroll // Scrolling one pane syncs the other 370 + /> 371 + 372 + // Core 373 + const diffView = new DiffRenderable(renderer, { 374 + id: "diff", 375 + diff: unifiedDiff, 376 + view: "split", 377 + syncScroll: true, 378 + }) 379 + 380 + // Toggle at runtime 381 + diffView.syncScroll = true 382 + diffView.syncScroll = false 383 + ``` 384 + 385 + ### Options 386 + 387 + ```tsx 388 + <diff 389 + oldCode={originalCode} 390 + newCode={modifiedCode} 391 + language="typescript" 392 + mode="unified" 393 + showLineNumbers 394 + context={3} // Lines of context around changes 395 + /> 396 + ``` 397 + 398 + ### Styling 399 + 400 + ```tsx 401 + <diff 402 + oldCode={old} 403 + newCode={new} 404 + addedLineColor="#2d4f2d" // Background for added lines 405 + removedLineColor="#4f2d2d" // Background for removed lines 406 + unchangedLineColor="transparent" 407 + /> 408 + ``` 409 + 410 + ### Line Highlighting API (Core) 411 + 412 + Programmatically highlight specific lines in a diff: 413 + 414 + ```typescript 415 + // Set a single line's color 416 + diffView.setLineColor(5, "#2d4f2d") 417 + diffView.setLineColor(5, { gutter: "#333", content: "#2d4f2d" }) 418 + 419 + // Clear a single line's color 420 + diffView.clearLineColor(5) 421 + 422 + // Set multiple lines at once 423 + diffView.setLineColors(new Map([ 424 + [1, "#2d4f2d"], 425 + [2, "#4f2d2d"], 426 + ])) 427 + 428 + // Highlight a range 429 + diffView.highlightLines(10, 20, "#2d4f2d") 430 + diffView.clearHighlightLines(10, 20) 431 + 432 + // Clear all line colors 433 + diffView.clearAllLineColors() 434 + ``` 435 + 436 + The `LineNumberRenderable` also supports programmatic highlighting: 437 + 438 + ```typescript 439 + lineNumberView.highlightLines(5, 10, "#2d4f2d") 440 + lineNumberView.clearHighlightLines(5, 10) 441 + ``` 442 + ``` 443 + 444 + ## Markdown Component 445 + 446 + Render markdown content with syntax highlighting for code blocks. 447 + 448 + ### Basic Usage 449 + 450 + ```tsx 451 + // React 452 + <markdown 453 + content={markdownText} 454 + syntaxStyle={mySyntaxStyle} 455 + /> 456 + 457 + // Solid 458 + <markdown 459 + content={markdownText} 460 + syntaxStyle={mySyntaxStyle} 461 + /> 462 + 463 + // Core 464 + import { MarkdownRenderable } from "@opentui/core" 465 + 466 + const md = new MarkdownRenderable(renderer, { 467 + id: "markdown", 468 + content: "# Hello\n\nThis is **markdown**.", 469 + syntaxStyle: mySyntaxStyle, 470 + }) 471 + ``` 472 + 473 + ### Options 474 + 475 + ```tsx 476 + <markdown 477 + content={markdownText} 478 + syntaxStyle={syntaxStyle} 479 + treeSitterClient={client} // Optional: custom tree-sitter client 480 + conceal={true} // Hide markdown syntax characters 481 + streaming={true} // Enable streaming mode for incremental updates 482 + tableOptions={{ // Customize markdown table rendering 483 + widthMode: "full", // "content" | "full" 484 + wrapMode: "word", // "none" | "char" | "word" 485 + cellPadding: 0, 486 + borders: true, 487 + outerBorder: true, 488 + borderStyle: "single", 489 + borderColor: "#555", 490 + selectable: true, // Tables are selectable by default 491 + }} 492 + /> 493 + ``` 494 + 495 + ### Custom Node Rendering 496 + 497 + ```tsx 498 + // Core 499 + const md = new MarkdownRenderable(renderer, { 500 + id: "markdown", 501 + content: "# Custom Heading", 502 + syntaxStyle, 503 + renderNode: (node, ctx, defaultRender) => { 504 + if (node.type === "heading") { 505 + // Return custom renderable for headings 506 + return new TextRenderable(ctx, { 507 + content: `>> ${node.content} <<`, 508 + }) 509 + } 510 + return null // Use default rendering 511 + }, 512 + }) 513 + ``` 514 + 515 + ### Streaming Mode 516 + 517 + For real-time content like LLM output: 518 + 519 + ```tsx 520 + const [content, setContent] = useState("") 521 + 522 + // Append text as it arrives 523 + useEffect(() => { 524 + llmStream.on("token", (token) => { 525 + setContent(c => c + token) 526 + }) 527 + }, []) 528 + 529 + <markdown 530 + content={content} 531 + syntaxStyle={syntaxStyle} 532 + streaming={true} // Optimizes for incremental updates 533 + /> 534 + ``` 535 + 536 + ## Use Cases 537 + 538 + ### Code Editor 539 + 540 + ```tsx 541 + function CodeEditor() { 542 + const [code, setCode] = useState(`function hello() { 543 + console.log("Hello!"); 544 + }`) 545 + 546 + return ( 547 + <box flexDirection="column" height="100%"> 548 + <box height={1}> 549 + <text>editor.ts</text> 550 + </box> 551 + <textarea 552 + value={code} 553 + onChange={setCode} 554 + language="typescript" 555 + showLineNumbers 556 + flexGrow={1} 557 + focused 558 + /> 559 + </box> 560 + ) 561 + } 562 + ``` 563 + 564 + ### Code Review 565 + 566 + ```tsx 567 + function CodeReview({ oldCode, newCode }) { 568 + return ( 569 + <box flexDirection="column" height="100%"> 570 + <box height={1} backgroundColor="#333"> 571 + <text>Changes in src/utils.ts</text> 572 + </box> 573 + <diff 574 + oldCode={oldCode} 575 + newCode={newCode} 576 + language="typescript" 577 + mode="split" 578 + showLineNumbers 579 + /> 580 + </box> 581 + ) 582 + } 583 + ``` 584 + 585 + ### Syntax-Highlighted Preview 586 + 587 + ```tsx 588 + function MarkdownPreview({ content }) { 589 + // Extract code blocks from markdown 590 + const codeBlocks = extractCodeBlocks(content) 591 + 592 + return ( 593 + <scrollbox height={20}> 594 + {codeBlocks.map((block, i) => ( 595 + <box key={i} marginBottom={1}> 596 + <code 597 + code={block.code} 598 + language={block.language} 599 + /> 600 + </box> 601 + ))} 602 + </scrollbox> 603 + ) 604 + } 605 + ``` 606 + 607 + ### Error Display 608 + 609 + ```tsx 610 + function ErrorView({ errors, code }) { 611 + const diagnostics = errors.map(err => ({ 612 + line: err.line, 613 + severity: "error", 614 + message: err.message, 615 + })) 616 + 617 + return ( 618 + <line-number 619 + code={code} 620 + language="typescript" 621 + diagnostics={diagnostics} 622 + highlightedLines={errors.map(e => e.line)} 623 + /> 624 + ) 625 + } 626 + ``` 627 + 628 + ## Gotchas 629 + 630 + ### Solid Uses Underscores 631 + 632 + ```tsx 633 + // React 634 + <line-number /> 635 + 636 + // Solid 637 + <line_number /> 638 + ``` 639 + 640 + ### Language Required for Highlighting 641 + 642 + ```tsx 643 + // No highlighting (plain text) 644 + <code code={text} /> 645 + 646 + // With highlighting 647 + <code code={text} language="typescript" /> 648 + ``` 649 + 650 + ### Large Files 651 + 652 + For very large files, consider: 653 + - Pagination or virtual scrolling 654 + - Loading only visible portion 655 + - Using `scrollbox` wrapper 656 + 657 + ```tsx 658 + <scrollbox height={30}> 659 + <line-number 660 + code={largeFile} 661 + language="typescript" 662 + /> 663 + </scrollbox> 664 + ``` 665 + 666 + ### Tree-sitter Loading 667 + 668 + Syntax highlighting requires Tree-sitter grammars. If highlighting isn't working: 669 + 670 + 1. Check the language is supported 671 + 2. Verify grammars are installed 672 + 3. Check `OTUI_TREE_SITTER_WORKER_PATH` if using custom path
+417
skills/opentui/references/components/containers.md
··· 1 + # Container Components 2 + 3 + Components for grouping and organizing content in OpenTUI. 4 + 5 + ## Box Component 6 + 7 + The primary container component with borders, backgrounds, and layout capabilities. 8 + 9 + ### Basic Usage 10 + 11 + ```tsx 12 + // React/Solid 13 + <box> 14 + <text>Content inside box</text> 15 + </box> 16 + 17 + // Core 18 + const box = new BoxRenderable(renderer, { 19 + id: "container", 20 + }) 21 + box.add(child) 22 + ``` 23 + 24 + ### Borders 25 + 26 + ```tsx 27 + <box border> 28 + Simple border 29 + </box> 30 + 31 + <box 32 + border 33 + borderStyle="single" // single | double | rounded | bold | none 34 + borderColor="#FFFFFF" 35 + > 36 + Styled border 37 + </box> 38 + 39 + // Individual borders 40 + <box 41 + borderTop 42 + borderBottom 43 + borderLeft={false} 44 + borderRight={false} 45 + > 46 + Top and bottom only 47 + </box> 48 + ``` 49 + 50 + **Border Styles:** 51 + 52 + | Style | Appearance | 53 + |-------|------------| 54 + | `single` | `┌─┐│ │└─┘` | 55 + | `double` | `╔═╗║ ║╚═╝` | 56 + | `rounded` | `╭─╮│ │╰─╯` | 57 + | `bold` | `┏━┓┃ ┃┗━┛` | 58 + 59 + ### Title 60 + 61 + ```tsx 62 + <box 63 + border 64 + title="Settings" 65 + titleAlignment="center" // left | center | right 66 + > 67 + Panel content 68 + </box> 69 + ``` 70 + 71 + ### Background 72 + 73 + ```tsx 74 + <box backgroundColor="#1a1a2e"> 75 + Dark background 76 + </box> 77 + 78 + <box backgroundColor="transparent"> 79 + No background 80 + </box> 81 + ``` 82 + 83 + ### Layout 84 + 85 + Boxes are flex containers by default: 86 + 87 + ```tsx 88 + <box 89 + flexDirection="row" // row | column | row-reverse | column-reverse 90 + justifyContent="center" // flex-start | flex-end | center | space-between | space-around 91 + alignItems="center" // flex-start | flex-end | center | stretch | baseline 92 + gap={2} // Space between children 93 + > 94 + <text>Item 1</text> 95 + <text>Item 2</text> 96 + </box> 97 + ``` 98 + 99 + ### Spacing 100 + 101 + ```tsx 102 + <box 103 + padding={2} // All sides 104 + paddingTop={1} 105 + paddingRight={2} 106 + paddingBottom={1} 107 + paddingLeft={2} 108 + paddingX={2} // Horizontal (left + right) 109 + paddingY={1} // Vertical (top + bottom) 110 + margin={1} 111 + marginTop={1} 112 + marginX={2} // Horizontal (left + right) 113 + marginY={1} // Vertical (top + bottom) 114 + > 115 + Spaced content 116 + </box> 117 + ``` 118 + 119 + ### Dimensions 120 + 121 + ```tsx 122 + <box 123 + width={40} // Fixed width 124 + height={10} // Fixed height 125 + width="50%" // Percentage of parent 126 + minWidth={20} // Minimum width 127 + maxWidth={80} // Maximum width 128 + flexGrow={1} // Grow to fill space 129 + > 130 + Sized box 131 + </box> 132 + ``` 133 + 134 + ### Mouse Events 135 + 136 + ```tsx 137 + <box 138 + onMouseDown={(event) => { 139 + console.log("Clicked at:", event.x, event.y) 140 + }} 141 + onMouseUp={(event) => {}} 142 + onMouseMove={(event) => {}} 143 + > 144 + Clickable box 145 + </box> 146 + ``` 147 + 148 + ### Focusable Boxes 149 + 150 + By default, Box elements are not focusable. Set the `focusable` prop to enable focus behavior: 151 + 152 + ```tsx 153 + // Make a box focusable - it can receive focus via mouse click 154 + <box focusable border> 155 + <text>Click to focus</text> 156 + </box> 157 + 158 + // Controlled focus state 159 + const [focused, setFocused] = useState(false) 160 + 161 + <box 162 + focusable 163 + focused={focused} 164 + border 165 + borderColor={focused ? "#00ff00" : "#888"} 166 + > 167 + <text>{focused ? "Focused!" : "Not focused"}</text> 168 + </box> 169 + ``` 170 + 171 + When a focusable Box is clicked, focus bubbles up from the click target to the nearest focusable parent. Use `event.preventDefault()` in `onMouseDown` to prevent auto-focus. 172 + 173 + ## ScrollBox Component 174 + 175 + A scrollable container for content that exceeds the viewport. 176 + 177 + ### Basic Usage 178 + 179 + ```tsx 180 + // React 181 + <scrollbox height={10}> 182 + {items.map((item, i) => ( 183 + <text key={i}>{item}</text> 184 + ))} 185 + </scrollbox> 186 + 187 + // Solid 188 + <scrollbox height={10}> 189 + <For each={items()}> 190 + {(item) => <text>{item}</text>} 191 + </For> 192 + </scrollbox> 193 + 194 + // Core 195 + const scrollbox = new ScrollBoxRenderable(renderer, { 196 + id: "list", 197 + height: 10, 198 + }) 199 + items.forEach(item => { 200 + scrollbox.add(new TextRenderable(renderer, { content: item })) 201 + }) 202 + ``` 203 + 204 + ### Focus for Keyboard Scrolling 205 + 206 + ```tsx 207 + <scrollbox focused height={20}> 208 + {/* Use arrow keys to scroll */} 209 + </scrollbox> 210 + ``` 211 + 212 + ### Scrollbar Styling 213 + 214 + ```tsx 215 + // React 216 + <scrollbox 217 + style={{ 218 + rootOptions: { 219 + backgroundColor: "#24283b", 220 + }, 221 + wrapperOptions: { 222 + backgroundColor: "#1f2335", 223 + }, 224 + viewportOptions: { 225 + backgroundColor: "#1a1b26", 226 + }, 227 + contentOptions: { 228 + backgroundColor: "#16161e", 229 + }, 230 + scrollbarOptions: { 231 + showArrows: true, 232 + trackOptions: { 233 + foregroundColor: "#7aa2f7", 234 + backgroundColor: "#414868", 235 + }, 236 + }, 237 + }} 238 + > 239 + {content} 240 + </scrollbox> 241 + ``` 242 + 243 + ### Scroll Position (Core) 244 + 245 + ```typescript 246 + const scrollbox = new ScrollBoxRenderable(renderer, { 247 + id: "list", 248 + height: 20, 249 + }) 250 + 251 + // Scroll programmatically 252 + scrollbox.scrollTo(0) // Scroll to top 253 + scrollbox.scrollTo(100) // Scroll to position 254 + scrollbox.scrollBy(10) // Scroll relative 255 + scrollbox.scrollToBottom() // Scroll to end 256 + 257 + // Scroll a child into view (nearest alignment) 258 + scrollbox.scrollChildIntoView("child-id") // Searches descendants by ID 259 + ``` 260 + 261 + `scrollChildIntoView(childId)` scrolls the minimum amount needed to make the identified descendant visible. It mirrors `Element.scrollIntoView({ block: "nearest" })` from the CSSOM View spec. Works with nested descendants and handles both horizontal and vertical scrolling. 262 + 263 + ## Composition Patterns 264 + 265 + ### Card Component 266 + 267 + ```tsx 268 + function Card({ title, children }) { 269 + return ( 270 + <box 271 + border 272 + borderStyle="rounded" 273 + padding={2} 274 + marginBottom={1} 275 + > 276 + {title && ( 277 + <text fg="#00FFFF" bold> 278 + {title} 279 + </text> 280 + )} 281 + <box marginTop={title ? 1 : 0}> 282 + {children} 283 + </box> 284 + </box> 285 + ) 286 + } 287 + ``` 288 + 289 + ### Panel Component 290 + 291 + ```tsx 292 + function Panel({ title, children, width = 40 }) { 293 + return ( 294 + <box 295 + border 296 + borderStyle="double" 297 + width={width} 298 + backgroundColor="#1a1a2e" 299 + > 300 + {title && ( 301 + <box 302 + borderBottom 303 + padding={1} 304 + backgroundColor="#2a2a4e" 305 + > 306 + <text bold>{title}</text> 307 + </box> 308 + )} 309 + <box padding={2}> 310 + {children} 311 + </box> 312 + </box> 313 + ) 314 + } 315 + ``` 316 + 317 + ### List Container 318 + 319 + ```tsx 320 + function List({ items, renderItem }) { 321 + return ( 322 + <scrollbox height={15} focused> 323 + {items.map((item, i) => ( 324 + <box 325 + key={i} 326 + padding={1} 327 + backgroundColor={i % 2 === 0 ? "#222" : "#333"} 328 + > 329 + {renderItem(item, i)} 330 + </box> 331 + ))} 332 + </scrollbox> 333 + ) 334 + } 335 + ``` 336 + 337 + ## Nesting Containers 338 + 339 + ```tsx 340 + <box flexDirection="column" height="100%"> 341 + {/* Header */} 342 + <box height={3} border> 343 + <text>Header</text> 344 + </box> 345 + 346 + {/* Main area with sidebar */} 347 + <box flexDirection="row" flexGrow={1}> 348 + <box width={20} border> 349 + <text>Sidebar</text> 350 + </box> 351 + <box flexGrow={1}> 352 + <scrollbox height="100%"> 353 + {/* Scrollable content */} 354 + </scrollbox> 355 + </box> 356 + </box> 357 + 358 + {/* Footer */} 359 + <box height={1}> 360 + <text>Footer</text> 361 + </box> 362 + </box> 363 + ``` 364 + 365 + ## Gotchas 366 + 367 + ### Percentage Dimensions Need Parent Size 368 + 369 + ```tsx 370 + // WRONG - parent has no explicit size 371 + <box> 372 + <box width="50%">Won't work</box> 373 + </box> 374 + 375 + // CORRECT 376 + <box width="100%"> 377 + <box width="50%">Works</box> 378 + </box> 379 + ``` 380 + 381 + ### FlexGrow Needs Sized Parent 382 + 383 + ```tsx 384 + // WRONG 385 + <box> 386 + <box flexGrow={1}>Won't grow</box> 387 + </box> 388 + 389 + // CORRECT 390 + <box height="100%"> 391 + <box flexGrow={1}>Will grow</box> 392 + </box> 393 + ``` 394 + 395 + ### ScrollBox Needs Height 396 + 397 + ```tsx 398 + // WRONG - no height constraint 399 + <scrollbox> 400 + {items} 401 + </scrollbox> 402 + 403 + // CORRECT 404 + <scrollbox height={20}> 405 + {items} 406 + </scrollbox> 407 + ``` 408 + 409 + ### Borders Add to Size 410 + 411 + Borders take up space inside the box: 412 + 413 + ```tsx 414 + <box width={10} border> 415 + {/* Inner content area is 8 chars (10 - 2 for borders) */} 416 + </box> 417 + ```
+531
skills/opentui/references/components/inputs.md
··· 1 + # Input Components 2 + 3 + Components for user input in OpenTUI. 4 + 5 + ## Input Component 6 + 7 + Single-line text input field. 8 + 9 + ### Basic Usage 10 + 11 + ```tsx 12 + // React 13 + <input 14 + value={value} 15 + onChange={(newValue) => setValue(newValue)} 16 + placeholder="Enter text..." 17 + focused 18 + /> 19 + 20 + // Solid 21 + <input 22 + value={value()} 23 + onInput={(newValue) => setValue(newValue)} 24 + placeholder="Enter text..." 25 + focused 26 + /> 27 + 28 + // Core 29 + const input = new InputRenderable(renderer, { 30 + id: "name", 31 + placeholder: "Enter text...", 32 + }) 33 + input.on(InputRenderableEvents.CHANGE, (value) => { 34 + console.log("Value:", value) 35 + }) 36 + input.focus() 37 + ``` 38 + 39 + ### Styling 40 + 41 + ```tsx 42 + <input 43 + width={30} 44 + backgroundColor="#1a1a1a" 45 + textColor="#FFFFFF" 46 + cursorColor="#00FF00" 47 + focusedBackgroundColor="#2a2a2a" 48 + placeholderColor="#666666" 49 + /> 50 + ``` 51 + 52 + ### Events 53 + 54 + ```tsx 55 + // React 56 + <input 57 + onChange={(value) => console.log("Changed:", value)} 58 + onFocus={() => console.log("Focused")} 59 + onBlur={() => console.log("Blurred")} 60 + /> 61 + 62 + // Core 63 + input.on(InputRenderableEvents.CHANGE, (value) => {}) 64 + input.on(InputRenderableEvents.FOCUS, () => {}) 65 + input.on(InputRenderableEvents.BLUR, () => {}) 66 + ``` 67 + 68 + ### Controlled Input 69 + 70 + ```tsx 71 + // React 72 + function ControlledInput() { 73 + const [value, setValue] = useState("") 74 + 75 + return ( 76 + <input 77 + value={value} 78 + onChange={setValue} 79 + focused 80 + /> 81 + ) 82 + } 83 + 84 + // Solid 85 + function ControlledInput() { 86 + const [value, setValue] = createSignal("") 87 + 88 + return ( 89 + <input 90 + value={value()} 91 + onInput={setValue} 92 + focused 93 + /> 94 + ) 95 + } 96 + ``` 97 + 98 + ## Textarea Component 99 + 100 + Multi-line text input field. 101 + 102 + ### Basic Usage 103 + 104 + ```tsx 105 + // React 106 + <textarea 107 + value={text} 108 + onChange={(newText) => setText(newText)} 109 + placeholder="Enter multiple lines..." 110 + width={40} 111 + height={10} 112 + focused 113 + /> 114 + 115 + // Solid 116 + <textarea 117 + value={text()} 118 + onInput={(newText) => setText(newText)} 119 + placeholder="Enter multiple lines..." 120 + width={40} 121 + height={10} 122 + focused 123 + /> 124 + 125 + // Core 126 + const textarea = new TextareaRenderable(renderer, { 127 + id: "editor", 128 + width: 40, 129 + height: 10, 130 + placeholder: "Enter text...", 131 + }) 132 + ``` 133 + 134 + ### Features 135 + 136 + ```tsx 137 + <textarea 138 + showLineNumbers // Display line numbers 139 + wrapText // Wrap long lines 140 + readOnly // Disable editing 141 + tabSize={2} // Tab character width 142 + /> 143 + ``` 144 + 145 + ### Syntax Highlighting 146 + 147 + ```tsx 148 + <textarea 149 + language="typescript" 150 + value={code} 151 + onChange={setCode} 152 + /> 153 + ``` 154 + 155 + ## Select Component 156 + 157 + List selection for choosing from options. 158 + 159 + ### Basic Usage 160 + 161 + ```tsx 162 + // React 163 + <select 164 + options={[ 165 + { name: "Option 1", description: "First option", value: "1" }, 166 + { name: "Option 2", description: "Second option", value: "2" }, 167 + { name: "Option 3", description: "Third option", value: "3" }, 168 + ]} 169 + onSelect={(index, option) => { 170 + console.log("Selected:", option.name) // Called when Enter is pressed 171 + }} 172 + focused 173 + /> 174 + 175 + // Solid 176 + <select 177 + options={[ 178 + { name: "Option 1", description: "First option", value: "1" }, 179 + { name: "Option 2", description: "Second option", value: "2" }, 180 + ]} 181 + onSelect={(index, option) => { 182 + console.log("Selected:", option.name) // Called when Enter is pressed 183 + }} 184 + focused 185 + /> 186 + 187 + // Core 188 + const select = new SelectRenderable(renderer, { 189 + id: "menu", 190 + options: [ 191 + { name: "Option 1", description: "First option", value: "1" }, 192 + { name: "Option 2", description: "Second option", value: "2" }, 193 + ], 194 + }) 195 + select.on(SelectRenderableEvents.ITEM_SELECTED, (index, option) => { 196 + console.log("Selected:", option.name) // Called when Enter is pressed 197 + }) 198 + select.focus() 199 + ``` 200 + 201 + ### Option Format 202 + 203 + ```typescript 204 + interface SelectOption { 205 + name: string // Display text 206 + description?: string // Optional description shown below 207 + value?: any // Associated value 208 + } 209 + ``` 210 + 211 + ### Styling 212 + 213 + ```tsx 214 + <select 215 + height={8} // Visible height 216 + selectedIndex={0} // Initially selected 217 + showScrollIndicator // Show scroll arrows 218 + selectedBackgroundColor="#333" 219 + selectedTextColor="#fff" 220 + highlightBackgroundColor="#444" 221 + /> 222 + ``` 223 + 224 + ### Navigation 225 + 226 + Default keybindings: 227 + - `Up` / `k` - Move up 228 + - `Down` / `j` - Move down 229 + - `Enter` - Select item 230 + 231 + ### Events 232 + 233 + **Important**: `onSelect` and `onChange` serve different purposes: 234 + 235 + | Event | Trigger | Use Case | 236 + |-------|---------|----------| 237 + | `onSelect` | **Enter key pressed** - user confirms selection | Perform action with selected item | 238 + | `onChange` | **Arrow keys** - user navigates list | Preview, update UI as user browses | 239 + 240 + ```tsx 241 + // React/Solid 242 + <select 243 + onSelect={(index, option) => { 244 + // Called when Enter is pressed - selection confirmed 245 + console.log("User selected:", option.name) 246 + performAction(option) 247 + }} 248 + onChange={(index, option) => { 249 + // Called when navigating with arrow keys 250 + console.log("Browsing:", option.name) 251 + showPreview(option) 252 + }} 253 + /> 254 + 255 + // Core 256 + select.on(SelectRenderableEvents.ITEM_SELECTED, (index, option) => { 257 + // Called when Enter is pressed 258 + }) 259 + select.on(SelectRenderableEvents.SELECTION_CHANGED, (index, option) => { 260 + // Called when navigating with arrow keys 261 + }) 262 + ``` 263 + 264 + ## Tab Select Component 265 + 266 + Horizontal tab-based selection. 267 + 268 + ### Basic Usage 269 + 270 + ```tsx 271 + // React 272 + <tab-select 273 + options={[ 274 + { name: "Home", description: "Dashboard view" }, 275 + { name: "Settings", description: "Configuration" }, 276 + { name: "Help", description: "Documentation" }, 277 + ]} 278 + onSelect={(index, option) => { 279 + console.log("Tab selected:", option.name) // Called when Enter is pressed 280 + }} 281 + focused 282 + /> 283 + 284 + // Solid (note underscore) 285 + <tab_select 286 + options={[ 287 + { name: "Home", description: "Dashboard view" }, 288 + { name: "Settings", description: "Configuration" }, 289 + ]} 290 + onSelect={(index, option) => { 291 + console.log("Tab selected:", option.name) // Called when Enter is pressed 292 + }} 293 + focused 294 + /> 295 + 296 + // Core 297 + const tabs = new TabSelectRenderable(renderer, { 298 + id: "tabs", 299 + options: [...], 300 + tabWidth: 20, 301 + }) 302 + tabs.on(TabSelectRenderableEvents.ITEM_SELECTED, (index, option) => { 303 + console.log("Tab selected:", option.name) // Called when Enter is pressed 304 + }) 305 + tabs.focus() 306 + ``` 307 + 308 + ### Events 309 + 310 + Same pattern as Select - `onSelect` for Enter key, `onChange` for navigation: 311 + 312 + ```tsx 313 + <tab-select 314 + onSelect={(index, option) => { 315 + // Called when Enter is pressed - switch to tab 316 + setActiveTab(index) 317 + }} 318 + onChange={(index, option) => { 319 + // Called when navigating with arrow keys 320 + showTabPreview(option) 321 + }} 322 + /> 323 + ``` 324 + 325 + ### Styling 326 + 327 + ```tsx 328 + // React 329 + <tab-select 330 + tabWidth={20} // Width of each tab 331 + selectedIndex={0} // Initially selected tab 332 + /> 333 + 334 + // Solid 335 + <tab_select 336 + tabWidth={20} 337 + selectedIndex={0} 338 + /> 339 + ``` 340 + 341 + ### Navigation 342 + 343 + Default keybindings: 344 + - `Left` / `[` - Previous tab 345 + - `Right` / `]` - Next tab 346 + - `Enter` - Select tab 347 + 348 + ## Focus Management 349 + 350 + ### Single Focused Input 351 + 352 + ```tsx 353 + function SingleInput() { 354 + return <input placeholder="I'm focused" focused /> 355 + } 356 + ``` 357 + 358 + ### Multiple Inputs with Focus State 359 + 360 + ```tsx 361 + // React 362 + function Form() { 363 + const [focusIndex, setFocusIndex] = useState(0) 364 + const fields = ["name", "email", "message"] 365 + 366 + useKeyboard((key) => { 367 + if (key.name === "tab") { 368 + setFocusIndex(i => (i + 1) % fields.length) 369 + } 370 + }) 371 + 372 + return ( 373 + <box flexDirection="column" gap={1}> 374 + {fields.map((field, i) => ( 375 + <input 376 + key={field} 377 + placeholder={`Enter ${field}`} 378 + focused={i === focusIndex} 379 + /> 380 + ))} 381 + </box> 382 + ) 383 + } 384 + ``` 385 + 386 + ### Focus Methods (Core) 387 + 388 + ```typescript 389 + input.focus() // Give focus 390 + input.blur() // Remove focus 391 + input.isFocused() // Check focus state 392 + ``` 393 + 394 + ## Form Patterns 395 + 396 + ### Login Form 397 + 398 + ```tsx 399 + function LoginForm() { 400 + const [username, setUsername] = useState("") 401 + const [password, setPassword] = useState("") 402 + const [focusField, setFocusField] = useState<"username" | "password">("username") 403 + 404 + useKeyboard((key) => { 405 + if (key.name === "tab") { 406 + setFocusField(f => f === "username" ? "password" : "username") 407 + } 408 + if (key.name === "enter") { 409 + handleLogin() 410 + } 411 + }) 412 + 413 + return ( 414 + <box flexDirection="column" gap={1} border padding={2}> 415 + <box flexDirection="row" gap={1}> 416 + <text>Username:</text> 417 + <input 418 + value={username} 419 + onChange={setUsername} 420 + focused={focusField === "username"} 421 + width={20} 422 + /> 423 + </box> 424 + <box flexDirection="row" gap={1}> 425 + <text>Password:</text> 426 + <input 427 + value={password} 428 + onChange={setPassword} 429 + focused={focusField === "password"} 430 + width={20} 431 + /> 432 + </box> 433 + </box> 434 + ) 435 + } 436 + ``` 437 + 438 + ### Search with Results 439 + 440 + ```tsx 441 + function SearchableList({ items, onItemSelected }) { 442 + const [query, setQuery] = useState("") 443 + const [focusSearch, setFocusSearch] = useState(true) 444 + const [preview, setPreview] = useState(null) 445 + 446 + const filtered = items.filter(item => 447 + item.toLowerCase().includes(query.toLowerCase()) 448 + ) 449 + 450 + useKeyboard((key) => { 451 + if (key.name === "tab") { 452 + setFocusSearch(f => !f) 453 + } 454 + }) 455 + 456 + return ( 457 + <box flexDirection="column"> 458 + <input 459 + value={query} 460 + onChange={setQuery} 461 + placeholder="Search..." 462 + focused={focusSearch} 463 + /> 464 + <select 465 + options={filtered.map(item => ({ name: item }))} 466 + focused={!focusSearch} 467 + height={10} 468 + onSelect={(index, option) => { 469 + // Enter pressed - confirm selection 470 + onItemSelected(option) 471 + }} 472 + onChange={(index, option) => { 473 + // Navigating - show preview 474 + setPreview(option) 475 + }} 476 + /> 477 + </box> 478 + ) 479 + } 480 + ``` 481 + 482 + ## Gotchas 483 + 484 + ### Focus Required 485 + 486 + Inputs must be focused to receive keyboard input: 487 + 488 + ```tsx 489 + // WRONG - won't receive input 490 + <input placeholder="Type here" /> 491 + 492 + // CORRECT 493 + <input placeholder="Type here" focused /> 494 + ``` 495 + 496 + ### Select Options Format 497 + 498 + Options must be objects with `name` property: 499 + 500 + ```tsx 501 + // WRONG 502 + <select options={["a", "b", "c"]} /> 503 + 504 + // CORRECT 505 + <select options={[ 506 + { name: "A", description: "Option A" }, 507 + { name: "B", description: "Option B" }, 508 + ]} /> 509 + ``` 510 + 511 + ### Solid Uses Underscores 512 + 513 + ```tsx 514 + // React 515 + <tab-select /> 516 + 517 + // Solid 518 + <tab_select /> 519 + ``` 520 + 521 + ### Value vs onInput (Solid) 522 + 523 + Solid uses `onInput` instead of `onChange`: 524 + 525 + ```tsx 526 + // React 527 + <input value={value} onChange={setValue} /> 528 + 529 + // Solid 530 + <input value={value()} onInput={setValue} /> 531 + ```
+386
skills/opentui/references/components/text-display.md
··· 1 + # Text & Display Components 2 + 3 + Components for displaying text content in OpenTUI. 4 + 5 + ## Text Component 6 + 7 + The primary component for displaying styled text. 8 + 9 + ### Basic Usage 10 + 11 + ```tsx 12 + // React/Solid 13 + <text>Hello, World!</text> 14 + 15 + // With content prop 16 + <text content="Hello, World!" /> 17 + 18 + // Core 19 + const text = new TextRenderable(renderer, { 20 + id: "greeting", 21 + content: "Hello, World!", 22 + }) 23 + ``` 24 + 25 + ### Styling (React/Solid) 26 + 27 + For React and Solid, use **nested modifier tags** for text styling: 28 + 29 + ```tsx 30 + <text fg="#FFFFFF" bg="#000000"> 31 + <strong>Bold</strong>, <em>italic</em>, and <u>underlined</u> 32 + </text> 33 + ``` 34 + 35 + > **Important**: Do NOT use `bold`, `italic`, `underline`, `dim`, `strikethrough` as props on `<text>` — they don't work. Always use nested tags like `<strong>`, `<em>`, `<u>`, or `<span>` with styling. 36 + 37 + ### Styling (Core) - Text Attributes 38 + 39 + ```typescript 40 + import { TextRenderable, TextAttributes } from "@opentui/core" 41 + 42 + const text = new TextRenderable(renderer, { 43 + content: "Styled", 44 + attributes: TextAttributes.BOLD | TextAttributes.UNDERLINE, 45 + }) 46 + ``` 47 + 48 + **Available attributes:** 49 + - `TextAttributes.BOLD` 50 + - `TextAttributes.DIM` 51 + - `TextAttributes.ITALIC` 52 + - `TextAttributes.UNDERLINE` 53 + - `TextAttributes.BLINK` 54 + - `TextAttributes.INVERSE` 55 + - `TextAttributes.HIDDEN` 56 + - `TextAttributes.STRIKETHROUGH` 57 + 58 + ### Text Selection 59 + 60 + ```tsx 61 + <text selectable> 62 + This text can be selected by the user 63 + </text> 64 + 65 + <text selectable={false}> 66 + This text cannot be selected 67 + </text> 68 + ``` 69 + 70 + For copy-on-selection and the full selection API, see `keyboard/REFERENCE.md` (selection). 71 + 72 + ## Text Modifiers 73 + 74 + Inline styling elements that must be used inside `<text>`: 75 + 76 + ### Span 77 + 78 + Inline styled text: 79 + 80 + ```tsx 81 + <text> 82 + Normal text with <span fg="red">red text</span> inline 83 + </text> 84 + ``` 85 + 86 + ### Bold/Strong 87 + 88 + ```tsx 89 + <text> 90 + <strong>Bold text</strong> 91 + <b>Also bold</b> 92 + </text> 93 + ``` 94 + 95 + ### Italic/Emphasis 96 + 97 + ```tsx 98 + <text> 99 + <em>Italic text</em> 100 + <i>Also italic</i> 101 + </text> 102 + ``` 103 + 104 + ### Underline 105 + 106 + ```tsx 107 + <text> 108 + <u>Underlined text</u> 109 + </text> 110 + ``` 111 + 112 + ### Line Break 113 + 114 + ```tsx 115 + <text> 116 + Line one 117 + <br /> 118 + Line two 119 + </text> 120 + ``` 121 + 122 + ### Link 123 + 124 + ```tsx 125 + <text> 126 + Visit <a href="https://example.com">our website</a> 127 + </text> 128 + ``` 129 + 130 + ### Combined Modifiers 131 + 132 + ```tsx 133 + <text> 134 + <span fg="#00FF00"> 135 + <strong>Bold green</strong> 136 + </span> 137 + and 138 + <span fg="#FF0000"> 139 + <em><u>italic underlined red</u></em> 140 + </span> 141 + </text> 142 + ``` 143 + 144 + ## Styled Text Template (Core) 145 + 146 + The `t` template literal for complex styling: 147 + 148 + ```typescript 149 + import { t, bold, italic, underline, fg, bg, dim } from "@opentui/core" 150 + 151 + const styled = t` 152 + ${bold("Bold")} and ${italic("italic")} text. 153 + ${fg("#FF0000")("Red text")} with ${bg("#0000FF")("blue background")}. 154 + ${dim("Dimmed")} and ${underline("underlined")}. 155 + ` 156 + 157 + const text = new TextRenderable(renderer, { 158 + content: styled, 159 + }) 160 + ``` 161 + 162 + ### Style Functions 163 + 164 + | Function | Description | 165 + |----------|-------------| 166 + | `bold(text)` | Bold text | 167 + | `italic(text)` | Italic text | 168 + | `underline(text)` | Underlined text | 169 + | `dim(text)` | Dimmed text | 170 + | `strikethrough(text)` | Strikethrough text | 171 + | `fg(color)(text)` | Set foreground color | 172 + | `bg(color)(text)` | Set background color | 173 + 174 + ## ASCII Font Component 175 + 176 + Display large ASCII art text banners. 177 + 178 + ### Basic Usage 179 + 180 + ```tsx 181 + // React 182 + <ascii-font text="TITLE" font="tiny" /> 183 + 184 + // Solid 185 + <ascii_font text="TITLE" font="tiny" /> 186 + 187 + // Core 188 + const title = new ASCIIFontRenderable(renderer, { 189 + id: "title", 190 + text: "TITLE", 191 + font: "tiny", 192 + }) 193 + ``` 194 + 195 + ### Available Fonts 196 + 197 + | Font | Description | 198 + |------|-------------| 199 + | `tiny` | Compact ASCII font | 200 + | `block` | Block-style letters | 201 + | `slick` | Sleek modern style | 202 + | `shade` | Shaded 3D effect | 203 + 204 + ### Styling 205 + 206 + ```tsx 207 + // React 208 + <ascii-font 209 + text="HELLO" 210 + font="block" 211 + color="#00FF00" 212 + /> 213 + 214 + // Core 215 + import { RGBA } from "@opentui/core" 216 + 217 + const title = new ASCIIFontRenderable(renderer, { 218 + text: "HELLO", 219 + font: "block", 220 + color: RGBA.fromHex("#00FF00"), 221 + }) 222 + ``` 223 + 224 + ### Example Output 225 + 226 + ``` 227 + Font: tiny 228 + ╭─╮╭─╮╭─╮╭╮╭╮╭─╮╶╮╶ ╶╮ 229 + │ ││─┘├┤ │╰╯││ │ │ 230 + ╰─╯╵ ╰─╯╵ ╵╰─╯╶╯╶╰─╯ 231 + 232 + Font: block 233 + █▀▀█ █▀▀█ █▀▀ █▀▀▄ 234 + █ █ █▀▀▀ █▀▀ █ █ 235 + ▀▀▀▀ ▀ ▀▀▀ ▀ ▀ 236 + ``` 237 + 238 + ## Colors 239 + 240 + ### Color Formats 241 + 242 + ```tsx 243 + // Hex colors 244 + <text fg="#FF0000">Red</text> 245 + <text fg="#F00">Short hex</text> 246 + 247 + // Named colors 248 + <text fg="red">Red</text> 249 + <text fg="blue">Blue</text> 250 + 251 + // Transparent 252 + <text bg="transparent">No background</text> 253 + ``` 254 + 255 + ### RGBA Class 256 + 257 + The `RGBA` class from `@opentui/core` can be used in **all frameworks** (Core, React, Solid) for programmatic color manipulation: 258 + 259 + ```typescript 260 + import { RGBA } from "@opentui/core" 261 + 262 + // From hex string (most common) 263 + const red = RGBA.fromHex("#FF0000") 264 + const shortHex = RGBA.fromHex("#F00") // Short form supported 265 + 266 + // From integers (0-255 range for each channel) 267 + const green = RGBA.fromInts(0, 255, 0, 255) // r, g, b, a 268 + const semiGreen = RGBA.fromInts(0, 255, 0, 128) // 50% transparent 269 + 270 + // From normalized floats (0.0-1.0 range) 271 + const blue = RGBA.fromValues(0.0, 0.0, 1.0, 1.0) // r, g, b, a 272 + const overlay = RGBA.fromValues(0.1, 0.1, 0.1, 0.7) // Dark semi-transparent 273 + 274 + // Common use cases 275 + const backgroundColor = RGBA.fromHex("#1a1a2e") 276 + const textColor = RGBA.fromHex("#FFFFFF") 277 + const borderColor = RGBA.fromInts(122, 162, 247, 255) // Tokyo Night blue 278 + const shadowColor = RGBA.fromValues(0.0, 0.0, 0.0, 0.5) // 50% black 279 + ``` 280 + 281 + **When to use each method:** 282 + - `fromHex()` - When working with design specs or CSS colors 283 + - `fromInts()` - When you have 8-bit color values (0-255) 284 + - `fromValues()` - When doing color math or interpolation (normalized 0.0-1.0) 285 + 286 + ### Using RGBA in React/Solid 287 + 288 + ```tsx 289 + // React or Solid - RGBA works with color props 290 + import { RGBA } from "@opentui/core" 291 + 292 + const primaryColor = RGBA.fromHex("#7aa2f7") 293 + 294 + function MyComponent() { 295 + return ( 296 + <box backgroundColor={primaryColor} borderColor={primaryColor}> 297 + <text fg={RGBA.fromHex("#c0caf5")}>Styled with RGBA</text> 298 + </box> 299 + ) 300 + } 301 + ``` 302 + 303 + Most props that accept color strings (`"#FF0000"`, `"red"`) also accept `RGBA` objects directly. 304 + 305 + ## Text Wrapping 306 + 307 + Text wraps based on parent container: 308 + 309 + ```tsx 310 + <box width={40}> 311 + <text> 312 + This long text will wrap when it reaches the edge of the 313 + 40-character wide parent container. 314 + </text> 315 + </box> 316 + ``` 317 + 318 + ## Dynamic Content 319 + 320 + ### React 321 + 322 + ```tsx 323 + function Counter() { 324 + const [count, setCount] = useState(0) 325 + return <text>Count: {count}</text> 326 + } 327 + ``` 328 + 329 + ### Solid 330 + 331 + ```tsx 332 + function Counter() { 333 + const [count, setCount] = createSignal(0) 334 + return <text>Count: {count()}</text> 335 + } 336 + ``` 337 + 338 + ### Core 339 + 340 + ```typescript 341 + const text = new TextRenderable(renderer, { 342 + id: "counter", 343 + content: "Count: 0", 344 + }) 345 + 346 + // Update later 347 + text.setContent("Count: 1") 348 + ``` 349 + 350 + ## Gotchas 351 + 352 + ### Text Modifiers Outside Text 353 + 354 + ```tsx 355 + // WRONG - modifiers only work inside <text> 356 + <box> 357 + <strong>Won't work</strong> 358 + </box> 359 + 360 + // CORRECT 361 + <box> 362 + <text> 363 + <strong>This works</strong> 364 + </text> 365 + </box> 366 + ``` 367 + 368 + ### Empty Text 369 + 370 + ```tsx 371 + // May cause layout issues 372 + <text></text> 373 + 374 + // Better - use space or conditional 375 + <text>{content || " "}</text> 376 + ``` 377 + 378 + ### Color Format 379 + 380 + ```tsx 381 + // WRONG 382 + <text fg="FF0000">Missing #</text> 383 + 384 + // CORRECT 385 + <text fg="#FF0000">With #</text> 386 + ```
+145
skills/opentui/references/core/REFERENCE.md
··· 1 + # OpenTUI Core (@opentui/core) 2 + 3 + The foundational library for building terminal user interfaces. Provides an imperative API with all primitives, giving you maximum control over rendering, state, and behavior. 4 + 5 + ## Overview 6 + 7 + OpenTUI Core runs on Bun with native Zig bindings for performance-critical operations: 8 + - **Renderer**: Manages terminal output, input events, and the rendering loop 9 + - **Renderables**: Hierarchical UI building blocks with Yoga layout 10 + - **Constructs**: Declarative wrappers for composing Renderables 11 + - **FrameBuffer**: Low-level 2D rendering surface for custom graphics 12 + 13 + ## When to Use Core 14 + 15 + Use the core imperative API when: 16 + - Building a library or framework on top of OpenTUI 17 + - Need maximum control over rendering and state 18 + - Want smallest possible bundle size (no React/Solid runtime) 19 + - Building performance-critical applications 20 + - Integrating with existing imperative codebases 21 + 22 + ## When NOT to Use Core 23 + 24 + | Scenario | Use Instead | 25 + |----------|-------------| 26 + | Familiar with React patterns | `@opentui/react` | 27 + | Want fine-grained reactivity | `@opentui/solid` | 28 + | Building typical applications | React or Solid reconciler | 29 + | Rapid prototyping | React or Solid reconciler | 30 + 31 + ## Quick Start 32 + 33 + ### Using create-tui (Recommended) 34 + 35 + ```bash 36 + bunx create-tui@latest -t core my-app 37 + cd my-app 38 + bun run src/index.ts 39 + ``` 40 + 41 + The CLI creates the `my-app` directory for you - it must **not already exist**. 42 + 43 + **Agent guidance**: Always use autonomous mode with `-t <template>` flag. Never use interactive mode (`bunx create-tui@latest my-app` without `-t`) as it requires user prompts that agents cannot respond to. 44 + 45 + ### Manual Setup 46 + 47 + ```bash 48 + mkdir my-tui && cd my-tui 49 + bun init 50 + bun install @opentui/core 51 + ``` 52 + 53 + ```typescript 54 + import { createCliRenderer, TextRenderable, BoxRenderable } from "@opentui/core" 55 + 56 + const renderer = await createCliRenderer() 57 + 58 + // Create a box container 59 + const container = new BoxRenderable(renderer, { 60 + id: "container", 61 + width: 40, 62 + height: 10, 63 + border: true, 64 + borderStyle: "rounded", 65 + padding: 1, 66 + }) 67 + 68 + // Create text inside the box 69 + const greeting = new TextRenderable(renderer, { 70 + id: "greeting", 71 + content: "Hello, OpenTUI!", 72 + fg: "#00FF00", 73 + }) 74 + 75 + // Compose the tree 76 + container.add(greeting) 77 + renderer.root.add(container) 78 + ``` 79 + 80 + ## Core Concepts 81 + 82 + ### Renderer 83 + 84 + The `CliRenderer` orchestrates everything: 85 + - Manages the terminal viewport and alternate screen 86 + - Handles input events (keyboard, mouse, paste) 87 + - Runs the rendering loop (configurable FPS) 88 + - Provides the root node for the renderable tree 89 + 90 + ### Renderables vs Constructs 91 + 92 + | Renderables (Imperative) | Constructs (Declarative) | 93 + |--------------------------|--------------------------| 94 + | `new TextRenderable(renderer, {...})` | `Text({...})` | 95 + | Requires renderer at creation | Creates VNode, instantiated later | 96 + | Direct mutation via methods | Chained calls recorded, replayed on instantiation | 97 + | Full control | Cleaner composition | 98 + 99 + ### Storage Options 100 + 101 + Renderables can be composed in two ways: 102 + 1. **Imperative**: Create instances, call `.add()` to compose 103 + 2. **Declarative (Constructs)**: Create VNodes, pass children as arguments 104 + 105 + ## Essential Commands 106 + 107 + ```bash 108 + bun install @opentui/core # Install 109 + bun run src/index.ts # Run directly (no build needed) 110 + bun test # Run tests 111 + ``` 112 + 113 + ## Runtime Requirements 114 + 115 + OpenTUI runs on Bun and uses Zig for native builds. 116 + 117 + ```bash 118 + # Package management 119 + bun install @opentui/core 120 + 121 + # Running 122 + bun run src/index.ts 123 + bun test 124 + 125 + # Building (only needed for native code changes) 126 + bun run build 127 + ``` 128 + 129 + **Zig** is required for building native components. 130 + 131 + ## In This Reference 132 + 133 + - [Configuration](./configuration.md) - Renderer options, environment variables 134 + - [API](./api.md) - Renderer, Renderables, types, utilities 135 + - [Patterns](./patterns.md) - Composition, events, state management 136 + - [Gotchas](./gotchas.md) - Common issues, debugging, limitations 137 + 138 + ## See Also 139 + 140 + - [React](../react/REFERENCE.md) - React reconciler for declarative TUI 141 + - [Solid](../solid/REFERENCE.md) - Solid reconciler for declarative TUI 142 + - [Layout](../layout/REFERENCE.md) - Yoga/Flexbox layout system 143 + - [Components](../components/REFERENCE.md) - Component reference by category 144 + - [Keyboard](../keyboard/REFERENCE.md) - Input handling and shortcuts 145 + - [Testing](../testing/REFERENCE.md) - Test renderer and snapshots
+543
skills/opentui/references/core/api.md
··· 1 + # Core API Reference 2 + 3 + ## Renderer 4 + 5 + ### createCliRenderer(config?) 6 + 7 + Creates and initializes the CLI renderer. 8 + 9 + ```typescript 10 + import { createCliRenderer, type CliRendererConfig } from "@opentui/core" 11 + 12 + const renderer = await createCliRenderer({ 13 + targetFPS: 60, // Target frames per second 14 + exitOnCtrlC: true, // Exit process on Ctrl+C 15 + consoleOptions: { // Debug console overlay 16 + position: ConsolePosition.BOTTOM, 17 + sizePercent: 30, 18 + startInDebugMode: false, 19 + }, 20 + onDestroy: () => {}, // Cleanup callback 21 + }) 22 + ``` 23 + 24 + ### CliRenderer Instance 25 + 26 + ```typescript 27 + renderer.root // Root renderable node 28 + renderer.width // Terminal width in columns 29 + renderer.height // Terminal height in rows 30 + renderer.keyInput // Keyboard event emitter 31 + renderer.console // Console overlay controller 32 + 33 + renderer.start() // Start render loop 34 + renderer.stop() // Stop render loop 35 + renderer.destroy() // Cleanup and exit alternate screen 36 + renderer.requestRender() // Request a re-render 37 + 38 + renderer.setCursorStyle(options) // Set cursor style 39 + renderer.setCursorColor(color) // Set cursor color 40 + renderer.setMousePointer(style) // Set mouse pointer shape 41 + ``` 42 + 43 + ### Cursor & Mouse Pointer 44 + 45 + ```typescript 46 + import { type CursorStyleOptions, type MousePointerStyle } from "@opentui/core" 47 + 48 + // Set cursor style (options object) 49 + renderer.setCursorStyle({ 50 + style: "block", // "block" | "line" | "underline" | "default" 51 + blinking: true, // Cursor blink 52 + color: RGBA.fromHex("#FF0000"), // Cursor color 53 + cursor: "pointer", // Mouse pointer shape 54 + }) 55 + 56 + // Set mouse pointer shape (OSC 22) 57 + renderer.setMousePointer("pointer") 58 + // Available: "default" | "pointer" | "text" | "crosshair" | "move" | "not-allowed" 59 + ``` 60 + 61 + ### Renderer Events 62 + 63 + ```typescript 64 + renderer.on("resize", (width, height) => {}) // Terminal resized 65 + renderer.on("focus", () => {}) // Terminal window gained focus 66 + renderer.on("blur", () => {}) // Terminal window lost focus 67 + renderer.on("theme_mode", (mode) => {}) // "dark" | "light" 68 + renderer.on("capabilities", (caps) => {}) // Terminal capabilities detected 69 + renderer.on("selection", (selection) => {}) // Text selection finished (mouse-up) 70 + renderer.on("destroy", () => {}) // Renderer destroyed 71 + renderer.on("memory:snapshot", (snapshot) => {}) // Memory snapshot 72 + renderer.on("debugOverlay:toggle", () => {}) // Debug overlay toggled 73 + ``` 74 + 75 + ### Console Overlay 76 + 77 + ```typescript 78 + renderer.console.show() // Show console overlay 79 + renderer.console.hide() // Hide console overlay 80 + renderer.console.toggle() // Toggle visibility/focus 81 + renderer.console.clear() // Clear console contents 82 + ``` 83 + 84 + ## Renderables 85 + 86 + All renderables extend the base `Renderable` class and share common properties. 87 + 88 + ### Common Properties 89 + 90 + ```typescript 91 + interface CommonProps { 92 + id?: string // Unique identifier 93 + 94 + // Positioning 95 + position?: "relative" | "absolute" 96 + left?: number | string 97 + top?: number | string 98 + right?: number | string 99 + bottom?: number | string 100 + 101 + // Dimensions 102 + width?: number | string | "auto" 103 + height?: number | string | "auto" 104 + minWidth?: number 105 + minHeight?: number 106 + maxWidth?: number 107 + maxHeight?: number 108 + 109 + // Flexbox 110 + flexDirection?: "row" | "column" | "row-reverse" | "column-reverse" 111 + flexGrow?: number 112 + flexShrink?: number 113 + flexBasis?: number | string 114 + flexWrap?: "nowrap" | "wrap" | "wrap-reverse" 115 + justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly" 116 + alignItems?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline" 117 + alignSelf?: "auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline" 118 + alignContent?: "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-around" 119 + 120 + // Spacing 121 + padding?: number 122 + paddingTop?: number 123 + paddingRight?: number 124 + paddingBottom?: number 125 + paddingLeft?: number 126 + margin?: number 127 + marginTop?: number 128 + marginRight?: number 129 + marginBottom?: number 130 + marginLeft?: number 131 + gap?: number 132 + 133 + // Display 134 + display?: "flex" | "none" 135 + overflow?: "visible" | "hidden" | "scroll" 136 + zIndex?: number 137 + } 138 + ``` 139 + 140 + ### Renderable Methods 141 + 142 + ```typescript 143 + renderable.add(child) // Add child renderable 144 + renderable.remove(child) // Remove child renderable 145 + renderable.getRenderable(id) // Find child by ID 146 + renderable.focus() // Focus this renderable 147 + renderable.blur() // Remove focus 148 + renderable.destroy() // Destroy and cleanup 149 + 150 + renderable.on(event, handler) // Add event listener 151 + renderable.off(event, handler) // Remove event listener 152 + renderable.emit(event, ...args) // Emit event 153 + ``` 154 + 155 + ### TextRenderable 156 + 157 + Display styled text content. 158 + 159 + ```typescript 160 + import { TextRenderable, TextAttributes, t, bold, fg, underline } from "@opentui/core" 161 + 162 + const text = new TextRenderable(renderer, { 163 + id: "text", 164 + content: "Hello World", 165 + fg: "#FFFFFF", // Foreground color 166 + bg: "#000000", // Background color 167 + attributes: TextAttributes.BOLD | TextAttributes.UNDERLINE, 168 + selectable: true, // Allow text selection 169 + }) 170 + 171 + // Styled text with template literals 172 + const styled = new TextRenderable(renderer, { 173 + content: t`${bold("Bold")} and ${fg("#FF0000")(underline("red underlined"))}`, 174 + }) 175 + ``` 176 + 177 + **TextAttributes flags:** 178 + - `TextAttributes.BOLD` 179 + - `TextAttributes.DIM` 180 + - `TextAttributes.ITALIC` 181 + - `TextAttributes.UNDERLINE` 182 + - `TextAttributes.BLINK` 183 + - `TextAttributes.INVERSE` 184 + - `TextAttributes.HIDDEN` 185 + - `TextAttributes.STRIKETHROUGH` 186 + 187 + ### BoxRenderable 188 + 189 + Container with borders and layout. 190 + 191 + ```typescript 192 + import { BoxRenderable } from "@opentui/core" 193 + 194 + const box = new BoxRenderable(renderer, { 195 + id: "box", 196 + width: 40, 197 + height: 10, 198 + backgroundColor: "#1a1a2e", 199 + border: true, 200 + borderStyle: "single" | "double" | "rounded" | "bold" | "none", 201 + borderColor: "#FFFFFF", 202 + title: "Panel Title", 203 + titleAlignment: "left" | "center" | "right", 204 + onMouseDown: (event) => {}, 205 + onMouseUp: (event) => {}, 206 + onMouseMove: (event) => {}, 207 + }) 208 + ``` 209 + 210 + ### InputRenderable 211 + 212 + Single-line text input. 213 + 214 + ```typescript 215 + import { InputRenderable, InputRenderableEvents } from "@opentui/core" 216 + 217 + const input = new InputRenderable(renderer, { 218 + id: "input", 219 + width: 30, 220 + placeholder: "Enter text...", 221 + value: "", // Initial value 222 + backgroundColor: "#1a1a1a", 223 + textColor: "#FFFFFF", 224 + cursorColor: "#00FF00", 225 + focusedBackgroundColor: "#2a2a2a", 226 + }) 227 + 228 + input.on(InputRenderableEvents.CHANGE, (value: string) => { 229 + console.log("Value:", value) 230 + }) 231 + 232 + input.focus() // Must be focused to receive input 233 + ``` 234 + 235 + ### SelectRenderable 236 + 237 + List selection component. 238 + 239 + ```typescript 240 + import { SelectRenderable, SelectRenderableEvents } from "@opentui/core" 241 + 242 + const select = new SelectRenderable(renderer, { 243 + id: "select", 244 + width: 30, 245 + height: 10, 246 + options: [ 247 + { name: "Option 1", description: "First option", value: "1" }, 248 + { name: "Option 2", description: "Second option", value: "2" }, 249 + ], 250 + selectedIndex: 0, 251 + }) 252 + 253 + // Called when Enter is pressed - selection confirmed 254 + select.on(SelectRenderableEvents.ITEM_SELECTED, (index, option) => { 255 + console.log("Selected:", option.name) 256 + performAction(option) 257 + }) 258 + 259 + // Called when navigating with arrow keys 260 + select.on(SelectRenderableEvents.SELECTION_CHANGED, (index, option) => { 261 + console.log("Browsing:", option.name) 262 + showPreview(option) 263 + }) 264 + 265 + select.focus() // Navigate with up/down/j/k, select with enter 266 + ``` 267 + 268 + **Event distinction:** 269 + - `ITEM_SELECTED` - Enter key pressed, user confirms selection 270 + - `SELECTION_CHANGED` - Arrow keys, user navigating/browsing options 271 + 272 + ### TabSelectRenderable 273 + 274 + Horizontal tab selection. 275 + 276 + ```typescript 277 + import { TabSelectRenderable, TabSelectRenderableEvents } from "@opentui/core" 278 + 279 + const tabs = new TabSelectRenderable(renderer, { 280 + id: "tabs", 281 + width: 60, 282 + options: [ 283 + { name: "Home", description: "Dashboard" }, 284 + { name: "Settings", description: "Configuration" }, 285 + ], 286 + tabWidth: 20, 287 + }) 288 + 289 + // Called when Enter is pressed - tab selected 290 + tabs.on(TabSelectRenderableEvents.ITEM_SELECTED, (index, option) => { 291 + console.log("Tab selected:", option.name) 292 + switchToTab(index) 293 + }) 294 + 295 + // Called when navigating with arrow keys 296 + tabs.on(TabSelectRenderableEvents.SELECTION_CHANGED, (index, option) => { 297 + console.log("Browsing tab:", option.name) 298 + }) 299 + 300 + tabs.focus() // Navigate with left/right/[/], select with enter 301 + ``` 302 + 303 + **Event distinction** (same as SelectRenderable): 304 + - `ITEM_SELECTED` - Enter key pressed, user confirms tab 305 + - `SELECTION_CHANGED` - Arrow keys, user navigating tabs 306 + 307 + ### ScrollBoxRenderable 308 + 309 + Scrollable container. 310 + 311 + ```typescript 312 + import { ScrollBoxRenderable } from "@opentui/core" 313 + 314 + const scrollbox = new ScrollBoxRenderable(renderer, { 315 + id: "scrollbox", 316 + width: 40, 317 + height: 20, 318 + showScrollbar: true, 319 + scrollbarOptions: { 320 + showArrows: true, 321 + trackOptions: { 322 + foregroundColor: "#7aa2f7", 323 + backgroundColor: "#414868", 324 + }, 325 + }, 326 + }) 327 + 328 + // Add content that exceeds viewport 329 + for (let i = 0; i < 100; i++) { 330 + scrollbox.add(new TextRenderable(renderer, { 331 + id: `line-${i}`, 332 + content: `Line ${i}`, 333 + })) 334 + } 335 + 336 + scrollbox.focus() // Scroll with arrow keys 337 + ``` 338 + 339 + ### ASCIIFontRenderable 340 + 341 + ASCII art text. 342 + 343 + ```typescript 344 + import { ASCIIFontRenderable, RGBA } from "@opentui/core" 345 + 346 + const title = new ASCIIFontRenderable(renderer, { 347 + id: "title", 348 + text: "OPENTUI", 349 + font: "tiny" | "block" | "slick" | "shade", 350 + color: RGBA.fromHex("#FFFFFF"), 351 + }) 352 + ``` 353 + 354 + ### FrameBufferRenderable 355 + 356 + Low-level 2D rendering surface. 357 + 358 + ```typescript 359 + import { FrameBufferRenderable, RGBA } from "@opentui/core" 360 + 361 + const canvas = new FrameBufferRenderable(renderer, { 362 + id: "canvas", 363 + width: 50, 364 + height: 20, 365 + }) 366 + 367 + // Direct pixel manipulation 368 + canvas.frameBuffer.fillRect(10, 5, 20, 8, RGBA.fromHex("#FF0000")) 369 + canvas.frameBuffer.drawText("Custom", 12, 7, RGBA.fromHex("#FFFFFF")) 370 + canvas.frameBuffer.setCell(x, y, char, fg, bg) 371 + ``` 372 + 373 + ## Constructs (VNode API) 374 + 375 + Declarative wrappers that create VNodes instead of direct instances. 376 + 377 + ```typescript 378 + import { Text, Box, Input, Select, instantiate, delegate } from "@opentui/core" 379 + 380 + // Create VNode tree 381 + const ui = Box( 382 + { border: true, padding: 1 }, 383 + Text({ content: "Hello" }), 384 + Input({ placeholder: "Type here..." }), 385 + ) 386 + 387 + // Instantiate onto renderer 388 + renderer.root.add(ui) 389 + 390 + // Delegate focus to nested element 391 + const form = delegate( 392 + { focus: "email-input" }, 393 + Box( 394 + {}, 395 + Text({ content: "Email:" }), 396 + Input({ id: "email-input", placeholder: "you@example.com" }), 397 + ), 398 + ) 399 + form.focus() // Focuses the input, not the box 400 + ``` 401 + 402 + ## Colors (RGBA) 403 + 404 + The `RGBA` class is exported from `@opentui/core` but works across **all frameworks** (Core, React, Solid). Use it for programmatic color manipulation. 405 + 406 + ### Creating Colors 407 + 408 + ```typescript 409 + import { RGBA, parseColor } from "@opentui/core" 410 + 411 + // From hex string (most common) 412 + RGBA.fromHex("#FF0000") // Full hex 413 + RGBA.fromHex("#F00") // Short hex 414 + 415 + // From integers (0-255 range) 416 + RGBA.fromInts(255, 0, 0, 255) // r, g, b, a - fully opaque red 417 + RGBA.fromInts(255, 0, 0, 128) // 50% transparent red 418 + RGBA.fromInts(0, 0, 0, 0) // Fully transparent 419 + 420 + // From normalized floats (0.0-1.0 range) 421 + RGBA.fromValues(1.0, 0.0, 0.0, 1.0) // Fully opaque red 422 + RGBA.fromValues(0.1, 0.1, 0.1, 0.7) // Dark gray, 70% opaque 423 + RGBA.fromValues(0.0, 0.5, 1.0, 1.0) // Light blue 424 + ``` 425 + 426 + ### Common Color Patterns 427 + 428 + ```typescript 429 + // Theme colors 430 + const primary = RGBA.fromHex("#7aa2f7") // Tokyo Night blue 431 + const background = RGBA.fromHex("#1a1a2e") 432 + const foreground = RGBA.fromHex("#c0caf5") 433 + const error = RGBA.fromHex("#f7768e") 434 + 435 + // Overlays and shadows 436 + const modalOverlay = RGBA.fromValues(0.0, 0.0, 0.0, 0.5) // 50% black 437 + const shadow = RGBA.fromInts(0, 0, 0, 77) // 30% black 438 + 439 + // Borders 440 + const activeBorder = RGBA.fromHex("#7aa2f7") 441 + const inactiveBorder = RGBA.fromInts(65, 72, 104, 255) 442 + ``` 443 + 444 + ### parseColor Utility 445 + 446 + ```typescript 447 + // Accepts multiple formats 448 + parseColor("#FF0000") // Hex string 449 + parseColor("red") // CSS color name 450 + parseColor("transparent") // Special values 451 + parseColor(RGBA.fromHex("#F00")) // Pass-through RGBA objects 452 + ``` 453 + 454 + ### When to Use Each Method 455 + 456 + | Method | Use When | 457 + |--------|----------| 458 + | `fromHex()` | Working with design specs, CSS colors, config files | 459 + | `fromInts()` | You have 8-bit values (0-255), common in graphics | 460 + | `fromValues()` | Doing color interpolation, animations, math | 461 + | `parseColor()` | Accepting user input or config that could be any format | 462 + 463 + ### Using RGBA in React/Solid 464 + 465 + ```tsx 466 + // Import from @opentui/core, use in any framework 467 + import { RGBA } from "@opentui/core" 468 + 469 + // React or Solid component 470 + function ThemedBox() { 471 + const bg = RGBA.fromHex("#1a1a2e") 472 + const border = RGBA.fromInts(122, 162, 247, 255) 473 + 474 + return ( 475 + <box backgroundColor={bg} borderColor={border} border> 476 + <text fg={RGBA.fromHex("#c0caf5")}>Works everywhere!</text> 477 + </box> 478 + ) 479 + } 480 + ``` 481 + 482 + Color props in React/Solid accept both string formats (`"#FF0000"`, `"red"`) and `RGBA` objects. 483 + 484 + ## Keyboard Input 485 + 486 + ```typescript 487 + import { type KeyEvent } from "@opentui/core" 488 + 489 + renderer.keyInput.on("keypress", (key: KeyEvent) => { 490 + console.log(key.name) // "a", "escape", "f1", etc. 491 + console.log(key.sequence) // Raw escape sequence 492 + console.log(key.ctrl) // Ctrl held 493 + console.log(key.shift) // Shift held 494 + console.log(key.meta) // Alt held 495 + console.log(key.option) // Option held (macOS) 496 + console.log(key.eventType) // "press" | "release" | "repeat" 497 + }) 498 + 499 + renderer.keyInput.on("paste", (event: PasteEvent) => { 500 + const text = decodePasteBytes(event.bytes) 501 + console.log("Pasted:", text) 502 + }) 503 + ``` 504 + 505 + ## Animation Timeline 506 + 507 + ```typescript 508 + import { Timeline, engine } from "@opentui/core" 509 + 510 + const timeline = new Timeline({ 511 + duration: 2000, 512 + loop: false, 513 + autoplay: true, 514 + }) 515 + 516 + timeline.add( 517 + { width: 0 }, 518 + { 519 + width: 50, 520 + duration: 1000, 521 + ease: "easeOutQuad", 522 + onUpdate: (anim) => { 523 + box.setWidth(anim.targets[0].width) 524 + }, 525 + }, 526 + ) 527 + 528 + engine.attach(renderer) 529 + engine.addTimeline(timeline) 530 + ``` 531 + 532 + ## Type Exports 533 + 534 + ```typescript 535 + import type { 536 + CliRenderer, 537 + CliRendererConfig, 538 + RenderContext, 539 + KeyEvent, 540 + Renderable, 541 + // ... and more 542 + } from "@opentui/core" 543 + ```
+168
skills/opentui/references/core/configuration.md
··· 1 + # Core Configuration 2 + 3 + ## Renderer Configuration 4 + 5 + ### createCliRenderer Options 6 + 7 + ```typescript 8 + import { createCliRenderer, ConsolePosition } from "@opentui/core" 9 + 10 + const renderer = await createCliRenderer({ 11 + // Rendering 12 + targetFPS: 60, // Target frames per second (default: 60) 13 + 14 + // Behavior 15 + exitOnCtrlC: true, // Exit on Ctrl+C (default: true) 16 + 17 + // Console overlay 18 + consoleOptions: { 19 + position: ConsolePosition.BOTTOM, // BOTTOM | TOP | LEFT | RIGHT 20 + sizePercent: 30, // Percentage of screen 21 + colorInfo: "#00FFFF", 22 + colorWarn: "#FFFF00", 23 + colorError: "#FF0000", 24 + colorDebug: "#888888", 25 + startInDebugMode: false, 26 + }, 27 + 28 + // Lifecycle 29 + onDestroy: () => { 30 + // Cleanup callback 31 + }, 32 + }) 33 + ``` 34 + 35 + ## Environment Variables 36 + 37 + OpenTUI respects several environment variables for configuration and debugging. 38 + 39 + ### Debug & Development 40 + 41 + | Variable | Type | Default | Description | 42 + |----------|------|---------|-------------| 43 + | `OTUI_DEBUG` | boolean | false | Enable debug mode, capture raw input | 44 + | `OTUI_DEBUG_FFI` | boolean | false | Debug logging for FFI bindings | 45 + | `OTUI_TRACE_FFI` | boolean | false | Tracing for FFI bindings | 46 + | `OTUI_SHOW_STATS` | boolean | false | Show debug overlay at startup | 47 + | `OTUI_DUMP_CAPTURES` | boolean | false | Dump captured output on exit | 48 + 49 + ### Console 50 + 51 + | Variable | Type | Default | Description | 52 + |----------|------|---------|-------------| 53 + | `OTUI_USE_CONSOLE` | boolean | true | Enable console capture | 54 + | `SHOW_CONSOLE` | boolean | false | Show console at startup | 55 + 56 + ### Rendering 57 + 58 + | Variable | Type | Default | Description | 59 + |----------|------|---------|-------------| 60 + | `OTUI_NO_NATIVE_RENDER` | boolean | false | Disable ANSI output (for debugging) | 61 + | `OTUI_USE_ALTERNATE_SCREEN` | boolean | true | Use alternate screen buffer | 62 + | `OTUI_OVERRIDE_STDOUT` | boolean | true | Override stdout stream | 63 + 64 + ### Terminal Capabilities 65 + 66 + | Variable | Type | Default | Description | 67 + |----------|------|---------|-------------| 68 + | `OPENTUI_NO_GRAPHICS` | boolean | false | Disable Kitty graphics protocol | 69 + | `OPENTUI_FORCE_UNICODE` | boolean | false | Force Mode 2026 Unicode support | 70 + | `OPENTUI_FORCE_WCWIDTH` | boolean | false | Use wcwidth for character width | 71 + | `OPENTUI_FORCE_NOZWJ` | boolean | false | Disable ZWJ emoji joining | 72 + | `OPENTUI_FORCE_EXPLICIT_WIDTH` | string | - | Force explicit width ("true"/"false") | 73 + 74 + ### Tree-sitter (Syntax Highlighting) 75 + 76 + | Variable | Type | Default | Description | 77 + |----------|------|---------|-------------| 78 + | `OTUI_TS_STYLE_WARN` | boolean | false | Warn on missing syntax styles | 79 + | `OTUI_TREE_SITTER_WORKER_PATH` | string | "" | Custom tree-sitter worker path | 80 + 81 + ### XDG Paths 82 + 83 + | Variable | Type | Default | Description | 84 + |----------|------|---------|-------------| 85 + | `XDG_CONFIG_HOME` | string | "" | User config directory | 86 + | `XDG_DATA_HOME` | string | "" | User data directory | 87 + 88 + ## Usage Examples 89 + 90 + ### Development Mode 91 + 92 + ```bash 93 + # Show debug overlay and console 94 + OTUI_SHOW_STATS=true SHOW_CONSOLE=true bun run src/index.ts 95 + 96 + # Debug FFI issues 97 + OTUI_DEBUG_FFI=true OTUI_TRACE_FFI=true bun run src/index.ts 98 + 99 + # Disable native rendering for testing 100 + OTUI_NO_NATIVE_RENDER=true bun run src/index.ts 101 + ``` 102 + 103 + ### Terminal Compatibility 104 + 105 + ```bash 106 + # Force wcwidth for problematic terminals 107 + OPENTUI_FORCE_WCWIDTH=true bun run src/index.ts 108 + 109 + # Disable graphics for SSH sessions 110 + OPENTUI_NO_GRAPHICS=true bun run src/index.ts 111 + ``` 112 + 113 + ## Project Setup 114 + 115 + ### package.json 116 + 117 + ```json 118 + { 119 + "name": "my-tui-app", 120 + "type": "module", 121 + "scripts": { 122 + "start": "bun run src/index.ts", 123 + "dev": "bun --watch run src/index.ts", 124 + "test": "bun test" 125 + }, 126 + "dependencies": { 127 + "@opentui/core": "latest" 128 + }, 129 + "devDependencies": { 130 + "@types/bun": "latest", 131 + "typescript": "latest" 132 + } 133 + } 134 + ``` 135 + 136 + ### tsconfig.json 137 + 138 + ```json 139 + { 140 + "compilerOptions": { 141 + "lib": ["ESNext"], 142 + "target": "ESNext", 143 + "module": "NodeNext", 144 + "moduleResolution": "NodeNext", 145 + "strict": true, 146 + "skipLibCheck": true, 147 + "noEmit": true, 148 + "types": ["bun-types"] 149 + }, 150 + "include": ["src/**/*"] 151 + } 152 + ``` 153 + 154 + > **Note**: OpenTUI uses `NodeNext` module resolution. All internal imports use `.js` extensions. If you use `bundler` resolution, imports still work but `NodeNext` is recommended for compatibility. 155 + 156 + ## Building Native Code 157 + 158 + Native code changes require rebuilding: 159 + 160 + ```bash 161 + # From repo root (if developing OpenTUI itself) 162 + bun run build 163 + 164 + # Zig is required for native compilation 165 + # Install: https://ziglang.org/learn/getting-started/ 166 + ``` 167 + 168 + **Note**: TypeScript changes do NOT require building. Bun runs TypeScript directly.
+393
skills/opentui/references/core/gotchas.md
··· 1 + # Core Gotchas 2 + 3 + ## Runtime Environment 4 + 5 + ### Use Bun, Not Node.js 6 + 7 + OpenTUI is built for Bun. Always use Bun commands: 8 + 9 + ```bash 10 + # CORRECT 11 + bun install @opentui/core 12 + bun run src/index.ts 13 + bun test 14 + 15 + # WRONG 16 + npm install @opentui/core 17 + node src/index.ts 18 + npx jest 19 + ``` 20 + 21 + ### Bun APIs to Use 22 + 23 + Prefer Bun's built-in APIs for your application code: 24 + 25 + ```typescript 26 + // CORRECT - Bun APIs 27 + Bun.serve({ ... }) // Instead of express 28 + Bun.$`ls -la` // Instead of execa 29 + import { Database } from "bun:sqlite" // Instead of better-sqlite3 30 + 31 + // WRONG - Node.js patterns 32 + import express from "express" 33 + ``` 34 + 35 + > **Note**: OpenTUI itself uses `node:fs` internally for file I/O (for broader compatibility), but your application code should still prefer Bun APIs where available. 36 + 37 + ### Avoid process.exit() 38 + 39 + **Never use `process.exit()` directly** - it prevents proper terminal cleanup and can leave the terminal in a broken state (alternate screen mode, raw input mode, etc.). 40 + 41 + ```typescript 42 + // WRONG - Terminal may be left in broken state 43 + if (error) { 44 + console.error("Fatal error") 45 + process.exit(1) 46 + } 47 + 48 + // CORRECT - Use renderer.destroy() for cleanup 49 + if (error) { 50 + console.error("Fatal error") 51 + await renderer.destroy() 52 + process.exit(1) // Only after destroy 53 + } 54 + 55 + // BETTER - Let destroy handle exit 56 + const renderer = await createCliRenderer({ 57 + exitOnCtrlC: true, // Handles Ctrl+C properly 58 + }) 59 + 60 + // For programmatic exit 61 + renderer.destroy() // Cleans up and exits 62 + ``` 63 + 64 + `renderer.destroy()` restores the terminal to its original state before exiting. 65 + 66 + ### Environment Variables 67 + 68 + Bun auto-loads `.env` files. Don't use dotenv: 69 + 70 + ```typescript 71 + // CORRECT 72 + const apiKey = process.env.API_KEY 73 + 74 + // WRONG 75 + import dotenv from "dotenv" 76 + dotenv.config() 77 + ``` 78 + 79 + ## Debugging TUIs 80 + 81 + ### Cannot See console.log Output 82 + 83 + OpenTUI captures console output for the debug overlay. You can't see logs in the terminal while the TUI is running. 84 + 85 + **Solutions:** 86 + 87 + 1. **Use the console overlay:** 88 + ```typescript 89 + const renderer = await createCliRenderer() 90 + renderer.console.show() 91 + console.log("This appears in the overlay") 92 + ``` 93 + 94 + 2. **Toggle with keyboard:** 95 + ```typescript 96 + renderer.keyInput.on("keypress", (key) => { 97 + if (key.name === "f12") { 98 + renderer.console.toggle() 99 + } 100 + }) 101 + ``` 102 + 103 + 3. **Write to a file:** 104 + ```typescript 105 + import { appendFileSync } from "node:fs" 106 + function debugLog(msg: string) { 107 + appendFileSync("debug.log", `${new Date().toISOString()} ${msg}\n`) 108 + } 109 + ``` 110 + 111 + 4. **Disable console capture:** 112 + ```bash 113 + OTUI_USE_CONSOLE=false bun run src/index.ts 114 + ``` 115 + 116 + ### Reproduce Issues in Tests 117 + 118 + Don't guess at bugs. Create a reproducible test: 119 + 120 + ```typescript 121 + import { test, expect } from "bun:test" 122 + import { createTestRenderer } from "@opentui/core/testing" 123 + 124 + test("reproduces the issue", async () => { 125 + const { renderer, snapshot } = await createTestRenderer({ 126 + width: 40, 127 + height: 10, 128 + }) 129 + 130 + // Setup that reproduces the bug 131 + const box = new BoxRenderable(renderer, { ... }) 132 + renderer.root.add(box) 133 + 134 + // Verify with snapshot 135 + expect(snapshot()).toMatchSnapshot() 136 + }) 137 + ``` 138 + 139 + ## Focus Management 140 + 141 + ### Components Must Be Focused 142 + 143 + Input components only receive keyboard input when focused: 144 + 145 + ```typescript 146 + const input = new InputRenderable(renderer, { 147 + id: "input", 148 + placeholder: "Type here...", 149 + }) 150 + 151 + renderer.root.add(input) 152 + 153 + // WRONG - input won't receive keystrokes 154 + // (no focus call) 155 + 156 + // CORRECT 157 + input.focus() 158 + ``` 159 + 160 + ### Focus in Nested Components 161 + 162 + When a component is inside a container, focus the component directly: 163 + 164 + ```typescript 165 + const container = new BoxRenderable(renderer, { id: "container" }) 166 + const input = new InputRenderable(renderer, { id: "input" }) 167 + container.add(input) 168 + renderer.root.add(container) 169 + 170 + // WRONG 171 + container.focus() 172 + 173 + // CORRECT 174 + input.focus() 175 + 176 + // Or use getRenderable 177 + container.getRenderable("input")?.focus() 178 + 179 + // Or use delegate (constructs) 180 + const form = delegate( 181 + { focus: "input" }, 182 + Box({}, Input({ id: "input" })), 183 + ) 184 + form.focus() // Routes to the input 185 + ``` 186 + 187 + ## Build Requirements 188 + 189 + ### Zig is Required 190 + 191 + Native code compilation requires Zig: 192 + 193 + ```bash 194 + # Install Zig first 195 + # macOS 196 + brew install zig 197 + 198 + # Linux 199 + # Download from https://ziglang.org/download/ 200 + 201 + # Then build 202 + bun run build 203 + ``` 204 + 205 + ### When to Build 206 + 207 + - **TypeScript changes**: NO build needed (Bun runs TS directly) 208 + - **Native code changes**: Build required 209 + 210 + ```bash 211 + # Only needed when changing native (Zig) code 212 + cd packages/core 213 + bun run build 214 + ``` 215 + 216 + ## Common Errors 217 + 218 + ### "Cannot read properties of undefined" 219 + 220 + Usually means a renderable wasn't added to the tree: 221 + 222 + ```typescript 223 + // WRONG - not added to tree 224 + const text = new TextRenderable(renderer, { content: "Hello" }) 225 + // text.someMethod() // May fail 226 + 227 + // CORRECT 228 + const text = new TextRenderable(renderer, { content: "Hello" }) 229 + renderer.root.add(text) 230 + text.someMethod() 231 + ``` 232 + 233 + ### Layout Not Updating 234 + 235 + Yoga layout is calculated lazily. Force a recalculation: 236 + 237 + ```typescript 238 + // After changing layout properties 239 + box.setWidth(newWidth) 240 + renderer.requestRender() 241 + ``` 242 + 243 + ### Text Overflow/Clipping 244 + 245 + Text doesn't wrap by default. Set explicit width: 246 + 247 + ```typescript 248 + // May overflow 249 + const text = new TextRenderable(renderer, { 250 + content: "Very long text that might overflow the terminal...", 251 + }) 252 + 253 + // Contained within width 254 + const text = new TextRenderable(renderer, { 255 + content: "Very long text that might overflow the terminal...", 256 + width: 40, // Will clip or wrap based on parent 257 + }) 258 + ``` 259 + 260 + ### Colors Not Showing 261 + 262 + Check terminal capability and color format: 263 + 264 + ```typescript 265 + // CORRECT formats 266 + fg: "#FF0000" // Hex 267 + fg: "red" // CSS color name 268 + fg: RGBA.fromHex("#FF0000") 269 + 270 + // WRONG 271 + fg: "FF0000" // Missing # 272 + fg: 0xFF0000 // Number (not supported) 273 + ``` 274 + 275 + ## Performance 276 + 277 + ### Avoid Frequent Re-renders 278 + 279 + Batch updates when possible: 280 + 281 + ```typescript 282 + // WRONG - multiple render calls 283 + item1.setContent("...") 284 + item2.setContent("...") 285 + item3.setContent("...") 286 + 287 + // BETTER - single render after all updates 288 + // (OpenTUI batches automatically, but be mindful) 289 + items.forEach((item, i) => { 290 + item.setContent(data[i]) 291 + }) 292 + ``` 293 + 294 + ### Minimize Tree Depth 295 + 296 + Deep nesting impacts layout calculation: 297 + 298 + ```typescript 299 + // Avoid unnecessary wrappers 300 + // WRONG 301 + Box({}, Box({}, Box({}, Text({ content: "Hello" })))) 302 + 303 + // CORRECT 304 + Box({}, Text({ content: "Hello" })) 305 + ``` 306 + 307 + ### Use display: none 308 + 309 + Hide elements instead of removing/re-adding: 310 + 311 + ```typescript 312 + // For toggling visibility 313 + element.setDisplay("none") // Hidden 314 + element.setDisplay("flex") // Visible 315 + 316 + // Instead of 317 + parent.remove(element) 318 + parent.add(element) 319 + ``` 320 + 321 + ## Testing 322 + 323 + ### Test Runner 324 + 325 + Use Bun's test runner: 326 + 327 + ```typescript 328 + import { test, expect, beforeEach, afterEach } from "bun:test" 329 + 330 + test("my test", () => { 331 + expect(1 + 1).toBe(2) 332 + }) 333 + ``` 334 + 335 + ### Test from Package Directories 336 + 337 + Run tests from the specific package directory: 338 + 339 + ```bash 340 + # CORRECT 341 + cd packages/core 342 + bun test 343 + 344 + # For native tests 345 + cd packages/core 346 + bun run test:native 347 + ``` 348 + 349 + ### Filter Tests 350 + 351 + ```bash 352 + # Bun test filter 353 + bun test --filter "component name" 354 + 355 + # Native test filter 356 + bun run test:native -Dtest-filter="test name" 357 + ``` 358 + 359 + ## Keyboard Handling 360 + 361 + ### Key Names 362 + 363 + Common key names for `KeyEvent.name`: 364 + 365 + ```typescript 366 + // Letters/numbers 367 + "a", "b", ..., "z" 368 + "1", "2", ..., "0" 369 + 370 + // Special keys 371 + "escape", "enter", "return", "tab", "backspace", "delete" 372 + "up", "down", "left", "right" 373 + "home", "end", "pageup", "pagedown" 374 + "f1", "f2", ..., "f12" 375 + "space" 376 + 377 + // Modifiers (check boolean properties) 378 + key.ctrl // Ctrl held 379 + key.shift // Shift held 380 + key.meta // Alt held 381 + key.option // Option held (macOS) 382 + ``` 383 + 384 + ### Key Event Types 385 + 386 + ```typescript 387 + renderer.keyInput.on("keypress", (key) => { 388 + // eventType: "press" | "release" | "repeat" 389 + if (key.eventType === "repeat") { 390 + // Key being held down 391 + } 392 + }) 393 + ```
+449
skills/opentui/references/core/patterns.md
··· 1 + # Core Patterns 2 + 3 + ## Composition Patterns 4 + 5 + ### Imperative Composition 6 + 7 + Create renderables and compose with `.add()`: 8 + 9 + ```typescript 10 + import { createCliRenderer, BoxRenderable, TextRenderable } from "@opentui/core" 11 + 12 + const renderer = await createCliRenderer() 13 + 14 + // Create parent 15 + const container = new BoxRenderable(renderer, { 16 + id: "container", 17 + flexDirection: "column", 18 + padding: 1, 19 + }) 20 + 21 + // Create children 22 + const header = new TextRenderable(renderer, { 23 + id: "header", 24 + content: "Header", 25 + fg: "#00FF00", 26 + }) 27 + 28 + const body = new TextRenderable(renderer, { 29 + id: "body", 30 + content: "Body content", 31 + }) 32 + 33 + // Compose tree 34 + container.add(header) 35 + container.add(body) 36 + renderer.root.add(container) 37 + ``` 38 + 39 + ### Declarative Composition (Constructs) 40 + 41 + Use VNode functions for cleaner composition: 42 + 43 + ```typescript 44 + import { createCliRenderer, Box, Text, Input, delegate } from "@opentui/core" 45 + 46 + const renderer = await createCliRenderer() 47 + 48 + // Compose as function calls 49 + const ui = Box( 50 + { flexDirection: "column", padding: 1 }, 51 + Text({ content: "Header", fg: "#00FF00" }), 52 + Box( 53 + { flexDirection: "row", gap: 2 }, 54 + Text({ content: "Name:" }), 55 + Input({ id: "name", placeholder: "Enter name..." }), 56 + ), 57 + ) 58 + 59 + renderer.root.add(ui) 60 + ``` 61 + 62 + ### Reusable Components 63 + 64 + Create factory functions for reusable UI pieces: 65 + 66 + ```typescript 67 + // Imperative factory 68 + function createLabeledInput( 69 + renderer: RenderContext, 70 + props: { id: string; label: string; placeholder: string } 71 + ) { 72 + const container = new BoxRenderable(renderer, { 73 + id: `${props.id}-container`, 74 + flexDirection: "row", 75 + gap: 1, 76 + }) 77 + 78 + container.add(new TextRenderable(renderer, { 79 + id: `${props.id}-label`, 80 + content: props.label, 81 + })) 82 + 83 + container.add(new InputRenderable(renderer, { 84 + id: `${props.id}-input`, 85 + placeholder: props.placeholder, 86 + width: 20, 87 + })) 88 + 89 + return container 90 + } 91 + 92 + // Declarative factory 93 + function LabeledInput(props: { id: string; label: string; placeholder: string }) { 94 + return delegate( 95 + { focus: `${props.id}-input` }, 96 + Box( 97 + { flexDirection: "row", gap: 1 }, 98 + Text({ content: props.label }), 99 + Input({ 100 + id: `${props.id}-input`, 101 + placeholder: props.placeholder, 102 + width: 20, 103 + }), 104 + ), 105 + ) 106 + } 107 + ``` 108 + 109 + ### Focus Delegation 110 + 111 + Route focus calls to nested elements: 112 + 113 + ```typescript 114 + import { delegate, Box, Input, Text } from "@opentui/core" 115 + 116 + const form = delegate( 117 + { 118 + focus: "email-input", // Route .focus() to this child 119 + blur: "email-input", // Route .blur() to this child 120 + }, 121 + Box( 122 + { border: true, padding: 1 }, 123 + Text({ content: "Email:" }), 124 + Input({ id: "email-input", placeholder: "you@example.com" }), 125 + ), 126 + ) 127 + 128 + // This focuses the input inside, not the box 129 + form.focus() 130 + ``` 131 + 132 + ## Event Handling 133 + 134 + ### Keyboard Events 135 + 136 + ```typescript 137 + const renderer = await createCliRenderer() 138 + 139 + // Global keyboard handler 140 + renderer.keyInput.on("keypress", (key) => { 141 + if (key.name === "escape") { 142 + renderer.destroy() 143 + process.exit(0) 144 + } 145 + 146 + if (key.ctrl && key.name === "c") { 147 + // Ctrl+C handling (if exitOnCtrlC is false) 148 + } 149 + 150 + if (key.name === "tab") { 151 + // Tab navigation 152 + focusNext() 153 + } 154 + }) 155 + 156 + // Paste events 157 + renderer.keyInput.on("paste", (event) => { 158 + const text = decodePasteBytes(event.bytes) 159 + currentInput?.setValue(currentInput.value + text) 160 + }) 161 + ``` 162 + 163 + ### Component Events 164 + 165 + ```typescript 166 + import { InputRenderable, InputRenderableEvents } from "@opentui/core" 167 + 168 + const input = new InputRenderable(renderer, { 169 + id: "search", 170 + placeholder: "Search...", 171 + }) 172 + 173 + input.on(InputRenderableEvents.CHANGE, (value) => { 174 + performSearch(value) 175 + }) 176 + 177 + // Select events 178 + const select = new SelectRenderable(renderer, { 179 + id: "menu", 180 + options: [...], 181 + }) 182 + 183 + select.on(SelectRenderableEvents.ITEM_SELECTED, (index, option) => { 184 + handleSelection(option) 185 + }) 186 + 187 + select.on(SelectRenderableEvents.SELECTION_CHANGED, (index, option) => { 188 + showPreview(option) 189 + }) 190 + ``` 191 + 192 + ### Mouse Events 193 + 194 + ```typescript 195 + const button = new BoxRenderable(renderer, { 196 + id: "button", 197 + border: true, 198 + onMouseDown: (event) => { 199 + button.setBackgroundColor("#444444") 200 + }, 201 + onMouseUp: (event) => { 202 + button.setBackgroundColor("#222222") 203 + handleClick() 204 + }, 205 + onMouseMove: (event) => { 206 + // Hover effect 207 + }, 208 + }) 209 + ``` 210 + 211 + ## State Management 212 + 213 + ### Local State 214 + 215 + Manage state in closures or objects: 216 + 217 + ```typescript 218 + // Closure-based state 219 + function createCounter(renderer: RenderContext) { 220 + let count = 0 221 + 222 + const display = new TextRenderable(renderer, { 223 + id: "count", 224 + content: `Count: ${count}`, 225 + }) 226 + 227 + const increment = () => { 228 + count++ 229 + display.setContent(`Count: ${count}`) 230 + } 231 + 232 + return { display, increment } 233 + } 234 + 235 + // Class-based state 236 + class CounterWidget { 237 + private count = 0 238 + private display: TextRenderable 239 + 240 + constructor(renderer: RenderContext) { 241 + this.display = new TextRenderable(renderer, { 242 + id: "count", 243 + content: this.formatCount(), 244 + }) 245 + } 246 + 247 + private formatCount() { 248 + return `Count: ${this.count}` 249 + } 250 + 251 + increment() { 252 + this.count++ 253 + this.display.setContent(this.formatCount()) 254 + } 255 + 256 + getRenderable() { 257 + return this.display 258 + } 259 + } 260 + ``` 261 + 262 + ### Focus Management 263 + 264 + Track and manage focus across components: 265 + 266 + ```typescript 267 + class FocusManager { 268 + private focusables: Renderable[] = [] 269 + private currentIndex = 0 270 + 271 + register(renderable: Renderable) { 272 + this.focusables.push(renderable) 273 + } 274 + 275 + focusNext() { 276 + this.focusables[this.currentIndex]?.blur() 277 + this.currentIndex = (this.currentIndex + 1) % this.focusables.length 278 + this.focusables[this.currentIndex]?.focus() 279 + } 280 + 281 + focusPrevious() { 282 + this.focusables[this.currentIndex]?.blur() 283 + this.currentIndex = (this.currentIndex - 1 + this.focusables.length) % this.focusables.length 284 + this.focusables[this.currentIndex]?.focus() 285 + } 286 + } 287 + 288 + // Usage 289 + const focusManager = new FocusManager() 290 + focusManager.register(input1) 291 + focusManager.register(input2) 292 + focusManager.register(select1) 293 + 294 + renderer.keyInput.on("keypress", (key) => { 295 + if (key.name === "tab") { 296 + key.shift ? focusManager.focusPrevious() : focusManager.focusNext() 297 + } 298 + }) 299 + ``` 300 + 301 + ## Lifecycle Patterns 302 + 303 + ### Cleanup 304 + 305 + Always clean up resources: 306 + 307 + ```typescript 308 + const renderer = await createCliRenderer() 309 + 310 + // Track intervals/timeouts 311 + const intervals: Timer[] = [] 312 + 313 + intervals.push(setInterval(() => { 314 + updateClock() 315 + }, 1000)) 316 + 317 + // Cleanup on exit 318 + process.on("SIGINT", () => { 319 + intervals.forEach(clearInterval) 320 + renderer.destroy() 321 + process.exit(0) 322 + }) 323 + 324 + // Or use onDestroy callback 325 + const renderer = await createCliRenderer({ 326 + onDestroy: () => { 327 + intervals.forEach(clearInterval) 328 + }, 329 + }) 330 + ``` 331 + 332 + ### Dynamic Updates 333 + 334 + Update UI based on external data: 335 + 336 + ```typescript 337 + async function createDashboard(renderer: RenderContext) { 338 + const statsText = new TextRenderable(renderer, { 339 + id: "stats", 340 + content: "Loading...", 341 + }) 342 + 343 + // Poll for updates 344 + const updateStats = async () => { 345 + const data = await fetchStats() 346 + statsText.setContent(`CPU: ${data.cpu}% | Memory: ${data.memory}%`) 347 + } 348 + 349 + // Initial load 350 + await updateStats() 351 + 352 + // Periodic updates 353 + setInterval(updateStats, 5000) 354 + 355 + return statsText 356 + } 357 + ``` 358 + 359 + ## Layout Patterns 360 + 361 + ### Responsive Layout 362 + 363 + Adapt to terminal size: 364 + 365 + ```typescript 366 + const renderer = await createCliRenderer() 367 + 368 + const mainPanel = new BoxRenderable(renderer, { 369 + id: "main", 370 + width: "100%", 371 + height: "100%", 372 + flexDirection: renderer.width > 80 ? "row" : "column", 373 + }) 374 + 375 + // Listen for resize 376 + process.stdout.on("resize", () => { 377 + mainPanel.setFlexDirection(renderer.width > 80 ? "row" : "column") 378 + }) 379 + ``` 380 + 381 + ### Split Panels 382 + 383 + ```typescript 384 + function createSplitView(renderer: RenderContext, ratio = 0.3) { 385 + const container = new BoxRenderable(renderer, { 386 + id: "split", 387 + flexDirection: "row", 388 + width: "100%", 389 + height: "100%", 390 + }) 391 + 392 + const left = new BoxRenderable(renderer, { 393 + id: "left", 394 + width: `${ratio * 100}%`, 395 + border: true, 396 + }) 397 + 398 + const right = new BoxRenderable(renderer, { 399 + id: "right", 400 + flexGrow: 1, 401 + border: true, 402 + }) 403 + 404 + container.add(left) 405 + container.add(right) 406 + 407 + return { container, left, right } 408 + } 409 + ``` 410 + 411 + ## Debugging Patterns 412 + 413 + ### Console Overlay 414 + 415 + Use the built-in console for debugging: 416 + 417 + ```typescript 418 + const renderer = await createCliRenderer({ 419 + consoleOptions: { 420 + startInDebugMode: true, 421 + }, 422 + }) 423 + 424 + // Show console 425 + renderer.console.show() 426 + 427 + // All console methods work 428 + console.log("Debug info") 429 + console.warn("Warning") 430 + console.error("Error") 431 + 432 + // Toggle with keyboard 433 + renderer.keyInput.on("keypress", (key) => { 434 + if (key.name === "f12") { 435 + renderer.console.toggle() 436 + } 437 + }) 438 + ``` 439 + 440 + ### State Inspection 441 + 442 + ```typescript 443 + function debugState(label: string, state: unknown) { 444 + console.log(`[${label}]`, JSON.stringify(state, null, 2)) 445 + } 446 + 447 + // In your update logic 448 + debugState("form", { name: nameInput.value, email: emailInput.value }) 449 + ```
+617
skills/opentui/references/keyboard/REFERENCE.md
··· 1 + # Keyboard Input Handling 2 + 3 + How to handle keyboard input in OpenTUI applications. 4 + 5 + ## Overview 6 + 7 + OpenTUI provides keyboard input handling through: 8 + - **Core**: `renderer.keyInput` EventEmitter 9 + - **React**: `useKeyboard()` hook 10 + - **Solid**: `useKeyboard()` hook 11 + 12 + ## When to Use 13 + 14 + Use this reference when you need keyboard shortcuts, focus-aware input handling, or custom keybindings. 15 + 16 + ## KeyEvent Object 17 + 18 + All keyboard handlers receive a `KeyEvent` object: 19 + 20 + ```typescript 21 + interface KeyEvent { 22 + name: string // Key name: "a", "escape", "f1", etc. 23 + sequence: string // Raw escape sequence 24 + ctrl: boolean // Ctrl modifier held 25 + shift: boolean // Shift modifier held 26 + meta: boolean // Alt modifier held 27 + option: boolean // Option modifier held (macOS) 28 + eventType: "press" | "release" | "repeat" 29 + repeated: boolean // Key is being held (repeat event) 30 + } 31 + ``` 32 + 33 + ## Basic Usage 34 + 35 + ### Core 36 + 37 + ```typescript 38 + import { createCliRenderer, type KeyEvent } from "@opentui/core" 39 + 40 + const renderer = await createCliRenderer() 41 + 42 + renderer.keyInput.on("keypress", (key: KeyEvent) => { 43 + if (key.name === "escape") { 44 + renderer.destroy() 45 + return 46 + } 47 + 48 + if (key.ctrl && key.name === "s") { 49 + saveDocument() 50 + } 51 + }) 52 + ``` 53 + 54 + ### React 55 + 56 + ```tsx 57 + import { useKeyboard, useRenderer } from "@opentui/react" 58 + 59 + function App() { 60 + const renderer = useRenderer() 61 + useKeyboard((key) => { 62 + if (key.name === "escape") { 63 + renderer.destroy() 64 + } 65 + }) 66 + 67 + return <text>Press ESC to exit</text> 68 + } 69 + ``` 70 + 71 + 72 + ### Solid 73 + 74 + ```tsx 75 + import { useKeyboard, useRenderer } from "@opentui/solid" 76 + 77 + function App() { 78 + const renderer = useRenderer() 79 + useKeyboard((key) => { 80 + if (key.name === "escape") { 81 + renderer.destroy() 82 + } 83 + }) 84 + 85 + return <text>Press ESC to exit</text> 86 + } 87 + ``` 88 + 89 + ## Key Names 90 + 91 + ### Alphabetic Keys 92 + 93 + Lowercase: `a`, `b`, `c`, ... `z` 94 + 95 + With Shift: Check `key.shift && key.name === "a"` for uppercase 96 + 97 + ### Numeric Keys 98 + 99 + `0`, `1`, `2`, ... `9` 100 + 101 + ### Function Keys 102 + 103 + `f1`, `f2`, `f3`, ... `f12` 104 + 105 + ### Special Keys 106 + 107 + | Key Name | Description | 108 + |----------|-------------| 109 + | `escape` | Escape key | 110 + | `enter` | Enter/Return | 111 + | `return` | Enter/Return (alias) | 112 + | `tab` | Tab key | 113 + | `backspace` | Backspace | 114 + | `delete` | Delete key | 115 + | `space` | Spacebar | 116 + 117 + ### Arrow Keys 118 + 119 + | Key Name | Description | 120 + |----------|-------------| 121 + | `up` | Up arrow | 122 + | `down` | Down arrow | 123 + | `left` | Left arrow | 124 + | `right` | Right arrow | 125 + 126 + ### Navigation Keys 127 + 128 + | Key Name | Description | 129 + |----------|-------------| 130 + | `home` | Home key | 131 + | `end` | End key | 132 + | `pageup` | Page Up | 133 + | `pagedown` | Page Down | 134 + | `insert` | Insert key | 135 + 136 + ## Modifier Keys 137 + 138 + Check modifier properties on `KeyEvent`: 139 + 140 + ```typescript 141 + renderer.keyInput.on("keypress", (key) => { 142 + if (key.ctrl && key.name === "c") { 143 + // Ctrl+C 144 + } 145 + 146 + if (key.shift && key.name === "tab") { 147 + // Shift+Tab 148 + } 149 + 150 + if (key.meta && key.name === "s") { 151 + // Alt+S (meta = Alt on most systems) 152 + } 153 + 154 + if (key.option && key.name === "a") { 155 + // Option+A (macOS) 156 + } 157 + }) 158 + ``` 159 + 160 + ### Modifier Combinations 161 + 162 + ```typescript 163 + // Ctrl+Shift+S 164 + if (key.ctrl && key.shift && key.name === "s") { 165 + saveAs() 166 + } 167 + 168 + // Ctrl+Alt+Delete (careful with system shortcuts!) 169 + if (key.ctrl && key.meta && key.name === "delete") { 170 + // ... 171 + } 172 + ``` 173 + 174 + ## Event Types 175 + 176 + ### Press Events (Default) 177 + 178 + Normal key press: 179 + 180 + ```typescript 181 + renderer.keyInput.on("keypress", (key) => { 182 + if (key.eventType === "press") { 183 + // Initial key press 184 + } 185 + }) 186 + ``` 187 + 188 + ### Repeat Events 189 + 190 + Key held down: 191 + 192 + ```typescript 193 + renderer.keyInput.on("keypress", (key) => { 194 + if (key.eventType === "repeat" || key.repeated) { 195 + // Key is being held 196 + } 197 + }) 198 + ``` 199 + 200 + ### Release Events 201 + 202 + Key released (opt-in): 203 + 204 + ```tsx 205 + // React 206 + useKeyboard( 207 + (key) => { 208 + if (key.eventType === "release") { 209 + // Key released 210 + } 211 + }, 212 + { release: true } // Enable release events 213 + ) 214 + 215 + // Solid 216 + useKeyboard( 217 + (key) => { 218 + if (key.eventType === "release") { 219 + // Key released 220 + } 221 + }, 222 + { release: true } 223 + ) 224 + ``` 225 + 226 + ## Patterns 227 + 228 + ### Navigation Menu 229 + 230 + ```tsx 231 + function Menu() { 232 + const [selectedIndex, setSelectedIndex] = useState(0) 233 + const items = ["Home", "Settings", "Help", "Quit"] 234 + 235 + useKeyboard((key) => { 236 + switch (key.name) { 237 + case "up": 238 + case "k": 239 + setSelectedIndex(i => Math.max(0, i - 1)) 240 + break 241 + case "down": 242 + case "j": 243 + setSelectedIndex(i => Math.min(items.length - 1, i + 1)) 244 + break 245 + case "enter": 246 + handleSelect(items[selectedIndex]) 247 + break 248 + } 249 + }) 250 + 251 + return ( 252 + <box flexDirection="column"> 253 + {items.map((item, i) => ( 254 + <text 255 + key={item} 256 + fg={i === selectedIndex ? "#00FF00" : "#FFFFFF"} 257 + > 258 + {i === selectedIndex ? "> " : " "}{item} 259 + </text> 260 + ))} 261 + </box> 262 + ) 263 + } 264 + ``` 265 + 266 + ### Modal Escape 267 + 268 + ```tsx 269 + function Modal({ onClose, children }) { 270 + useKeyboard((key) => { 271 + if (key.name === "escape") { 272 + onClose() 273 + } 274 + }) 275 + 276 + return ( 277 + <box border padding={2}> 278 + {children} 279 + </box> 280 + ) 281 + } 282 + ``` 283 + 284 + ### Vim-style Modes 285 + 286 + ```tsx 287 + function Editor() { 288 + const [mode, setMode] = useState<"normal" | "insert">("normal") 289 + const [content, setContent] = useState("") 290 + 291 + useKeyboard((key) => { 292 + if (mode === "normal") { 293 + switch (key.name) { 294 + case "i": 295 + setMode("insert") 296 + break 297 + case "escape": 298 + // Already in normal mode 299 + break 300 + case "j": 301 + moveCursorDown() 302 + break 303 + case "k": 304 + moveCursorUp() 305 + break 306 + } 307 + } else if (mode === "insert") { 308 + if (key.name === "escape") { 309 + setMode("normal") 310 + } 311 + // Input component handles text in insert mode 312 + } 313 + }) 314 + 315 + return ( 316 + <box flexDirection="column"> 317 + <text>Mode: {mode}</text> 318 + <textarea 319 + value={content} 320 + onChange={setContent} 321 + focused={mode === "insert"} 322 + /> 323 + </box> 324 + ) 325 + } 326 + ``` 327 + 328 + ### Game Controls 329 + 330 + ```tsx 331 + function Game() { 332 + const [pressed, setPressed] = useState(new Set<string>()) 333 + 334 + useKeyboard( 335 + (key) => { 336 + setPressed(keys => { 337 + const newKeys = new Set(keys) 338 + if (key.eventType === "release") { 339 + newKeys.delete(key.name) 340 + } else { 341 + newKeys.add(key.name) 342 + } 343 + return newKeys 344 + }) 345 + }, 346 + { release: true } 347 + ) 348 + 349 + // Game logic uses pressed set 350 + useEffect(() => { 351 + if (pressed.has("up") || pressed.has("w")) { 352 + moveUp() 353 + } 354 + if (pressed.has("down") || pressed.has("s")) { 355 + moveDown() 356 + } 357 + }, [pressed]) 358 + 359 + return <text>WASD or arrows to move</text> 360 + } 361 + ``` 362 + 363 + ### Keyboard Shortcuts Help 364 + 365 + ```tsx 366 + function ShortcutsHelp() { 367 + const shortcuts = [ 368 + { keys: "Ctrl+S", action: "Save" }, 369 + { keys: "Ctrl+Q", action: "Quit" }, 370 + { keys: "Ctrl+F", action: "Find" }, 371 + { keys: "Tab", action: "Next field" }, 372 + { keys: "Shift+Tab", action: "Previous field" }, 373 + ] 374 + 375 + return ( 376 + <box border title="Keyboard Shortcuts" padding={1}> 377 + {shortcuts.map(({ keys, action }) => ( 378 + <box key={keys} flexDirection="row"> 379 + <text width={15} fg="#00FFFF">{keys}</text> 380 + <text>{action}</text> 381 + </box> 382 + ))} 383 + </box> 384 + ) 385 + } 386 + ``` 387 + 388 + ## Paste Events 389 + 390 + Handle pasted content. Paste events deliver raw bytes, not decoded text. 391 + 392 + ### PasteEvent Object 393 + 394 + ```typescript 395 + import { type PasteEvent } from "@opentui/core" 396 + 397 + interface PasteEvent { 398 + type: "paste" // Always "paste" 399 + bytes: Uint8Array // Raw pasted bytes 400 + metadata?: PasteMetadata // Optional metadata 401 + preventDefault(): void // Prevent default paste handling 402 + defaultPrevented: boolean // Whether preventDefault was called 403 + } 404 + 405 + interface PasteMetadata { 406 + mimeType?: string // MIME type if available 407 + kind?: PasteKind // Paste kind 408 + } 409 + ``` 410 + 411 + ### Decoding Paste Bytes 412 + 413 + Use `decodePasteBytes` to convert raw bytes to a string, and `stripAnsiSequences` to remove ANSI escape codes: 414 + 415 + ```typescript 416 + import { decodePasteBytes, stripAnsiSequences } from "@opentui/core" 417 + 418 + const text = decodePasteBytes(event.bytes) // Decode UTF-8 419 + const clean = stripAnsiSequences(decodePasteBytes(event.bytes)) // Decode + strip ANSI 420 + ``` 421 + 422 + ### Core 423 + 424 + ```typescript 425 + import { type PasteEvent, decodePasteBytes } from "@opentui/core" 426 + 427 + renderer.keyInput.on("paste", (event: PasteEvent) => { 428 + const text = decodePasteBytes(event.bytes) 429 + console.log("Pasted:", text) 430 + }) 431 + ``` 432 + 433 + ### Solid 434 + 435 + Solid provides a dedicated `usePaste` hook: 436 + 437 + ```tsx 438 + import { usePaste } from "@opentui/solid" 439 + import { decodePasteBytes } from "@opentui/core" 440 + 441 + function App() { 442 + usePaste((event) => { 443 + const text = decodePasteBytes(event.bytes) 444 + console.log("Pasted:", text) 445 + }) 446 + 447 + return <text>Paste something</text> 448 + } 449 + ``` 450 + 451 + > **Note**: `usePaste` is **Solid-only**. React does not have this hook - handle paste via the Core event emitter or input component's `onChange`. 452 + 453 + ## Text Selection 454 + 455 + Text selection is renderer-managed. The renderer owns a single `Selection` object, walks the renderable tree to find selectable children, and emits a `"selection"` event when the user finishes selecting (mouse-up). The `Selection` object aggregates text from all selected renderables automatically. 456 + 457 + ### Making Renderables Selectable 458 + 459 + A renderable must have `selectable` set to `true` to participate in selection. Text-based renderables (`TextRenderable`, `TextareaRenderable`, `ASCIIFontRenderable`, `TextTableRenderable`) support this: 460 + 461 + ```tsx 462 + // React / Solid 463 + <text selectable>This text can be selected</text> 464 + 465 + // Core 466 + const text = new TextRenderable(renderer, { 467 + id: "label", 468 + content: "This text can be selected", 469 + selectable: true, 470 + }) 471 + ``` 472 + 473 + ### Copy-on-Selection (Core) 474 + 475 + Listen to the renderer's `"selection"` event. The `Selection` object's `getSelectedText()` returns text aggregated from all selected renderables in reading order: 476 + 477 + ```typescript 478 + import type { Selection } from "@opentui/core" 479 + 480 + renderer.on("selection", (selection: Selection) => { 481 + const text = selection.getSelectedText() 482 + if (text) { 483 + renderer.copyToClipboardOSC52(text) 484 + } 485 + }) 486 + ``` 487 + 488 + > **Important**: Call `selection.getSelectedText()` on the `Selection` object from the event -- not `renderer.root.getSelectedText()`. Individual renderables only return their own selected text. The `Selection` object aggregates across the tree. 489 + 490 + ### Copy-on-Selection (Solid) 491 + 492 + ```tsx 493 + import { useSelectionHandler } from "@opentui/solid" 494 + 495 + function App() { 496 + useSelectionHandler((selection) => { 497 + const text = selection.getSelectedText() 498 + if (text) { 499 + renderer.copyToClipboardOSC52(text) 500 + } 501 + }) 502 + 503 + return <text selectable>Select this text</text> 504 + } 505 + ``` 506 + 507 + > **Note**: `useSelectionHandler` is **Solid-only**. React does not have this hook -- use the Core `renderer.on("selection", ...)` event. 508 + 509 + ### Selection Object 510 + 511 + The `Selection` object passed to the event callback: 512 + 513 + ```typescript 514 + selection.getSelectedText() // Aggregated text from all selected renderables 515 + selection.bounds // { startX, startY, endX, endY } bounding rect 516 + selection.selectedRenderables // Renderable[] with active selections 517 + selection.isActive // Whether selection is still active 518 + ``` 519 + 520 + Individual renderables also expose: 521 + 522 + ```typescript 523 + renderable.hasSelection() // Does this renderable have selected text? 524 + renderable.getSelectedText() // Selected text in this renderable only 525 + ``` 526 + 527 + ### How Selection Traversal Works 528 + 529 + When the user drags to select, the renderer: 530 + 1. Identifies the selection container (common ancestor of start and end points) 531 + 2. Walks all `selectable` descendants within the selection bounds 532 + 3. Calls `onSelectionChanged(selection)` on each, which computes local selection 533 + 4. Tracks which renderables have active selections in `selection.selectedRenderables` 534 + 535 + This means selection works across multiple renderables. Dragging across two `<text selectable>` elements selects text in both, and `selection.getSelectedText()` joins them with newlines. 536 + 537 + ## Clipboard API (OSC 52) 538 + 539 + Copy text to the system clipboard using OSC 52 escape sequences. Works over SSH and in most modern terminal emulators. 540 + 541 + ```typescript 542 + // Copy to clipboard 543 + const success = renderer.copyToClipboardOSC52("text to copy") 544 + 545 + // Check if OSC 52 is supported 546 + if (renderer.isOsc52Supported()) { 547 + renderer.copyToClipboardOSC52("Hello!") 548 + } 549 + 550 + // Clear clipboard 551 + renderer.clearClipboardOSC52() 552 + 553 + // Target specific clipboard (X11) 554 + import { ClipboardTarget } from "@opentui/core" 555 + renderer.copyToClipboardOSC52("text", ClipboardTarget.Primary) // X11 primary 556 + renderer.copyToClipboardOSC52("text", ClipboardTarget.Clipboard) // System clipboard (default) 557 + ``` 558 + 559 + ## Focus and Input Components 560 + 561 + Input components (`<input>`, `<textarea>`, `<select>`) capture keyboard events when focused: 562 + 563 + ```tsx 564 + <input focused /> // Receives keyboard input 565 + 566 + // Global useKeyboard still fires, but input consumes characters 567 + ``` 568 + 569 + To prevent conflicts, check if an input is focused before handling global shortcuts: 570 + 571 + ```tsx 572 + function App() { 573 + const renderer = useRenderer() 574 + const [inputFocused, setInputFocused] = useState(false) 575 + 576 + useKeyboard((key) => { 577 + if (inputFocused) return // Let input handle it 578 + 579 + // Global shortcuts 580 + if (key.name === "escape") { 581 + renderer.destroy() 582 + } 583 + }) 584 + 585 + return ( 586 + <input 587 + focused={inputFocused} 588 + onFocus={() => setInputFocused(true)} 589 + onBlur={() => setInputFocused(false)} 590 + /> 591 + ) 592 + } 593 + ``` 594 + 595 + ## Gotchas 596 + 597 + ### Terminal Limitations 598 + 599 + Some key combinations are captured by the terminal or OS: 600 + - `Ctrl+C` often sends SIGINT (use `exitOnCtrlC: false` to handle) 601 + - `Ctrl+Z` suspends the process 602 + - Some function keys may be intercepted 603 + 604 + ### SSH and Remote Sessions 605 + 606 + Key detection may vary over SSH. Test on target environments. 607 + 608 + ### Multiple Handlers 609 + 610 + Multiple `useKeyboard` calls all receive events. Coordinate handlers to prevent conflicts. 611 + 612 + ## See Also 613 + 614 + - [React API](../react/api.md) - `useKeyboard` hook reference 615 + - [Solid API](../solid/api.md) - `useKeyboard` hook reference 616 + - [Input Components](../components/inputs.md) - Focus management with input, textarea, select 617 + - [Testing](../testing/REFERENCE.md) - Simulating key presses in tests
+337
skills/opentui/references/layout/REFERENCE.md
··· 1 + # OpenTUI Layout System 2 + 3 + OpenTUI uses the Yoga layout engine, providing CSS Flexbox-like capabilities for positioning and sizing components in the terminal. 4 + 5 + ## Overview 6 + 7 + Key concepts: 8 + - **Flexbox model**: Familiar CSS Flexbox properties 9 + - **Yoga engine**: Facebook's cross-platform layout engine 10 + - **Terminal units**: Dimensions are in character cells (columns x rows) 11 + - **Percentage support**: Relative sizing based on parent 12 + 13 + ## Flex Container Properties 14 + 15 + ### flexDirection 16 + 17 + Controls the main axis direction: 18 + 19 + ```tsx 20 + // Row (default) - children flow horizontally 21 + <box flexDirection="row"> 22 + <text>1</text> 23 + <text>2</text> 24 + <text>3</text> 25 + </box> 26 + // Output: 1 2 3 27 + 28 + // Column - children flow vertically 29 + <box flexDirection="column"> 30 + <text>1</text> 31 + <text>2</text> 32 + <text>3</text> 33 + </box> 34 + // Output: 35 + // 1 36 + // 2 37 + // 3 38 + 39 + // Reverse variants 40 + <box flexDirection="row-reverse">...</box> // 3 2 1 41 + <box flexDirection="column-reverse">...</box> // Bottom to top 42 + ``` 43 + 44 + ### justifyContent 45 + 46 + Aligns children along the main axis: 47 + 48 + ```tsx 49 + <box flexDirection="row" width={40} justifyContent="flex-start"> 50 + {/* Children at start (left for row) */} 51 + </box> 52 + 53 + <box flexDirection="row" width={40} justifyContent="flex-end"> 54 + {/* Children at end (right for row) */} 55 + </box> 56 + 57 + <box flexDirection="row" width={40} justifyContent="center"> 58 + {/* Children centered */} 59 + </box> 60 + 61 + <box flexDirection="row" width={40} justifyContent="space-between"> 62 + {/* First at start, last at end, rest evenly distributed */} 63 + </box> 64 + 65 + <box flexDirection="row" width={40} justifyContent="space-around"> 66 + {/* Equal space around each child */} 67 + </box> 68 + 69 + <box flexDirection="row" width={40} justifyContent="space-evenly"> 70 + {/* Equal space between all children and edges */} 71 + </box> 72 + ``` 73 + 74 + ### alignItems 75 + 76 + Aligns children along the cross axis: 77 + 78 + ```tsx 79 + <box flexDirection="row" height={10} alignItems="flex-start"> 80 + {/* Children at top */} 81 + </box> 82 + 83 + <box flexDirection="row" height={10} alignItems="flex-end"> 84 + {/* Children at bottom */} 85 + </box> 86 + 87 + <box flexDirection="row" height={10} alignItems="center"> 88 + {/* Children vertically centered */} 89 + </box> 90 + 91 + <box flexDirection="row" height={10} alignItems="stretch"> 92 + {/* Children stretch to fill height */} 93 + </box> 94 + 95 + <box flexDirection="row" height={10} alignItems="baseline"> 96 + {/* Children aligned by text baseline */} 97 + </box> 98 + ``` 99 + 100 + ### flexWrap 101 + 102 + Controls whether children wrap to new lines: 103 + 104 + ```tsx 105 + <box flexDirection="row" flexWrap="nowrap" width={20}> 106 + {/* Children overflow (default) */} 107 + </box> 108 + 109 + <box flexDirection="row" flexWrap="wrap" width={20}> 110 + {/* Children wrap to next row */} 111 + </box> 112 + 113 + <box flexDirection="row" flexWrap="wrap-reverse" width={20}> 114 + {/* Children wrap upward */} 115 + </box> 116 + ``` 117 + 118 + ### gap 119 + 120 + Space between children: 121 + 122 + ```tsx 123 + <box flexDirection="row" gap={2}> 124 + <text>A</text> 125 + <text>B</text> 126 + <text>C</text> 127 + </box> 128 + // Output: A B C (2 spaces between) 129 + ``` 130 + 131 + ## Flex Item Properties 132 + 133 + ### flexGrow 134 + 135 + How much a child should grow relative to siblings: 136 + 137 + ```tsx 138 + <box flexDirection="row" width={30}> 139 + <box flexGrow={1}><text>1</text></box> 140 + <box flexGrow={2}><text>2</text></box> 141 + <box flexGrow={1}><text>1</text></box> 142 + </box> 143 + // Widths: 7.5 | 15 | 7.5 (1:2:1 ratio) 144 + ``` 145 + 146 + ### flexShrink 147 + 148 + How much a child should shrink when space is limited: 149 + 150 + ```tsx 151 + <box flexDirection="row" width={20}> 152 + <box width={15} flexShrink={1}><text>Shrinks</text></box> 153 + <box width={15} flexShrink={0}><text>Fixed</text></box> 154 + </box> 155 + ``` 156 + 157 + ### flexBasis 158 + 159 + Initial size before growing/shrinking: 160 + 161 + ```tsx 162 + <box flexDirection="row"> 163 + <box flexBasis={20} flexGrow={1}>Starts at 20, can grow</box> 164 + <box flexBasis="50%">Half of parent</box> 165 + </box> 166 + ``` 167 + 168 + ### alignSelf 169 + 170 + Override parent's alignItems for this child: 171 + 172 + ```tsx 173 + <box flexDirection="row" height={10} alignItems="center"> 174 + <text>Centered</text> 175 + <text alignSelf="flex-start">Top</text> 176 + <text alignSelf="flex-end">Bottom</text> 177 + </box> 178 + ``` 179 + 180 + ## Dimensions 181 + 182 + ### Fixed Dimensions 183 + 184 + ```tsx 185 + <box width={40} height={10}> 186 + {/* Exactly 40 columns by 10 rows */} 187 + </box> 188 + ``` 189 + 190 + ### Percentage Dimensions 191 + 192 + Parent must have explicit size: 193 + 194 + ```tsx 195 + <box width="100%" height="100%"> 196 + <box width="50%" height="50%"> 197 + {/* Half of parent */} 198 + </box> 199 + </box> 200 + ``` 201 + 202 + ### Min/Max Constraints 203 + 204 + ```tsx 205 + <box 206 + minWidth={20} 207 + maxWidth={60} 208 + minHeight={5} 209 + maxHeight={20} 210 + > 211 + {/* Constrained sizing */} 212 + </box> 213 + ``` 214 + 215 + ## Spacing 216 + 217 + ### Padding (inside) 218 + 219 + ```tsx 220 + // All sides 221 + <box padding={2}>Content</box> 222 + 223 + // Individual sides 224 + <box 225 + paddingTop={1} 226 + paddingRight={2} 227 + paddingBottom={1} 228 + paddingLeft={2} 229 + > 230 + Content 231 + </box> 232 + ``` 233 + 234 + ### Margin (outside) 235 + 236 + ```tsx 237 + // All sides 238 + <box margin={1}>Content</box> 239 + 240 + // Individual sides 241 + <box 242 + marginTop={1} 243 + marginRight={2} 244 + marginBottom={1} 245 + marginLeft={2} 246 + > 247 + Content 248 + </box> 249 + ``` 250 + 251 + ## Positioning 252 + 253 + ### Relative (default) 254 + 255 + Element flows in normal document order: 256 + 257 + ```tsx 258 + <box position="relative"> 259 + {/* Normal flow */} 260 + </box> 261 + ``` 262 + 263 + ### Absolute 264 + 265 + Element positioned relative to nearest positioned ancestor: 266 + 267 + ```tsx 268 + <box position="relative" width="100%" height="100%"> 269 + <box 270 + position="absolute" 271 + left={10} 272 + top={5} 273 + width={20} 274 + height={5} 275 + > 276 + Positioned at (10, 5) 277 + </box> 278 + </box> 279 + ``` 280 + 281 + ### Position Properties 282 + 283 + ```tsx 284 + <box 285 + position="absolute" 286 + left={10} // From left edge 287 + top={5} // From top edge 288 + right={10} // From right edge 289 + bottom={5} // From bottom edge 290 + > 291 + Content 292 + </box> 293 + ``` 294 + 295 + ## Display 296 + 297 + ### Visibility Control 298 + 299 + ```tsx 300 + // Visible (default) 301 + <box display="flex">Visible</box> 302 + 303 + // Hidden (removed from layout) 304 + <box display="none">Hidden</box> 305 + ``` 306 + 307 + ## Overflow 308 + 309 + ```tsx 310 + <box overflow="visible"> 311 + {/* Content can extend beyond bounds (default) */} 312 + </box> 313 + 314 + <box overflow="hidden"> 315 + {/* Content clipped at bounds */} 316 + </box> 317 + 318 + <box overflow="scroll"> 319 + {/* Scrollable when content exceeds bounds */} 320 + </box> 321 + ``` 322 + 323 + ## Z-Index 324 + 325 + Control stacking order for overlapping elements: 326 + 327 + ```tsx 328 + <box position="relative"> 329 + <box position="absolute" zIndex={1}>Behind</box> 330 + <box position="absolute" zIndex={2}>In front</box> 331 + </box> 332 + ``` 333 + 334 + ## See Also 335 + 336 + - [Layout Patterns](./patterns.md) - Common layout recipes 337 + - [Components/Containers](../components/containers.md) - Box and ScrollBox details
+444
skills/opentui/references/layout/patterns.md
··· 1 + # Layout Patterns 2 + 3 + Common layout recipes for terminal user interfaces. 4 + 5 + ## Full-Screen App 6 + 7 + Fill the entire terminal: 8 + 9 + ```tsx 10 + function App() { 11 + return ( 12 + <box width="100%" height="100%"> 13 + {/* Content fills terminal */} 14 + </box> 15 + ) 16 + } 17 + ``` 18 + 19 + ## Header/Content/Footer 20 + 21 + Classic app layout: 22 + 23 + ```tsx 24 + function AppLayout() { 25 + return ( 26 + <box flexDirection="column" width="100%" height="100%"> 27 + {/* Header - fixed height */} 28 + <box height={3} borderStyle="single" borderBottom> 29 + <text>Header</text> 30 + </box> 31 + 32 + {/* Content - fills remaining space */} 33 + <box flexGrow={1}> 34 + <text>Main Content</text> 35 + </box> 36 + 37 + {/* Footer - fixed height */} 38 + <box height={1}> 39 + <text>Status: Ready</text> 40 + </box> 41 + </box> 42 + ) 43 + } 44 + ``` 45 + 46 + ## Sidebar Layout 47 + 48 + ```tsx 49 + function SidebarLayout() { 50 + return ( 51 + <box flexDirection="row" width="100%" height="100%"> 52 + {/* Sidebar - fixed width */} 53 + <box width={25} borderStyle="single" borderRight> 54 + <text>Sidebar</text> 55 + </box> 56 + 57 + {/* Main - fills remaining space */} 58 + <box flexGrow={1}> 59 + <text>Main Content</text> 60 + </box> 61 + </box> 62 + ) 63 + } 64 + ``` 65 + 66 + ## Resizable Sidebar 67 + 68 + Responsive based on terminal width: 69 + 70 + ```tsx 71 + function ResponsiveSidebar() { 72 + const dims = useTerminalDimensions() // React: useTerminalDimensions() 73 + const showSidebar = dims.width > 60 74 + const sidebarWidth = Math.min(30, Math.floor(dims.width * 0.3)) 75 + 76 + return ( 77 + <box flexDirection="row" width="100%" height="100%"> 78 + {showSidebar && ( 79 + <box width={sidebarWidth} border> 80 + <text>Sidebar</text> 81 + </box> 82 + )} 83 + <box flexGrow={1}> 84 + <text>Main</text> 85 + </box> 86 + </box> 87 + ) 88 + } 89 + ``` 90 + 91 + ## Centered Content 92 + 93 + ### Horizontally Centered 94 + 95 + ```tsx 96 + <box width="100%" justifyContent="center"> 97 + <box width={40}> 98 + <text>Centered horizontally</text> 99 + </box> 100 + </box> 101 + ``` 102 + 103 + ### Vertically Centered 104 + 105 + ```tsx 106 + <box height="100%" alignItems="center"> 107 + <text>Centered vertically</text> 108 + </box> 109 + ``` 110 + 111 + ### Both Axes 112 + 113 + ```tsx 114 + <box 115 + width="100%" 116 + height="100%" 117 + justifyContent="center" 118 + alignItems="center" 119 + > 120 + <box width={40} height={10} border> 121 + <text>Centered both ways</text> 122 + </box> 123 + </box> 124 + ``` 125 + 126 + ## Modal/Dialog 127 + 128 + Centered overlay: 129 + 130 + ```tsx 131 + function Modal({ children, visible }) { 132 + if (!visible) return null 133 + 134 + return ( 135 + <box 136 + position="absolute" 137 + left={0} 138 + top={0} 139 + width="100%" 140 + height="100%" 141 + justifyContent="center" 142 + alignItems="center" 143 + backgroundColor="rgba(0,0,0,0.5)" 144 + > 145 + <box 146 + width={50} 147 + height={15} 148 + border 149 + borderStyle="double" 150 + backgroundColor="#1a1a2e" 151 + padding={2} 152 + > 153 + {children} 154 + </box> 155 + </box> 156 + ) 157 + } 158 + ``` 159 + 160 + ## Grid Layout 161 + 162 + Using flexWrap: 163 + 164 + ```tsx 165 + function Grid({ items, columns = 3 }) { 166 + const itemWidth = `${Math.floor(100 / columns)}%` 167 + 168 + return ( 169 + <box flexDirection="row" flexWrap="wrap" width="100%"> 170 + {items.map((item, i) => ( 171 + <box key={i} width={itemWidth} padding={1}> 172 + <text>{item}</text> 173 + </box> 174 + ))} 175 + </box> 176 + ) 177 + } 178 + ``` 179 + 180 + ## Split Panels 181 + 182 + ### Horizontal Split 183 + 184 + ```tsx 185 + function HorizontalSplit({ ratio = 0.5 }) { 186 + return ( 187 + <box flexDirection="row" width="100%" height="100%"> 188 + <box width={`${ratio * 100}%`} border> 189 + <text>Left Panel</text> 190 + </box> 191 + <box flexGrow={1} border> 192 + <text>Right Panel</text> 193 + </box> 194 + </box> 195 + ) 196 + } 197 + ``` 198 + 199 + ### Vertical Split 200 + 201 + ```tsx 202 + function VerticalSplit({ ratio = 0.5 }) { 203 + return ( 204 + <box flexDirection="column" width="100%" height="100%"> 205 + <box height={`${ratio * 100}%`} border> 206 + <text>Top Panel</text> 207 + </box> 208 + <box flexGrow={1} border> 209 + <text>Bottom Panel</text> 210 + </box> 211 + </box> 212 + ) 213 + } 214 + ``` 215 + 216 + ## Form Layout 217 + 218 + Label + Input pairs: 219 + 220 + ```tsx 221 + function FormField({ label, children }) { 222 + return ( 223 + <box flexDirection="row" marginBottom={1}> 224 + <box width={15}> 225 + <text>{label}:</text> 226 + </box> 227 + <box flexGrow={1}> 228 + {children} 229 + </box> 230 + </box> 231 + ) 232 + } 233 + 234 + function LoginForm() { 235 + return ( 236 + <box flexDirection="column" padding={2} border width={50}> 237 + <FormField label="Username"> 238 + <input placeholder="Enter username" /> 239 + </FormField> 240 + <FormField label="Password"> 241 + <input placeholder="Enter password" /> 242 + </FormField> 243 + <box marginTop={2} justifyContent="flex-end"> 244 + <box border padding={1}> 245 + <text>Login</text> 246 + </box> 247 + </box> 248 + </box> 249 + ) 250 + } 251 + ``` 252 + 253 + ## Navigation Tabs 254 + 255 + ```tsx 256 + function TabBar({ tabs, activeIndex, onSelect }) { 257 + return ( 258 + <box flexDirection="row" borderBottom> 259 + {tabs.map((tab, i) => ( 260 + <box 261 + key={i} 262 + padding={1} 263 + backgroundColor={i === activeIndex ? "#333" : "transparent"} 264 + onMouseDown={() => onSelect(i)} 265 + > 266 + <text fg={i === activeIndex ? "#fff" : "#888"}> 267 + {tab} 268 + </text> 269 + </box> 270 + ))} 271 + </box> 272 + ) 273 + } 274 + ``` 275 + 276 + ## Sticky Footer 277 + 278 + Footer always at bottom: 279 + 280 + ```tsx 281 + function StickyFooterLayout() { 282 + return ( 283 + <box flexDirection="column" width="100%" height="100%"> 284 + {/* Content area */} 285 + <box flexGrow={1} flexDirection="column"> 286 + {/* Your content here */} 287 + <text>Content that might be short</text> 288 + </box> 289 + 290 + {/* Footer pushed to bottom */} 291 + <box height={1}> 292 + <text fg="#888">Press ? for help | q to quit</text> 293 + </box> 294 + </box> 295 + ) 296 + } 297 + ``` 298 + 299 + ## Absolute Positioning Overlay 300 + 301 + Tooltip or popup: 302 + 303 + ```tsx 304 + function Tooltip({ x, y, children }) { 305 + return ( 306 + <box 307 + position="absolute" 308 + left={x} 309 + top={y} 310 + border 311 + backgroundColor="#333" 312 + padding={1} 313 + zIndex={100} 314 + > 315 + {children} 316 + </box> 317 + ) 318 + } 319 + ``` 320 + 321 + ## Responsive Breakpoints 322 + 323 + Different layouts based on terminal size: 324 + 325 + ```tsx 326 + function ResponsiveApp() { 327 + const { width, height } = useTerminalDimensions() 328 + 329 + // Define breakpoints 330 + const isSmall = width < 60 331 + const isMedium = width >= 60 && width < 100 332 + const isLarge = width >= 100 333 + 334 + if (isSmall) { 335 + // Mobile-like: stacked layout 336 + return ( 337 + <box flexDirection="column"> 338 + <Navigation /> 339 + <Content /> 340 + </box> 341 + ) 342 + } 343 + 344 + if (isMedium) { 345 + // Tablet-like: sidebar + content 346 + return ( 347 + <box flexDirection="row"> 348 + <box width={20}><Navigation /></box> 349 + <box flexGrow={1}><Content /></box> 350 + </box> 351 + ) 352 + } 353 + 354 + // Large: full layout 355 + return ( 356 + <box flexDirection="row"> 357 + <box width={25}><Navigation /></box> 358 + <box flexGrow={1}><Content /></box> 359 + <box width={30}><Sidebar /></box> 360 + </box> 361 + ) 362 + } 363 + ``` 364 + 365 + ## Equal Height Columns 366 + 367 + ```tsx 368 + function EqualColumns() { 369 + return ( 370 + <box flexDirection="row" alignItems="stretch" height={20}> 371 + <box flexGrow={1} border> 372 + <text>Short content</text> 373 + </box> 374 + <box flexGrow={1} border> 375 + <text> 376 + Longer content that 377 + spans multiple lines 378 + and takes up space 379 + </text> 380 + </box> 381 + <box flexGrow={1} border> 382 + <text>Medium content</text> 383 + </box> 384 + </box> 385 + ) 386 + } 387 + ``` 388 + 389 + ## Spacing Utilities 390 + 391 + Consistent spacing patterns: 392 + 393 + ```tsx 394 + // Spacer component 395 + function Spacer({ size = 1 }) { 396 + return <box height={size} width={size} /> 397 + } 398 + 399 + // Divider component 400 + function Divider() { 401 + return <box height={1} width="100%" backgroundColor="#333" /> 402 + } 403 + 404 + // Usage 405 + <box flexDirection="column"> 406 + <text>Section 1</text> 407 + <Spacer size={2} /> 408 + <Divider /> 409 + <Spacer size={2} /> 410 + <text>Section 2</text> 411 + </box> 412 + ``` 413 + 414 + ### Axis Shorthand Props 415 + 416 + Use `paddingX`/`paddingY` and `marginX`/`marginY` for horizontal/vertical spacing: 417 + 418 + ```tsx 419 + // Horizontal padding (left + right) 420 + <box paddingX={4}> 421 + <text>4 chars padding left and right</text> 422 + </box> 423 + 424 + // Vertical padding (top + bottom) 425 + <box paddingY={2}> 426 + <text>2 lines padding top and bottom</text> 427 + </box> 428 + 429 + // Horizontal margin for centering-like effect 430 + <box marginX={10}> 431 + <text>Indented content</text> 432 + </box> 433 + 434 + // Combined for card-like spacing 435 + <box paddingX={3} paddingY={1} marginY={1} border> 436 + <text>Nicely spaced card</text> 437 + </box> 438 + ``` 439 + 440 + These are shorthand for: 441 + - `paddingX={n}` = `paddingLeft={n}` + `paddingRight={n}` 442 + - `paddingY={n}` = `paddingTop={n}` + `paddingBottom={n}` 443 + - `marginX={n}` = `marginLeft={n}` + `marginRight={n}` 444 + - `marginY={n}` = `marginTop={n}` + `marginBottom={n}`
+174
skills/opentui/references/react/REFERENCE.md
··· 1 + # OpenTUI React (@opentui/react) 2 + 3 + A React reconciler for building terminal user interfaces with familiar React patterns. Write TUIs using JSX, hooks, and component composition. 4 + 5 + ## Overview 6 + 7 + OpenTUI React provides: 8 + - **Custom reconciler**: React components render to OpenTUI renderables 9 + - **JSX intrinsics**: `<text>`, `<box>`, `<input>`, etc. 10 + - **Hooks**: `useKeyboard`, `useRenderer`, `useTimeline`, etc. 11 + - **Full React compatibility**: useState, useEffect, context, and more 12 + 13 + ## When to Use React 14 + 15 + Use the React reconciler when: 16 + - You're familiar with React patterns 17 + - You want declarative UI composition 18 + - You need React's ecosystem (context, state management libraries) 19 + - Building applications with complex state 20 + - Team knows React already 21 + 22 + ## When NOT to Use React 23 + 24 + | Scenario | Use Instead | 25 + |----------|-------------| 26 + | Maximum performance critical | `@opentui/core` (imperative) | 27 + | Fine-grained reactivity | `@opentui/solid` | 28 + | Smallest bundle size | `@opentui/core` | 29 + | Building a framework/library | `@opentui/core` | 30 + 31 + ## Quick Start 32 + 33 + ```bash 34 + bunx create-tui@latest -t react my-app 35 + cd my-app 36 + bun run src/index.tsx 37 + ``` 38 + 39 + The CLI creates the `my-app` directory for you - it must **not already exist**. 40 + 41 + **Agent guidance**: Always use autonomous mode with `-t <template>` flag. Never use interactive mode (`bunx create-tui@latest my-app` without `-t`) as it requires user prompts that agents cannot respond to. 42 + 43 + Or manual setup: 44 + 45 + ```bash 46 + mkdir my-tui && cd my-tui 47 + bun init 48 + bun install @opentui/react @opentui/core react 49 + ``` 50 + 51 + ```tsx 52 + import { createCliRenderer } from "@opentui/core" 53 + import { createRoot } from "@opentui/react" 54 + import { useState } from "react" 55 + 56 + function App() { 57 + const [count, setCount] = useState(0) 58 + 59 + return ( 60 + <box border padding={2}> 61 + <text>Count: {count}</text> 62 + <box 63 + border 64 + onMouseDown={() => setCount(c => c + 1)} 65 + > 66 + <text>Click me!</text> 67 + </box> 68 + </box> 69 + ) 70 + } 71 + 72 + const renderer = await createCliRenderer() 73 + createRoot(renderer).render(<App />) 74 + ``` 75 + 76 + ## Core Concepts 77 + 78 + ### JSX Elements 79 + 80 + React maps JSX intrinsic elements to OpenTUI renderables: 81 + 82 + ```tsx 83 + // These are not HTML elements! 84 + <text>Hello</text> // TextRenderable 85 + <box border>Content</box> // BoxRenderable 86 + <input placeholder="..." /> // InputRenderable 87 + <select options={[...]} /> // SelectRenderable 88 + ``` 89 + 90 + ### Text Modifiers 91 + 92 + Inside `<text>`, use modifier elements: 93 + 94 + ```tsx 95 + <text> 96 + <strong>Bold</strong>, <em>italic</em>, and <u>underlined</u> 97 + <span fg="red">Colored text</span> 98 + <br /> 99 + New line with <a href="https://example.com">link</a> 100 + </text> 101 + ``` 102 + 103 + ### Styling 104 + 105 + Two approaches to styling: 106 + 107 + ```tsx 108 + // Direct props 109 + <box backgroundColor="blue" padding={2} border> 110 + <text fg="#00FF00">Green text</text> 111 + </box> 112 + 113 + // Style prop 114 + <box style={{ backgroundColor: "blue", padding: 2, border: true }}> 115 + <text style={{ fg: "#00FF00" }}>Green text</text> 116 + </box> 117 + ``` 118 + 119 + ## Available Components 120 + 121 + ### Layout & Display 122 + - `<text>` - Styled text content 123 + - `<box>` - Container with borders and layout 124 + - `<scrollbox>` - Scrollable container 125 + - `<ascii-font>` - ASCII art text 126 + 127 + ### Input 128 + - `<input>` - Single-line text input 129 + - `<textarea>` - Multi-line text input 130 + - `<select>` - List selection 131 + - `<tab-select>` - Tab-based selection 132 + 133 + ### Code & Diff 134 + - `<code>` - Syntax-highlighted code 135 + - `<line-number>` - Code with line numbers 136 + - `<diff>` - Unified or split diff viewer 137 + 138 + ### Text Modifiers (inside `<text>`) 139 + - `<span>` - Inline styled text 140 + - `<strong>`, `<b>` - Bold 141 + - `<em>`, `<i>` - Italic 142 + - `<u>` - Underline 143 + - `<br>` - Line break 144 + - `<a>` - Link 145 + 146 + ## Essential Hooks 147 + 148 + ```tsx 149 + import { 150 + useRenderer, 151 + useKeyboard, 152 + useOnResize, 153 + useTerminalDimensions, 154 + useTimeline, 155 + } from "@opentui/react" 156 + ``` 157 + 158 + See [API Reference](./api.md) for detailed hook documentation. 159 + 160 + ## In This Reference 161 + 162 + - [Configuration](./configuration.md) - Project setup, tsconfig, bundling 163 + - [API](./api.md) - Components, hooks, createRoot 164 + - [Patterns](./patterns.md) - State management, keyboard handling, forms 165 + - [Gotchas](./gotchas.md) - Common issues, debugging, limitations 166 + 167 + ## See Also 168 + 169 + - [Core](../core/REFERENCE.md) - Underlying imperative API 170 + - [Solid](../solid/REFERENCE.md) - Alternative declarative approach 171 + - [Components](../components/REFERENCE.md) - Component reference by category 172 + - [Layout](../layout/REFERENCE.md) - Flexbox layout system 173 + - [Keyboard](../keyboard/REFERENCE.md) - Input handling and shortcuts 174 + - [Testing](../testing/REFERENCE.md) - Test renderer and snapshots
+436
skills/opentui/references/react/api.md
··· 1 + # React API Reference 2 + 3 + ## Rendering 4 + 5 + ### createRoot(renderer) 6 + 7 + Creates a React root for rendering. 8 + 9 + ```tsx 10 + import { createCliRenderer } from "@opentui/core" 11 + import { createRoot } from "@opentui/react" 12 + 13 + const renderer = await createCliRenderer({ 14 + exitOnCtrlC: false, // Handle Ctrl+C yourself 15 + }) 16 + 17 + const root = createRoot(renderer) 18 + root.render(<App />) 19 + ``` 20 + 21 + ## Hooks 22 + 23 + ### useRenderer() 24 + 25 + Access the OpenTUI renderer instance. 26 + 27 + ```tsx 28 + import { useRenderer } from "@opentui/react" 29 + import { useEffect } from "react" 30 + 31 + function App() { 32 + const renderer = useRenderer() 33 + 34 + useEffect(() => { 35 + // Access renderer properties 36 + console.log(`Terminal: ${renderer.width}x${renderer.height}`) 37 + 38 + // Show debug console 39 + renderer.console.show() 40 + 41 + // Access theme mode (dark/light based on terminal settings) 42 + console.log(`Theme: ${renderer.themeMode}`) // "dark" | "light" | null 43 + }, [renderer]) 44 + 45 + return <text>Hello</text> 46 + } 47 + 48 + // Listen for theme mode changes 49 + function ThemedApp() { 50 + const renderer = useRenderer() 51 + const [theme, setTheme] = useState(renderer.themeMode ?? "dark") 52 + 53 + useEffect(() => { 54 + const handler = (mode: "dark" | "light") => setTheme(mode) 55 + renderer.on("theme_mode", handler) 56 + return () => renderer.off("theme_mode", handler) 57 + }, [renderer]) 58 + 59 + return ( 60 + <box backgroundColor={theme === "dark" ? "#1a1a2e" : "#ffffff"}> 61 + <text fg={theme === "dark" ? "#fff" : "#000"}> 62 + Current theme: {theme} 63 + </text> 64 + </box> 65 + ) 66 + } 67 + ``` 68 + 69 + ### useKeyboard(handler, options?) 70 + 71 + Handle keyboard events. 72 + 73 + ```tsx 74 + import { useKeyboard, useRenderer } from "@opentui/react" 75 + 76 + function App() { 77 + const renderer = useRenderer() 78 + 79 + useKeyboard((key) => { 80 + if (key.name === "escape") { 81 + renderer.destroy() // Never use process.exit() directly! 82 + } 83 + if (key.ctrl && key.name === "s") { 84 + saveDocument() 85 + } 86 + }) 87 + 88 + return <text>Press ESC to exit</text> 89 + } 90 + 91 + // With release events 92 + function GameControls() { 93 + const [pressed, setPressed] = useState(new Set<string>()) 94 + 95 + useKeyboard( 96 + (event) => { 97 + setPressed(keys => { 98 + const newKeys = new Set(keys) 99 + if (event.eventType === "release") { 100 + newKeys.delete(event.name) 101 + } else { 102 + newKeys.add(event.name) 103 + } 104 + return newKeys 105 + }) 106 + }, 107 + { release: true } // Include release events 108 + ) 109 + 110 + return <text>Pressed: {Array.from(pressed).join(", ")}</text> 111 + } 112 + ``` 113 + 114 + **Options:** 115 + - `release?: boolean` - Include key release events (default: false) 116 + 117 + **KeyEvent properties:** 118 + - `name: string` - Key name ("a", "escape", "f1", etc.) 119 + - `sequence: string` - Raw escape sequence 120 + - `ctrl: boolean` - Ctrl modifier 121 + - `shift: boolean` - Shift modifier 122 + - `meta: boolean` - Alt modifier 123 + - `option: boolean` - Option modifier (macOS) 124 + - `eventType: "press" | "release" | "repeat"` 125 + - `repeated: boolean` - Key is being held 126 + 127 + ### useOnResize(callback) 128 + 129 + Handle terminal resize events. 130 + 131 + ```tsx 132 + import { useOnResize } from "@opentui/react" 133 + 134 + function App() { 135 + useOnResize((width, height) => { 136 + console.log(`Resized to ${width}x${height}`) 137 + }) 138 + 139 + return <text>Resize the terminal</text> 140 + } 141 + ``` 142 + 143 + ### useTerminalDimensions() 144 + 145 + Get reactive terminal dimensions. 146 + 147 + ```tsx 148 + import { useTerminalDimensions } from "@opentui/react" 149 + 150 + function ResponsiveLayout() { 151 + const { width, height } = useTerminalDimensions() 152 + 153 + return ( 154 + <box flexDirection={width > 80 ? "row" : "column"}> 155 + <box flexGrow={1}> 156 + <text>Width: {width}</text> 157 + </box> 158 + <box flexGrow={1}> 159 + <text>Height: {height}</text> 160 + </box> 161 + </box> 162 + ) 163 + } 164 + ``` 165 + 166 + ### useTimeline(options?) 167 + 168 + Create animations with the timeline system. 169 + 170 + ```tsx 171 + import { useTimeline } from "@opentui/react" 172 + import { useEffect, useState } from "react" 173 + 174 + function AnimatedBox() { 175 + const [width, setWidth] = useState(0) 176 + 177 + const timeline = useTimeline({ 178 + duration: 2000, 179 + loop: false, 180 + }) 181 + 182 + useEffect(() => { 183 + timeline.add( 184 + { width: 0 }, 185 + { 186 + width: 50, 187 + duration: 2000, 188 + ease: "easeOutQuad", 189 + onUpdate: (anim) => { 190 + setWidth(Math.round(anim.targets[0].width)) 191 + }, 192 + } 193 + ) 194 + }, [timeline]) 195 + 196 + return <box style={{ width, height: 3, backgroundColor: "#6a5acd" }} /> 197 + } 198 + ``` 199 + 200 + **Options:** 201 + - `duration?: number` - Default duration (ms) 202 + - `loop?: boolean` - Loop the timeline 203 + - `autoplay?: boolean` - Auto-start (default: true) 204 + - `onComplete?: () => void` - Completion callback 205 + - `onPause?: () => void` - Pause callback 206 + 207 + **Timeline methods:** 208 + - `add(target, properties, startTime?)` - Add animation 209 + - `play()` - Start playback 210 + - `pause()` - Pause playback 211 + - `restart()` - Restart from beginning 212 + 213 + ## Components 214 + 215 + ### Text Component 216 + 217 + ```tsx 218 + <text 219 + content="Hello" // Or use children 220 + fg="#FFFFFF" // Foreground color 221 + bg="#000000" // Background color 222 + selectable={true} // Allow text selection 223 + > 224 + {/* Use nested modifier tags for styling */} 225 + <span fg="red">Red</span> 226 + <strong>Bold</strong> 227 + <em>Italic</em> 228 + <u>Underline</u> 229 + <br /> 230 + <a href="https://...">Link</a> 231 + </text> 232 + ``` 233 + 234 + > **Note**: Do NOT use `bold`, `italic`, `underline` as props on `<text>`. Use nested modifier tags like `<strong>`, `<em>`, `<u>` instead. 235 + 236 + ### Box Component 237 + 238 + ```tsx 239 + <box 240 + // Borders 241 + border // Enable border 242 + borderStyle="single" // single | double | rounded | bold 243 + borderColor="#FFFFFF" 244 + title="Title" 245 + titleAlignment="center" // left | center | right 246 + 247 + // Colors 248 + backgroundColor="#1a1a2e" 249 + 250 + // Layout (see layout/REFERENCE.md) 251 + flexDirection="row" 252 + justifyContent="center" 253 + alignItems="center" 254 + gap={2} 255 + 256 + // Spacing 257 + padding={2} 258 + paddingTop={1} 259 + paddingX={2} // Horizontal (left + right) 260 + paddingY={1} // Vertical (top + bottom) 261 + margin={1} 262 + marginX={2} // Horizontal (left + right) 263 + marginY={1} // Vertical (top + bottom) 264 + 265 + // Dimensions 266 + width={40} 267 + height={10} 268 + flexGrow={1} 269 + 270 + // Focus 271 + focusable // Allow box to receive focus 272 + focused={isFocused} // Controlled focus state 273 + 274 + // Events 275 + onMouseDown={(e) => {}} 276 + onMouseUp={(e) => {}} 277 + onMouseMove={(e) => {}} 278 + > 279 + {children} 280 + </box> 281 + ``` 282 + 283 + ### Scrollbox Component 284 + 285 + ```tsx 286 + <scrollbox 287 + focused // Enable keyboard scrolling 288 + style={{ 289 + rootOptions: { backgroundColor: "#24283b" }, 290 + wrapperOptions: { backgroundColor: "#1f2335" }, 291 + viewportOptions: { backgroundColor: "#1a1b26" }, 292 + contentOptions: { backgroundColor: "#16161e" }, 293 + scrollbarOptions: { 294 + showArrows: true, 295 + trackOptions: { 296 + foregroundColor: "#7aa2f7", 297 + backgroundColor: "#414868", 298 + }, 299 + }, 300 + }} 301 + > 302 + {/* Scrollable content */} 303 + {items.map((item, i) => ( 304 + <box key={i}> 305 + <text>{item}</text> 306 + </box> 307 + ))} 308 + </scrollbox> 309 + ``` 310 + 311 + ### Input Component 312 + 313 + ```tsx 314 + <input 315 + value={value} 316 + onChange={(newValue) => setValue(newValue)} 317 + placeholder="Enter text..." 318 + focused // Start focused 319 + width={30} 320 + backgroundColor="#1a1a1a" 321 + textColor="#FFFFFF" 322 + cursorColor="#00FF00" 323 + focusedBackgroundColor="#2a2a2a" 324 + /> 325 + ``` 326 + 327 + ### Textarea Component 328 + 329 + ```tsx 330 + <textarea 331 + value={text} 332 + onChange={(newValue) => setText(newValue)} 333 + placeholder="Enter multiple lines..." 334 + focused 335 + width={40} 336 + height={10} 337 + showLineNumbers 338 + wrapText 339 + /> 340 + ``` 341 + 342 + ### Select Component 343 + 344 + ```tsx 345 + <select 346 + options={[ 347 + { name: "Option 1", description: "First option", value: "1" }, 348 + { name: "Option 2", description: "Second option", value: "2" }, 349 + ]} 350 + onChange={(index, option) => setSelected(option)} 351 + selectedIndex={0} 352 + focused 353 + showScrollIndicator 354 + height={8} 355 + /> 356 + ``` 357 + 358 + ### Tab Select Component 359 + 360 + ```tsx 361 + <tab-select 362 + options={[ 363 + { name: "Home", description: "Dashboard" }, 364 + { name: "Settings", description: "Configuration" }, 365 + ]} 366 + onChange={(index, option) => setTab(option)} 367 + tabWidth={20} 368 + focused 369 + /> 370 + ``` 371 + 372 + ### ASCII Font Component 373 + 374 + ```tsx 375 + <ascii-font 376 + text="TITLE" 377 + font="tiny" // tiny | block | slick | shade 378 + color="#FFFFFF" 379 + /> 380 + ``` 381 + 382 + ### Code Component 383 + 384 + ```tsx 385 + <code 386 + code={sourceCode} 387 + language="typescript" 388 + showLineNumbers 389 + highlightLines={[1, 5, 10]} 390 + /> 391 + ``` 392 + 393 + ### Line Number Component 394 + 395 + ```tsx 396 + <line-number 397 + code={sourceCode} 398 + language="typescript" 399 + startLine={1} 400 + highlightedLines={[5]} 401 + diagnostics={[ 402 + { line: 3, severity: "error", message: "Syntax error" } 403 + ]} 404 + /> 405 + ``` 406 + 407 + ### Diff Component 408 + 409 + ```tsx 410 + <diff 411 + oldCode={originalCode} 412 + newCode={modifiedCode} 413 + language="typescript" 414 + mode="unified" // unified | split 415 + syncScroll // Sync scroll between split view panes 416 + showLineNumbers 417 + /> 418 + ``` 419 + 420 + ## Type Exports 421 + 422 + ```tsx 423 + import type { 424 + // Component props 425 + TextProps, 426 + BoxProps, 427 + InputProps, 428 + SelectProps, 429 + 430 + // Hook types 431 + KeyEvent, 432 + 433 + // From core 434 + CliRenderer, 435 + } from "@opentui/react" 436 + ```
+302
skills/opentui/references/react/configuration.md
··· 1 + # React Configuration 2 + 3 + ## Project Setup 4 + 5 + ### Quick Start 6 + 7 + ```bash 8 + bunx create-tui@latest -t react my-app 9 + cd my-app && bun install 10 + ``` 11 + 12 + The CLI creates the `my-app` directory for you - it must **not already exist**. 13 + 14 + Options: `--no-git` (skip git init), `--no-install` (skip bun install) 15 + 16 + ### Manual Setup 17 + 18 + ```bash 19 + mkdir my-tui && cd my-tui 20 + bun init 21 + bun install @opentui/react @opentui/core react 22 + ``` 23 + 24 + ## TypeScript Configuration 25 + 26 + ### tsconfig.json 27 + 28 + ```json 29 + { 30 + "compilerOptions": { 31 + "lib": ["ESNext", "DOM"], 32 + "target": "ESNext", 33 + "module": "NodeNext", 34 + "moduleResolution": "NodeNext", 35 + 36 + "jsx": "react-jsx", 37 + "jsxImportSource": "@opentui/react", 38 + 39 + "strict": true, 40 + "skipLibCheck": true, 41 + "noEmit": true, 42 + "types": ["bun-types"] 43 + }, 44 + "include": ["src/**/*"] 45 + } 46 + ``` 47 + 48 + **Critical settings:** 49 + - `jsx: "react-jsx"` - Use the new JSX transform 50 + - `jsxImportSource: "@opentui/react"` - Import JSX runtime from OpenTUI 51 + - `module` / `moduleResolution: "NodeNext"` - Recommended for OpenTUI compatibility 52 + 53 + ### Why DOM lib? 54 + 55 + The `DOM` lib is needed for React types. OpenTUI's JSX types extend React's. 56 + 57 + ## Package Configuration 58 + 59 + ### package.json 60 + 61 + ```json 62 + { 63 + "name": "my-tui-app", 64 + "type": "module", 65 + "scripts": { 66 + "start": "bun run src/index.tsx", 67 + "dev": "bun --watch run src/index.tsx", 68 + "test": "bun test", 69 + "build": "bun build src/index.tsx --outdir=dist --target=bun" 70 + }, 71 + "dependencies": { 72 + "@opentui/core": "latest", 73 + "@opentui/react": "latest", 74 + "react": ">=19.0.0" 75 + }, 76 + "devDependencies": { 77 + "@types/bun": "latest", 78 + "@types/react": ">=19.0.0", 79 + "typescript": "latest" 80 + } 81 + } 82 + ``` 83 + 84 + ## Project Structure 85 + 86 + Recommended structure: 87 + 88 + ``` 89 + my-tui-app/ 90 + ├── src/ 91 + │ ├── components/ 92 + │ │ ├── Header.tsx 93 + │ │ ├── Sidebar.tsx 94 + │ │ └── MainContent.tsx 95 + │ ├── hooks/ 96 + │ │ └── useAppState.ts 97 + │ ├── App.tsx 98 + │ └── index.tsx 99 + ├── package.json 100 + └── tsconfig.json 101 + ``` 102 + 103 + ### Entry Point (src/index.tsx) 104 + 105 + ```tsx 106 + import { createCliRenderer } from "@opentui/core" 107 + import { createRoot } from "@opentui/react" 108 + import { App } from "./App" 109 + 110 + const renderer = await createCliRenderer({ 111 + exitOnCtrlC: true, 112 + }) 113 + 114 + createRoot(renderer).render(<App />) 115 + ``` 116 + 117 + ### App Component (src/App.tsx) 118 + 119 + ```tsx 120 + import { Header } from "./components/Header" 121 + import { Sidebar } from "./components/Sidebar" 122 + import { MainContent } from "./components/MainContent" 123 + 124 + export function App() { 125 + return ( 126 + <box flexDirection="column" width="100%" height="100%"> 127 + <Header /> 128 + <box flexDirection="row" flexGrow={1}> 129 + <Sidebar /> 130 + <MainContent /> 131 + </box> 132 + </box> 133 + ) 134 + } 135 + ``` 136 + 137 + ## Renderer Configuration 138 + 139 + ### createCliRenderer Options 140 + 141 + ```tsx 142 + import { createCliRenderer, ConsolePosition } from "@opentui/core" 143 + 144 + const renderer = await createCliRenderer({ 145 + // Rendering 146 + targetFPS: 60, 147 + 148 + // Behavior 149 + exitOnCtrlC: true, // Set false to handle Ctrl+C yourself 150 + autoFocus: true, // Auto-focus elements on click (default: true) 151 + useMouse: true, // Enable mouse support (default: true) 152 + 153 + // Debug console 154 + consoleOptions: { 155 + position: ConsolePosition.BOTTOM, 156 + sizePercent: 30, 157 + startInDebugMode: false, 158 + }, 159 + 160 + // Cleanup 161 + onDestroy: () => { 162 + // Cleanup code 163 + }, 164 + }) 165 + ``` 166 + 167 + ## Building for Distribution 168 + 169 + ### Bundling with Bun 170 + 171 + ```typescript 172 + // build.ts 173 + await Bun.build({ 174 + entrypoints: ["./src/index.tsx"], 175 + outdir: "./dist", 176 + target: "bun", 177 + minify: true, 178 + }) 179 + ``` 180 + 181 + Run: `bun run build.ts` 182 + 183 + ### Creating Executables 184 + 185 + ```typescript 186 + // build.ts 187 + await Bun.build({ 188 + entrypoints: ["./src/index.tsx"], 189 + outdir: "./dist", 190 + target: "bun", 191 + compile: { 192 + target: "bun-darwin-arm64", // or bun-linux-x64, etc. 193 + outfile: "my-app", 194 + }, 195 + }) 196 + ``` 197 + 198 + ## Environment Variables 199 + 200 + Create `.env` for development: 201 + 202 + ```env 203 + # Debug settings 204 + OTUI_SHOW_STATS=false 205 + SHOW_CONSOLE=false 206 + 207 + # App settings 208 + API_URL=https://api.example.com 209 + ``` 210 + 211 + Bun auto-loads `.env` files. Access via `process.env`: 212 + 213 + ```tsx 214 + const apiUrl = process.env.API_URL 215 + ``` 216 + 217 + ## React DevTools 218 + 219 + OpenTUI React supports React DevTools for debugging. 220 + 221 + ### Setup 222 + 223 + 1. Install DevTools as a dev dependency (must use version 7): 224 + ```bash 225 + bun add react-devtools-core@7 -d 226 + ``` 227 + 228 + 2. Run DevTools standalone app: 229 + ```bash 230 + npx react-devtools@7 231 + ``` 232 + 233 + 3. Start your app with `DEV=true` environment variable: 234 + ```bash 235 + DEV=true bun run src/index.tsx 236 + ``` 237 + 238 + **Important**: Auto-connect to DevTools ONLY happens when `DEV=true` is set. Without this environment variable, the DevTools connection code is not loaded. 239 + 240 + ### How It Works 241 + 242 + OpenTUI checks for `process.env["DEV"] === "true"` at startup. When true, it dynamically imports `react-devtools-core` and connects to the standalone DevTools app. 243 + 244 + ## Testing Configuration 245 + 246 + ### Test Setup 247 + 248 + ```typescript 249 + // src/test-utils.tsx 250 + import { createTestRenderer } from "@opentui/core/testing" 251 + import { createRoot } from "@opentui/react" 252 + 253 + export async function renderForTest( 254 + element: React.ReactElement, 255 + options = { width: 80, height: 24 } 256 + ) { 257 + const testSetup = await createTestRenderer(options) 258 + createRoot(testSetup.renderer).render(element) 259 + return testSetup 260 + } 261 + ``` 262 + 263 + ### Test Example 264 + 265 + ```typescript 266 + // src/components/Counter.test.tsx 267 + import { test, expect } from "bun:test" 268 + import { renderForTest } from "../test-utils" 269 + import { Counter } from "./Counter" 270 + 271 + test("Counter renders initial value", async () => { 272 + const { snapshot } = await renderForTest(<Counter initialValue={5} />) 273 + expect(snapshot()).toContain("Count: 5") 274 + }) 275 + ``` 276 + 277 + ## Common Issues 278 + 279 + ### JSX Types Not Working 280 + 281 + Ensure `jsxImportSource` is set: 282 + 283 + ```json 284 + { 285 + "compilerOptions": { 286 + "jsx": "react-jsx", 287 + "jsxImportSource": "@opentui/react" 288 + } 289 + } 290 + ``` 291 + 292 + ### React Version Mismatch 293 + 294 + Ensure React 19+: 295 + 296 + ```bash 297 + bun install react@19 @types/react@19 298 + ``` 299 + 300 + ### Module Resolution Errors 301 + 302 + Use `moduleResolution: "bundler"` for Bun compatibility.
+443
skills/opentui/references/react/gotchas.md
··· 1 + # React Gotchas 2 + 3 + ## Critical 4 + 5 + ### Never use `process.exit()` directly 6 + 7 + **This is the most common mistake.** Using `process.exit()` leaves the terminal in a broken state (cursor hidden, raw mode, alternate screen). 8 + 9 + ```tsx 10 + // WRONG - Terminal left in broken state 11 + process.exit(0) 12 + 13 + // CORRECT - Use renderer.destroy() 14 + import { useRenderer } from "@opentui/react" 15 + 16 + function App() { 17 + const renderer = useRenderer() 18 + 19 + const handleExit = () => { 20 + renderer.destroy() // Cleans up and exits properly 21 + } 22 + } 23 + ``` 24 + 25 + `renderer.destroy()` restores the terminal (exits alternate screen, restores cursor, etc.) before exiting. 26 + 27 + ### Signal Handling 28 + 29 + OpenTUI automatically handles cleanup for these signals: 30 + - `SIGINT` (Ctrl+C), `SIGTERM`, `SIGQUIT` - Standard termination 31 + - `SIGHUP` - Terminal closed/hangup 32 + - `SIGBREAK` - Ctrl+Break (Windows) 33 + - `SIGPIPE` - Broken pipe (output closed) 34 + - `SIGBUS`, `SIGFPE` - Hardware errors 35 + 36 + This ensures terminal state is restored even on unexpected termination. If you need custom signal handling, use `exitOnCtrlC: false` and handle signals yourself while still calling `renderer.destroy()`. 37 + 38 + ## JSX Configuration 39 + 40 + ### Missing jsxImportSource 41 + 42 + **Symptom**: JSX elements have wrong types, components don't render 43 + 44 + ``` 45 + // Error: Property 'text' does not exist on type 'JSX.IntrinsicElements' 46 + ``` 47 + 48 + **Fix**: Configure tsconfig.json: 49 + 50 + ```json 51 + { 52 + "compilerOptions": { 53 + "jsx": "react-jsx", 54 + "jsxImportSource": "@opentui/react" 55 + } 56 + } 57 + ``` 58 + 59 + ### HTML Elements vs TUI Elements 60 + 61 + OpenTUI's JSX elements are **not** HTML elements: 62 + 63 + ```tsx 64 + // WRONG - These are HTML concepts 65 + <div>Not supported</div> 66 + <button>Not supported</button> 67 + <span>Only works inside <text></span> 68 + 69 + // CORRECT - OpenTUI elements 70 + <box>Container</box> 71 + <text>Display text</text> 72 + <text><span>Inline styled</span></text> 73 + ``` 74 + 75 + ## Component Issues 76 + 77 + ### Text Modifiers Outside Text 78 + 79 + Text modifiers only work inside `<text>`: 80 + 81 + ```tsx 82 + // WRONG 83 + <box> 84 + <strong>This won't work</strong> 85 + </box> 86 + 87 + // CORRECT 88 + <box> 89 + <text> 90 + <strong>This works</strong> 91 + </text> 92 + </box> 93 + ``` 94 + 95 + ### Focus Not Working 96 + 97 + Components must be explicitly focused: 98 + 99 + ```tsx 100 + // WRONG - Won't receive keyboard input 101 + <input placeholder="Type here..." /> 102 + 103 + // CORRECT 104 + <input placeholder="Type here..." focused /> 105 + 106 + // Or manage focus state 107 + const [isFocused, setIsFocused] = useState(true) 108 + <input placeholder="Type here..." focused={isFocused} /> 109 + ``` 110 + 111 + ### Select Not Responding 112 + 113 + Select requires focus and proper options format: 114 + 115 + ```tsx 116 + // WRONG - Missing required properties 117 + <select options={["a", "b", "c"]} /> 118 + 119 + // CORRECT 120 + <select 121 + options={[ 122 + { name: "Option A", description: "First option", value: "a" }, 123 + { name: "Option B", description: "Second option", value: "b" }, 124 + ]} 125 + onSelect={(index, option) => { 126 + // Called when Enter is pressed 127 + console.log("Selected:", option.name) 128 + }} 129 + focused 130 + /> 131 + ``` 132 + 133 + ### Select Events Confusion 134 + 135 + Remember: `onSelect` fires on Enter (selection confirmed), `onChange` fires on navigation: 136 + 137 + ```tsx 138 + // WRONG - expecting onChange to fire on Enter 139 + <select 140 + options={options} 141 + onChange={(i, opt) => submitForm(opt)} // This fires on arrow keys! 142 + /> 143 + 144 + // CORRECT 145 + <select 146 + options={options} 147 + onSelect={(i, opt) => submitForm(opt)} // Enter pressed - submit 148 + onChange={(i, opt) => showPreview(opt)} // Arrow keys - preview 149 + /> 150 + ``` 151 + 152 + ## Hook Issues 153 + 154 + ### useKeyboard Not Firing 155 + 156 + Multiple `useKeyboard` hooks can conflict: 157 + 158 + ```tsx 159 + // Both handlers fire - may cause issues 160 + function App() { 161 + useKeyboard((key) => { /* parent handler */ }) 162 + return <ChildWithKeyboard /> 163 + } 164 + 165 + function ChildWithKeyboard() { 166 + useKeyboard((key) => { /* child handler */ }) 167 + return <text>Child</text> 168 + } 169 + ``` 170 + 171 + **Solution**: Use a single keyboard handler or implement event stopping: 172 + 173 + ```tsx 174 + function App() { 175 + const [handled, setHandled] = useState(false) 176 + 177 + useKeyboard((key) => { 178 + if (handled) { 179 + setHandled(false) 180 + return 181 + } 182 + // Handle at app level 183 + }) 184 + 185 + return <Child onKeyHandled={() => setHandled(true)} /> 186 + } 187 + ``` 188 + 189 + ### useEffect Cleanup 190 + 191 + Always clean up intervals and listeners: 192 + 193 + ```tsx 194 + // WRONG - Memory leak 195 + useEffect(() => { 196 + setInterval(() => updateData(), 1000) 197 + }, []) 198 + 199 + // CORRECT 200 + useEffect(() => { 201 + const interval = setInterval(() => updateData(), 1000) 202 + return () => clearInterval(interval) // Cleanup! 203 + }, []) 204 + ``` 205 + 206 + ## Styling Issues 207 + 208 + ### Colors Not Applying 209 + 210 + Check color format: 211 + 212 + ```tsx 213 + // CORRECT formats 214 + <text fg="#FF0000">Red</text> 215 + <text fg="red">Red</text> 216 + <box backgroundColor="#1a1a2e">Box</box> 217 + 218 + // WRONG 219 + <text fg="FF0000">Missing #</text> 220 + <text color="#FF0000">Wrong prop name (use fg)</text> 221 + ``` 222 + 223 + ### Layout Not Working 224 + 225 + Ensure parent has dimensions: 226 + 227 + ```tsx 228 + // WRONG - Parent has no height 229 + <box flexDirection="column"> 230 + <box flexGrow={1}>Won't grow</box> 231 + </box> 232 + 233 + // CORRECT 234 + <box flexDirection="column" height="100%"> 235 + <box flexGrow={1}>Will grow</box> 236 + </box> 237 + ``` 238 + 239 + ### Percentage Widths Not Working 240 + 241 + Parent must have explicit dimensions: 242 + 243 + ```tsx 244 + // WRONG 245 + <box> 246 + <box width="50%">Won't work</box> 247 + </box> 248 + 249 + // CORRECT 250 + <box width="100%"> 251 + <box width="50%">Works</box> 252 + </box> 253 + ``` 254 + 255 + ## Performance Issues 256 + 257 + ### Too Many Re-renders 258 + 259 + Avoid inline objects/functions in props: 260 + 261 + ```tsx 262 + // WRONG - New object every render 263 + <box style={{ padding: 2 }}>Content</box> 264 + 265 + // BETTER - Use direct props 266 + <box padding={2}>Content</box> 267 + 268 + // OR memoize style objects 269 + const style = useMemo(() => ({ padding: 2 }), []) 270 + <box style={style}>Content</box> 271 + ``` 272 + 273 + ### Heavy Components 274 + 275 + Use React.memo for expensive components: 276 + 277 + ```tsx 278 + const ExpensiveList = React.memo(function ExpensiveList({ 279 + items 280 + }: { 281 + items: Item[] 282 + }) { 283 + return ( 284 + <box flexDirection="column"> 285 + {items.map(item => ( 286 + <text key={item.id}>{item.name}</text> 287 + ))} 288 + </box> 289 + ) 290 + }) 291 + ``` 292 + 293 + ### State Updates During Render 294 + 295 + Don't update state during render: 296 + 297 + ```tsx 298 + // WRONG 299 + function Component({ value }: { value: number }) { 300 + const [count, setCount] = useState(0) 301 + 302 + // This causes infinite loop! 303 + if (value > 10) { 304 + setCount(value) 305 + } 306 + 307 + return <text>{count}</text> 308 + } 309 + 310 + // CORRECT 311 + function Component({ value }: { value: number }) { 312 + const [count, setCount] = useState(0) 313 + 314 + useEffect(() => { 315 + if (value > 10) { 316 + setCount(value) 317 + } 318 + }, [value]) 319 + 320 + return <text>{count}</text> 321 + } 322 + ``` 323 + 324 + ## Debugging 325 + 326 + ### Console Not Visible 327 + 328 + OpenTUI captures console output. Show the overlay: 329 + 330 + ```tsx 331 + import { useRenderer } from "@opentui/react" 332 + import { useEffect } from "react" 333 + 334 + function App() { 335 + const renderer = useRenderer() 336 + 337 + useEffect(() => { 338 + renderer.console.show() 339 + console.log("Now you can see this!") 340 + }, [renderer]) 341 + 342 + return <box>{/* ... */}</box> 343 + } 344 + ``` 345 + 346 + ### Component Not Rendering 347 + 348 + Check if component is in the tree: 349 + 350 + ```tsx 351 + // WRONG - Conditional returns nothing 352 + function MaybeComponent({ show }: { show: boolean }) { 353 + if (!show) return // Returns undefined! 354 + return <text>Visible</text> 355 + } 356 + 357 + // CORRECT 358 + function MaybeComponent({ show }: { show: boolean }) { 359 + if (!show) return null // Explicit null 360 + return <text>Visible</text> 361 + } 362 + ``` 363 + 364 + ### Events Not Firing 365 + 366 + Check event handler names: 367 + 368 + ```tsx 369 + // WRONG 370 + <box onClick={() => {}}>Click</box> // No onClick in TUI 371 + 372 + // CORRECT 373 + <box onMouseDown={() => {}}>Click</box> 374 + <box onMouseUp={() => {}}>Click</box> 375 + ``` 376 + 377 + ## Runtime Issues 378 + 379 + ### Use Bun, Not Node 380 + 381 + ```bash 382 + # WRONG 383 + node src/index.tsx 384 + npm run start 385 + 386 + # CORRECT 387 + bun run src/index.tsx 388 + bun run start 389 + ``` 390 + 391 + ### Async Top-level 392 + 393 + Bun supports top-level await, but be careful: 394 + 395 + ```tsx 396 + // index.tsx - This works in Bun 397 + const renderer = await createCliRenderer() 398 + createRoot(renderer).render(<App />) 399 + 400 + // If you need to handle errors 401 + try { 402 + const renderer = await createCliRenderer() 403 + createRoot(renderer).render(<App />) 404 + } catch (error) { 405 + console.error("Failed to initialize:", error) 406 + process.exit(1) 407 + } 408 + ``` 409 + 410 + ## Common Error Messages 411 + 412 + ### "Cannot read properties of undefined (reading 'root')" 413 + 414 + Renderer not initialized: 415 + 416 + ```tsx 417 + // WRONG 418 + const renderer = createCliRenderer() // Missing await! 419 + createRoot(renderer).render(<App />) 420 + 421 + // CORRECT 422 + const renderer = await createCliRenderer() 423 + createRoot(renderer).render(<App />) 424 + ``` 425 + 426 + ### "Invalid hook call" 427 + 428 + Hooks called outside component: 429 + 430 + ```tsx 431 + // WRONG 432 + const dimensions = useTerminalDimensions() // Outside component! 433 + 434 + function App() { 435 + return <text>{dimensions.width}</text> 436 + } 437 + 438 + // CORRECT 439 + function App() { 440 + const dimensions = useTerminalDimensions() 441 + return <text>{dimensions.width}</text> 442 + } 443 + ```
+501
skills/opentui/references/react/patterns.md
··· 1 + # React Patterns 2 + 3 + ## State Management 4 + 5 + ### Local State with useState 6 + 7 + ```tsx 8 + import { useState } from "react" 9 + 10 + function Counter() { 11 + const [count, setCount] = useState(0) 12 + 13 + return ( 14 + <box flexDirection="row" gap={2}> 15 + <text>Count: {count}</text> 16 + <box border onMouseDown={() => setCount(c => c - 1)}> 17 + <text>-</text> 18 + </box> 19 + <box border onMouseDown={() => setCount(c => c + 1)}> 20 + <text>+</text> 21 + </box> 22 + </box> 23 + ) 24 + } 25 + ``` 26 + 27 + ### Complex State with useReducer 28 + 29 + ```tsx 30 + import { useReducer } from "react" 31 + 32 + type State = { 33 + items: string[] 34 + selectedIndex: number 35 + } 36 + 37 + type Action = 38 + | { type: "ADD_ITEM"; item: string } 39 + | { type: "REMOVE_ITEM"; index: number } 40 + | { type: "SELECT"; index: number } 41 + 42 + function reducer(state: State, action: Action): State { 43 + switch (action.type) { 44 + case "ADD_ITEM": 45 + return { ...state, items: [...state.items, action.item] } 46 + case "REMOVE_ITEM": 47 + return { 48 + ...state, 49 + items: state.items.filter((_, i) => i !== action.index), 50 + } 51 + case "SELECT": 52 + return { ...state, selectedIndex: action.index } 53 + } 54 + } 55 + 56 + function ItemList() { 57 + const [state, dispatch] = useReducer(reducer, { 58 + items: [], 59 + selectedIndex: 0, 60 + }) 61 + 62 + // Use state and dispatch... 63 + } 64 + ``` 65 + 66 + ### Context for Global State 67 + 68 + ```tsx 69 + import { createContext, useContext, useState, ReactNode } from "react" 70 + 71 + type Theme = "dark" | "light" 72 + 73 + const ThemeContext = createContext<{ 74 + theme: Theme 75 + setTheme: (theme: Theme) => void 76 + } | null>(null) 77 + 78 + function ThemeProvider({ children }: { children: ReactNode }) { 79 + const [theme, setTheme] = useState<Theme>("dark") 80 + 81 + return ( 82 + <ThemeContext.Provider value={{ theme, setTheme }}> 83 + {children} 84 + </ThemeContext.Provider> 85 + ) 86 + } 87 + 88 + function useTheme() { 89 + const context = useContext(ThemeContext) 90 + if (!context) throw new Error("useTheme must be used within ThemeProvider") 91 + return context 92 + } 93 + 94 + // Usage 95 + function App() { 96 + return ( 97 + <ThemeProvider> 98 + <ThemedBox /> 99 + </ThemeProvider> 100 + ) 101 + } 102 + 103 + function ThemedBox() { 104 + const { theme } = useTheme() 105 + return ( 106 + <box backgroundColor={theme === "dark" ? "#1a1a2e" : "#f0f0f0"}> 107 + <text fg={theme === "dark" ? "#fff" : "#000"}> 108 + Current theme: {theme} 109 + </text> 110 + </box> 111 + ) 112 + } 113 + ``` 114 + 115 + ## Focus Management 116 + 117 + ### Focus State 118 + 119 + ```tsx 120 + import { useState } from "react" 121 + import { useKeyboard } from "@opentui/react" 122 + 123 + function FocusableForm() { 124 + const [focusIndex, setFocusIndex] = useState(0) 125 + const fields = ["name", "email", "message"] 126 + 127 + useKeyboard((key) => { 128 + if (key.name === "tab") { 129 + setFocusIndex(i => (i + 1) % fields.length) 130 + } 131 + if (key.shift && key.name === "tab") { 132 + setFocusIndex(i => (i - 1 + fields.length) % fields.length) 133 + } 134 + }) 135 + 136 + return ( 137 + <box flexDirection="column" gap={1}> 138 + {fields.map((field, i) => ( 139 + <input 140 + key={field} 141 + placeholder={`Enter ${field}...`} 142 + focused={i === focusIndex} 143 + /> 144 + ))} 145 + </box> 146 + ) 147 + } 148 + ``` 149 + 150 + ### Ref-based Focus 151 + 152 + ```tsx 153 + import { useRef, useEffect } from "react" 154 + 155 + function AutoFocusInput() { 156 + const inputRef = useRef<any>(null) 157 + 158 + useEffect(() => { 159 + // Focus on mount 160 + inputRef.current?.focus() 161 + }, []) 162 + 163 + return <input ref={inputRef} placeholder="Auto-focused" /> 164 + } 165 + ``` 166 + 167 + ## Keyboard Navigation 168 + 169 + ### Global Shortcuts 170 + 171 + ```tsx 172 + import { useKeyboard, useRenderer } from "@opentui/react" 173 + 174 + function App() { 175 + const renderer = useRenderer() 176 + 177 + useKeyboard((key) => { 178 + // Quit on Escape or Ctrl+C - use renderer.destroy(), never process.exit() 179 + if (key.name === "escape" || (key.ctrl && key.name === "c")) { 180 + renderer.destroy() 181 + return 182 + } 183 + 184 + // Toggle help on ? 185 + if (key.name === "?" || (key.shift && key.name === "/")) { 186 + setShowHelp(h => !h) 187 + } 188 + 189 + // Vim-style navigation 190 + if (key.name === "j") moveDown() 191 + if (key.name === "k") moveUp() 192 + }) 193 + 194 + return <box>{/* ... */}</box> 195 + } 196 + ``` 197 + 198 + ### Component-level Shortcuts 199 + 200 + ```tsx 201 + function Editor() { 202 + const [mode, setMode] = useState<"normal" | "insert">("normal") 203 + 204 + useKeyboard((key) => { 205 + if (mode === "normal") { 206 + if (key.name === "i") setMode("insert") 207 + if (key.name === "escape") setMode("normal") 208 + } else { 209 + if (key.name === "escape") setMode("normal") 210 + // Handle text input in insert mode 211 + } 212 + }) 213 + 214 + return ( 215 + <box> 216 + <text>Mode: {mode}</text> 217 + <textarea focused={mode === "insert"} /> 218 + </box> 219 + ) 220 + } 221 + ``` 222 + 223 + ## Form Handling 224 + 225 + ### Controlled Inputs 226 + 227 + ```tsx 228 + import { useState } from "react" 229 + 230 + function LoginForm() { 231 + const [username, setUsername] = useState("") 232 + const [password, setPassword] = useState("") 233 + 234 + const handleSubmit = () => { 235 + console.log("Login:", { username, password }) 236 + } 237 + 238 + return ( 239 + <box flexDirection="column" gap={1} padding={2} border> 240 + <text>Login</text> 241 + 242 + <box flexDirection="row" gap={1}> 243 + <text>Username:</text> 244 + <input 245 + value={username} 246 + onChange={setUsername} 247 + width={20} 248 + /> 249 + </box> 250 + 251 + <box flexDirection="row" gap={1}> 252 + <text>Password:</text> 253 + <input 254 + value={password} 255 + onChange={setPassword} 256 + width={20} 257 + /> 258 + </box> 259 + 260 + <box border onMouseDown={handleSubmit}> 261 + <text>Submit</text> 262 + </box> 263 + </box> 264 + ) 265 + } 266 + ``` 267 + 268 + ### Form Validation 269 + 270 + ```tsx 271 + function ValidatedForm() { 272 + const [email, setEmail] = useState("") 273 + const [error, setError] = useState("") 274 + 275 + const validateEmail = (value: string) => { 276 + if (!value.includes("@")) { 277 + setError("Invalid email address") 278 + } else { 279 + setError("") 280 + } 281 + setEmail(value) 282 + } 283 + 284 + return ( 285 + <box flexDirection="column" gap={1}> 286 + <input 287 + value={email} 288 + onChange={validateEmail} 289 + placeholder="Email" 290 + /> 291 + {error && <text fg="red">{error}</text>} 292 + </box> 293 + ) 294 + } 295 + ``` 296 + 297 + ## Responsive Design 298 + 299 + ### Terminal-size Responsive 300 + 301 + ```tsx 302 + import { useTerminalDimensions } from "@opentui/react" 303 + 304 + function ResponsiveLayout() { 305 + const { width } = useTerminalDimensions() 306 + 307 + // Stack vertically on narrow terminals 308 + const isNarrow = width < 80 309 + 310 + return ( 311 + <box flexDirection={isNarrow ? "column" : "row"}> 312 + <box flexGrow={isNarrow ? 0 : 1} height={isNarrow ? 10 : "100%"}> 313 + <text>Sidebar</text> 314 + </box> 315 + <box flexGrow={1}> 316 + <text>Main Content</text> 317 + </box> 318 + </box> 319 + ) 320 + } 321 + ``` 322 + 323 + ### Dynamic Layouts 324 + 325 + ```tsx 326 + function DynamicGrid({ items }: { items: string[] }) { 327 + const { width } = useTerminalDimensions() 328 + const columns = Math.max(1, Math.floor(width / 20)) 329 + 330 + return ( 331 + <box flexDirection="row" flexWrap="wrap"> 332 + {items.map((item, i) => ( 333 + <box key={i} width={`${100 / columns}%`} padding={1}> 334 + <text>{item}</text> 335 + </box> 336 + ))} 337 + </box> 338 + ) 339 + } 340 + ``` 341 + 342 + ## Async Data Loading 343 + 344 + ### Loading States 345 + 346 + ```tsx 347 + import { useState, useEffect } from "react" 348 + 349 + function DataDisplay() { 350 + const [data, setData] = useState<string[] | null>(null) 351 + const [loading, setLoading] = useState(true) 352 + const [error, setError] = useState<string | null>(null) 353 + 354 + useEffect(() => { 355 + async function load() { 356 + try { 357 + const response = await fetch("https://api.example.com/data") 358 + const json = await response.json() 359 + setData(json.items) 360 + } catch (e) { 361 + setError(e instanceof Error ? e.message : "Unknown error") 362 + } finally { 363 + setLoading(false) 364 + } 365 + } 366 + load() 367 + }, []) 368 + 369 + if (loading) { 370 + return <text>Loading...</text> 371 + } 372 + 373 + if (error) { 374 + return <text fg="red">Error: {error}</text> 375 + } 376 + 377 + return ( 378 + <box flexDirection="column"> 379 + {data?.map((item, i) => ( 380 + <text key={i}>{item}</text> 381 + ))} 382 + </box> 383 + ) 384 + } 385 + ``` 386 + 387 + ## Animation Patterns 388 + 389 + ### Simple Animations 390 + 391 + ```tsx 392 + import { useState, useEffect } from "react" 393 + import { useTimeline } from "@opentui/react" 394 + 395 + function ProgressBar() { 396 + const [progress, setProgress] = useState(0) 397 + 398 + const timeline = useTimeline({ duration: 3000 }) 399 + 400 + useEffect(() => { 401 + timeline.add( 402 + { value: 0 }, 403 + { 404 + value: 100, 405 + duration: 3000, 406 + ease: "linear", 407 + onUpdate: (anim) => { 408 + setProgress(Math.round(anim.targets[0].value)) 409 + }, 410 + } 411 + ) 412 + }, []) 413 + 414 + return ( 415 + <box flexDirection="column" gap={1}> 416 + <text>Progress: {progress}%</text> 417 + <box width={50} height={1} backgroundColor="#333"> 418 + <box 419 + width={`${progress}%`} 420 + height={1} 421 + backgroundColor="#00ff00" 422 + /> 423 + </box> 424 + </box> 425 + ) 426 + } 427 + ``` 428 + 429 + ### Interval-based Updates 430 + 431 + ```tsx 432 + function Clock() { 433 + const [time, setTime] = useState(new Date()) 434 + 435 + useEffect(() => { 436 + const interval = setInterval(() => { 437 + setTime(new Date()) 438 + }, 1000) 439 + 440 + return () => clearInterval(interval) 441 + }, []) 442 + 443 + return <text>{time.toLocaleTimeString()}</text> 444 + } 445 + ``` 446 + 447 + ## Component Composition 448 + 449 + ### Render Props 450 + 451 + ```tsx 452 + function Focusable({ 453 + children 454 + }: { 455 + children: (focused: boolean) => React.ReactNode 456 + }) { 457 + const [focused, setFocused] = useState(false) 458 + 459 + return ( 460 + <box 461 + onMouseDown={() => setFocused(true)} 462 + onMouseUp={() => setFocused(false)} 463 + > 464 + {children(focused)} 465 + </box> 466 + ) 467 + } 468 + 469 + // Usage 470 + <Focusable> 471 + {(focused) => ( 472 + <text fg={focused ? "#00ff00" : "#ffffff"}> 473 + {focused ? "Focused!" : "Click me"} 474 + </text> 475 + )} 476 + </Focusable> 477 + ``` 478 + 479 + ### Higher-Order Components 480 + 481 + ```tsx 482 + function withBorder<P extends object>( 483 + Component: React.ComponentType<P>, 484 + borderStyle: string = "single" 485 + ) { 486 + return function BorderedComponent(props: P) { 487 + return ( 488 + <box border borderStyle={borderStyle} padding={1}> 489 + <Component {...props} /> 490 + </box> 491 + ) 492 + } 493 + } 494 + 495 + // Usage 496 + const BorderedText = withBorder(({ content }: { content: string }) => ( 497 + <text>{content}</text> 498 + )) 499 + 500 + <BorderedText content="Hello!" /> 501 + ```
+201
skills/opentui/references/solid/REFERENCE.md
··· 1 + # OpenTUI Solid (@opentui/solid) 2 + 3 + A SolidJS reconciler for building terminal user interfaces with fine-grained reactivity. Get optimal performance with Solid's signal-based approach. 4 + 5 + ## Overview 6 + 7 + OpenTUI Solid provides: 8 + - **Custom reconciler**: Solid components render to OpenTUI renderables 9 + - **JSX intrinsics**: `<text>`, `<box>`, `<input>`, etc. 10 + - **Hooks**: `useKeyboard`, `useRenderer`, `useTimeline`, etc. 11 + - **Fine-grained reactivity**: Only what changes re-renders 12 + - **Portal & Dynamic**: Advanced composition primitives 13 + 14 + ## When to Use Solid 15 + 16 + Use the Solid reconciler when: 17 + - You want optimal re-rendering performance 18 + - You prefer signal-based reactivity 19 + - You need fine-grained control over updates 20 + - Building performance-critical applications 21 + - You already know SolidJS 22 + 23 + ## When NOT to Use Solid 24 + 25 + | Scenario | Use Instead | 26 + |----------|-------------| 27 + | Team knows React, not Solid | `@opentui/react` | 28 + | Maximum control needed | `@opentui/core` | 29 + | Smallest bundle size | `@opentui/core` | 30 + | Building a framework/library | `@opentui/core` | 31 + 32 + ## Quick Start 33 + 34 + ```bash 35 + bunx create-tui@latest -t solid my-app 36 + cd my-app && bun install 37 + ``` 38 + 39 + The CLI creates the `my-app` directory for you - it must **not already exist**. 40 + 41 + Options: `--no-git` (skip git init), `--no-install` (skip bun install) 42 + 43 + **Agent guidance**: Always use autonomous mode with `-t <template>` flag. Never use interactive mode (`bunx create-tui@latest my-app` without `-t`) as it requires user prompts that agents cannot respond to. 44 + 45 + Or manually: 46 + 47 + ```bash 48 + bun install @opentui/solid @opentui/core solid-js 49 + ``` 50 + 51 + ```tsx 52 + import { render } from "@opentui/solid" 53 + import { createSignal } from "solid-js" 54 + 55 + function App() { 56 + const [count, setCount] = createSignal(0) 57 + 58 + return ( 59 + <box border padding={2}> 60 + <text>Count: {count()}</text> 61 + <box 62 + border 63 + onMouseDown={() => setCount(c => c + 1)} 64 + > 65 + <text>Click me!</text> 66 + </box> 67 + </box> 68 + ) 69 + } 70 + 71 + render(() => <App />) 72 + ``` 73 + 74 + ## Core Concepts 75 + 76 + ### Signals 77 + 78 + Solid uses signals for reactive state: 79 + 80 + ```tsx 81 + import { createSignal, createEffect } from "solid-js" 82 + 83 + function Counter() { 84 + const [count, setCount] = createSignal(0) 85 + 86 + // Effect runs when count changes 87 + createEffect(() => { 88 + console.log("Count is now:", count()) 89 + }) 90 + 91 + return <text>Count: {count()}</text> 92 + } 93 + ``` 94 + 95 + ### JSX Elements 96 + 97 + Solid maps JSX intrinsic elements to OpenTUI renderables: 98 + 99 + ```tsx 100 + // Note: Some use underscores (Solid convention) 101 + <text>Hello</text> // TextRenderable 102 + <box border>Content</box> // BoxRenderable 103 + <input placeholder="..." /> // InputRenderable 104 + <select options={[...]} /> // SelectRenderable 105 + <tab_select /> // TabSelectRenderable (underscore!) 106 + <ascii_font /> // ASCIIFontRenderable (underscore!) 107 + <line_number /> // LineNumberRenderable (underscore!) 108 + ``` 109 + 110 + ### Text Modifiers 111 + 112 + Inside `<text>`, use modifier elements: 113 + 114 + ```tsx 115 + <text> 116 + <strong>Bold</strong>, <em>italic</em>, and <u>underlined</u> 117 + <span fg="red">Colored text</span> 118 + <br /> 119 + New line with <a href="https://example.com">link</a> 120 + </text> 121 + ``` 122 + 123 + ## Available Components 124 + 125 + ### Layout & Display 126 + - `<text>` - Styled text content 127 + - `<box>` - Container with borders and layout 128 + - `<scrollbox>` - Scrollable container 129 + - `<ascii_font>` - ASCII art text (note underscore) 130 + 131 + ### Input 132 + - `<input>` - Single-line text input 133 + - `<textarea>` - Multi-line text input 134 + - `<select>` - List selection 135 + - `<tab_select>` - Tab-based selection (note underscore) 136 + 137 + ### Code & Diff 138 + - `<code>` - Syntax-highlighted code 139 + - `<line_number>` - Code with line numbers (note underscore) 140 + - `<diff>` - Unified or split diff viewer 141 + 142 + ### Text Modifiers (inside `<text>`) 143 + - `<span>` - Inline styled text 144 + - `<strong>`, `<b>` - Bold 145 + - `<em>`, `<i>` - Italic 146 + - `<u>` - Underline 147 + - `<br>` - Line break 148 + - `<a>` - Link 149 + 150 + ## Special Components 151 + 152 + ### Portal 153 + 154 + Render children to a different mount node: 155 + 156 + ```tsx 157 + import { Portal } from "@opentui/solid" 158 + 159 + function Overlay() { 160 + return ( 161 + <Portal mount={renderer.root}> 162 + <box position="absolute" left={10} top={5} border> 163 + <text>Overlay content</text> 164 + </box> 165 + </Portal> 166 + ) 167 + } 168 + ``` 169 + 170 + ### Dynamic 171 + 172 + Render components dynamically: 173 + 174 + ```tsx 175 + import { Dynamic } from "@opentui/solid" 176 + 177 + function DynamicInput(props: { multiline: boolean }) { 178 + return ( 179 + <Dynamic 180 + component={props.multiline ? "textarea" : "input"} 181 + placeholder="Enter text..." 182 + /> 183 + ) 184 + } 185 + ``` 186 + 187 + ## In This Reference 188 + 189 + - [Configuration](./configuration.md) - Project setup, tsconfig, bunfig, building 190 + - [API](./api.md) - Components, hooks, render function 191 + - [Patterns](./patterns.md) - Signals, stores, control flow, composition 192 + - [Gotchas](./gotchas.md) - Common issues, debugging, limitations 193 + 194 + ## See Also 195 + 196 + - [Core](../core/REFERENCE.md) - Underlying imperative API 197 + - [React](../react/REFERENCE.md) - Alternative declarative approach 198 + - [Components](../components/REFERENCE.md) - Component reference by category 199 + - [Layout](../layout/REFERENCE.md) - Flexbox layout system 200 + - [Keyboard](../keyboard/REFERENCE.md) - Input handling and shortcuts 201 + - [Testing](../testing/REFERENCE.md) - Test renderer and snapshots
+564
skills/opentui/references/solid/api.md
··· 1 + # Solid API Reference 2 + 3 + ## Rendering 4 + 5 + ### render(node, rendererOrConfig?) 6 + 7 + Renders a Solid component tree into a CLI renderer. 8 + 9 + ```tsx 10 + import { render } from "@opentui/solid" 11 + 12 + // Simple usage - creates renderer automatically 13 + render(() => <App />) 14 + 15 + // With config 16 + render(() => <App />, { 17 + exitOnCtrlC: false, 18 + targetFPS: 60, 19 + }) 20 + 21 + // With existing renderer 22 + import { createCliRenderer } from "@opentui/core" 23 + 24 + const renderer = await createCliRenderer() 25 + render(() => <App />, renderer) 26 + ``` 27 + 28 + ### testRender(node, options?) 29 + 30 + Create a test renderer for snapshots and tests. 31 + 32 + ```tsx 33 + import { testRender } from "@opentui/solid" 34 + 35 + const testSetup = await testRender(() => <App />, { 36 + width: 40, 37 + height: 10, 38 + }) 39 + 40 + // Access test utilities 41 + testSetup.snapshot() // Get current render 42 + testSetup.renderer // Access renderer 43 + ``` 44 + 45 + ### extend(components) 46 + 47 + Register custom renderables as JSX intrinsic elements. 48 + 49 + ```tsx 50 + import { extend } from "@opentui/solid" 51 + import { CustomRenderable } from "./custom" 52 + 53 + extend({ 54 + custom: CustomRenderable, 55 + }) 56 + 57 + // Now usable in JSX 58 + <custom prop="value" /> 59 + ``` 60 + 61 + ### getComponentCatalogue() 62 + 63 + Returns the current component catalogue. 64 + 65 + ```tsx 66 + import { getComponentCatalogue } from "@opentui/solid" 67 + 68 + const catalogue = getComponentCatalogue() 69 + console.log(Object.keys(catalogue)) 70 + ``` 71 + 72 + ## Hooks 73 + 74 + ### useRenderer() 75 + 76 + Access the OpenTUI renderer instance. 77 + 78 + ```tsx 79 + import { useRenderer } from "@opentui/solid" 80 + import { onMount } from "solid-js" 81 + 82 + function App() { 83 + const renderer = useRenderer() 84 + 85 + onMount(() => { 86 + console.log(`Terminal: ${renderer.width}x${renderer.height}`) 87 + renderer.console.show() 88 + 89 + // Access theme mode (dark/light based on terminal settings) 90 + console.log(`Theme: ${renderer.themeMode}`) // "dark" | "light" | null 91 + }) 92 + 93 + return <text>Hello</text> 94 + } 95 + 96 + // Listen for theme mode changes 97 + function ThemedApp() { 98 + const renderer = useRenderer() 99 + const [theme, setTheme] = createSignal(renderer.themeMode ?? "dark") 100 + 101 + onMount(() => { 102 + renderer.on("theme_mode", (mode: "dark" | "light") => setTheme(mode)) 103 + }) 104 + 105 + return ( 106 + <box backgroundColor={theme() === "dark" ? "#1a1a2e" : "#ffffff"}> 107 + <text fg={theme() === "dark" ? "#fff" : "#000"}> 108 + Current theme: {theme()} 109 + </text> 110 + </box> 111 + ) 112 + } 113 + ``` 114 + 115 + ### useKeyboard(handler, options?) 116 + 117 + Handle keyboard events. 118 + 119 + ```tsx 120 + import { useKeyboard, useRenderer } from "@opentui/solid" 121 + 122 + function App() { 123 + const renderer = useRenderer() 124 + 125 + useKeyboard((key) => { 126 + if (key.name === "escape") { 127 + renderer.destroy() // Never use process.exit() directly! 128 + } 129 + if (key.ctrl && key.name === "s") { 130 + saveDocument() 131 + } 132 + }) 133 + 134 + return <text>Press ESC to exit</text> 135 + } 136 + 137 + // With release events 138 + function GameControls() { 139 + const [pressed, setPressed] = createSignal(new Set<string>()) 140 + 141 + useKeyboard( 142 + (event) => { 143 + setPressed(keys => { 144 + const newKeys = new Set(keys) 145 + if (event.eventType === "release") { 146 + newKeys.delete(event.name) 147 + } else { 148 + newKeys.add(event.name) 149 + } 150 + return newKeys 151 + }) 152 + }, 153 + { release: true } 154 + ) 155 + 156 + return <text>Pressed: {Array.from(pressed()).join(", ")}</text> 157 + } 158 + ``` 159 + 160 + ### usePaste(handler) 161 + 162 + Handle paste events. Receives a `PasteEvent` with raw bytes. 163 + 164 + ```tsx 165 + import { usePaste } from "@opentui/solid" 166 + import { decodePasteBytes } from "@opentui/core" 167 + 168 + function PasteHandler() { 169 + usePaste((event) => { 170 + const text = decodePasteBytes(event.bytes) 171 + console.log("Pasted:", text) 172 + }) 173 + 174 + return <text>Paste something</text> 175 + } 176 + ``` 177 + 178 + ### onResize(callback) 179 + 180 + Handle terminal resize events. 181 + 182 + ```tsx 183 + import { onResize } from "@opentui/solid" 184 + 185 + function App() { 186 + onResize((width, height) => { 187 + console.log(`Resized to ${width}x${height}`) 188 + }) 189 + 190 + return <text>Resize the terminal</text> 191 + } 192 + ``` 193 + 194 + ### useTerminalDimensions() 195 + 196 + Get reactive terminal dimensions. 197 + 198 + ```tsx 199 + import { useTerminalDimensions } from "@opentui/solid" 200 + 201 + function ResponsiveLayout() { 202 + const dimensions = useTerminalDimensions() 203 + 204 + return ( 205 + <box flexDirection={dimensions().width > 80 ? "row" : "column"}> 206 + <text>Width: {dimensions().width}</text> 207 + <text>Height: {dimensions().height}</text> 208 + </box> 209 + ) 210 + } 211 + ``` 212 + 213 + ### onFocus(callback) / onBlur(callback) 214 + 215 + Handle terminal window focus and blur events. Solid-only hooks. 216 + 217 + ```tsx 218 + import { onFocus, onBlur } from "@opentui/solid" 219 + 220 + function App() { 221 + onFocus(() => { 222 + console.log("Terminal window gained focus") 223 + }) 224 + 225 + onBlur(() => { 226 + console.log("Terminal window lost focus") 227 + }) 228 + 229 + return <text>Focus/blur tracking</text> 230 + } 231 + ``` 232 + 233 + These hooks fire when the terminal emulator window gains or loses operating system focus. The renderer deduplicates events (won't re-emit the same focus state). 234 + 235 + ### useSelectionHandler(handler) 236 + 237 + Handle text selection events. Fires when the user finishes a mouse selection (mouse-up). Solid-only hook - React does not have this. 238 + 239 + ```tsx 240 + import { useSelectionHandler } from "@opentui/solid" 241 + import type { Selection } from "@opentui/core" 242 + 243 + function SelectableText() { 244 + const [selected, setSelected] = createSignal("") 245 + const renderer = useRenderer() 246 + 247 + useSelectionHandler((selection: Selection) => { 248 + const text = selection.getSelectedText() 249 + if (text) { 250 + setSelected(text) 251 + renderer.copyToClipboardOSC52(text) 252 + } 253 + }) 254 + 255 + return ( 256 + <box flexDirection="column"> 257 + <text selectable>Select this text with your mouse</text> 258 + <text fg="#888">Selected: {selected()}</text> 259 + </box> 260 + ) 261 + } 262 + ``` 263 + 264 + The `Selection` object aggregates selected text from all selectable renderables in the tree. See `keyboard/REFERENCE.md` (selection) for full details on the selection API and traversal model. 265 + 266 + ### useTimeline(options?) 267 + 268 + Create animations with the timeline system. 269 + 270 + ```tsx 271 + import { useTimeline } from "@opentui/solid" 272 + import { createSignal, onMount } from "solid-js" 273 + 274 + function AnimatedBox() { 275 + const [width, setWidth] = createSignal(0) 276 + 277 + const timeline = useTimeline({ 278 + duration: 2000, 279 + loop: false, 280 + }) 281 + 282 + onMount(() => { 283 + timeline.add( 284 + { width: 0 }, 285 + { 286 + width: 50, 287 + duration: 2000, 288 + ease: "easeOutQuad", 289 + onUpdate: (anim) => { 290 + setWidth(Math.round(anim.targets[0].width)) 291 + }, 292 + } 293 + ) 294 + }) 295 + 296 + return <box style={{ width: width(), height: 3, backgroundColor: "#6a5acd" }} /> 297 + } 298 + ``` 299 + 300 + ## Components 301 + 302 + ### Text Component 303 + 304 + ```tsx 305 + <text 306 + content="Hello" // Or use children 307 + fg="#FFFFFF" // Foreground color 308 + bg="#000000" // Background color 309 + selectable={true} // Allow text selection 310 + > 311 + {/* Use nested modifier tags for styling */} 312 + <span fg="red">Red</span> 313 + <strong>Bold</strong> 314 + <em>Italic</em> 315 + <u>Underline</u> 316 + <br /> 317 + <a href="https://...">Link</a> 318 + </text> 319 + ``` 320 + 321 + > **Note**: Do NOT use `bold`, `italic`, `underline` as props on `<text>`. Use nested modifier tags like `<strong>`, `<em>`, `<u>` instead. 322 + 323 + ### Box Component 324 + 325 + ```tsx 326 + <box 327 + // Borders 328 + border // Enable border 329 + borderStyle="single" // single | double | rounded | bold 330 + borderColor="#FFFFFF" 331 + title="Title" 332 + titleAlignment="center" // left | center | right 333 + 334 + // Colors 335 + backgroundColor="#1a1a2e" 336 + 337 + // Layout 338 + flexDirection="row" 339 + justifyContent="center" 340 + alignItems="center" 341 + gap={2} 342 + 343 + // Spacing 344 + padding={2} 345 + paddingX={2} // Horizontal (left + right) 346 + paddingY={1} // Vertical (top + bottom) 347 + margin={1} 348 + marginX={2} // Horizontal (left + right) 349 + marginY={1} // Vertical (top + bottom) 350 + 351 + // Dimensions 352 + width={40} 353 + height={10} 354 + flexGrow={1} 355 + 356 + // Focus 357 + focusable // Allow box to receive focus 358 + focused={isFocused()} // Controlled focus state 359 + 360 + // Events 361 + onMouseDown={(e) => {}} 362 + onMouseUp={(e) => {}} 363 + > 364 + {children} 365 + </box> 366 + ``` 367 + 368 + ### Scrollbox Component 369 + 370 + ```tsx 371 + <scrollbox 372 + focused // Enable keyboard scrolling 373 + style={{ 374 + scrollbarOptions: { 375 + showArrows: true, 376 + trackOptions: { 377 + foregroundColor: "#7aa2f7", 378 + backgroundColor: "#414868", 379 + }, 380 + }, 381 + }} 382 + > 383 + <For each={items()}> 384 + {(item) => <text>{item}</text>} 385 + </For> 386 + </scrollbox> 387 + ``` 388 + 389 + ### Input Component 390 + 391 + ```tsx 392 + <input 393 + value={value()} 394 + onInput={(newValue) => setValue(newValue)} 395 + placeholder="Enter text..." 396 + focused 397 + width={30} 398 + /> 399 + ``` 400 + 401 + ### Textarea Component 402 + 403 + ```tsx 404 + <textarea 405 + value={text()} 406 + onInput={(newValue) => setText(newValue)} 407 + placeholder="Enter multiple lines..." 408 + focused 409 + width={40} 410 + height={10} 411 + /> 412 + ``` 413 + 414 + ### Select Component 415 + 416 + ```tsx 417 + <select 418 + options={[ 419 + { name: "Option 1", description: "First", value: "1" }, 420 + { name: "Option 2", description: "Second", value: "2" }, 421 + ]} 422 + onChange={(index, option) => setSelected(option)} 423 + selectedIndex={0} 424 + focused 425 + /> 426 + ``` 427 + 428 + ### Tab Select Component (Note: underscore) 429 + 430 + ```tsx 431 + <tab_select 432 + options={[ 433 + { name: "Home", description: "Dashboard" }, 434 + { name: "Settings", description: "Configuration" }, 435 + ]} 436 + onChange={(index, option) => setTab(option)} 437 + tabWidth={20} 438 + focused 439 + /> 440 + ``` 441 + 442 + ### ASCII Font Component (Note: underscore) 443 + 444 + ```tsx 445 + <ascii_font 446 + text="TITLE" 447 + font="tiny" // tiny | block | slick | shade 448 + color="#FFFFFF" 449 + /> 450 + ``` 451 + 452 + ### Code Component 453 + 454 + ```tsx 455 + <code 456 + code={sourceCode} 457 + language="typescript" 458 + /> 459 + ``` 460 + 461 + ### Line Number Component (Note: underscore) 462 + 463 + ```tsx 464 + <line_number 465 + code={sourceCode} 466 + language="typescript" 467 + startLine={1} 468 + highlightedLines={[5]} 469 + /> 470 + ``` 471 + 472 + ### Diff Component 473 + 474 + ```tsx 475 + <diff 476 + oldCode={originalCode} 477 + newCode={modifiedCode} 478 + language="typescript" 479 + mode="unified" // unified | split 480 + syncScroll // Sync scroll between split view panes 481 + /> 482 + ``` 483 + 484 + ## Control Flow 485 + 486 + Solid's control flow components work with OpenTUI: 487 + 488 + ### For 489 + 490 + ```tsx 491 + import { For } from "solid-js" 492 + 493 + <For each={items()}> 494 + {(item, index) => ( 495 + <box key={index()}> 496 + <text>{item.name}</text> 497 + </box> 498 + )} 499 + </For> 500 + ``` 501 + 502 + ### Show 503 + 504 + ```tsx 505 + import { Show } from "solid-js" 506 + 507 + <Show when={isVisible()} fallback={<text>Hidden</text>}> 508 + <text>Visible content</text> 509 + </Show> 510 + ``` 511 + 512 + ### Switch/Match 513 + 514 + ```tsx 515 + import { Switch, Match } from "solid-js" 516 + 517 + <Switch> 518 + <Match when={status() === "loading"}> 519 + <text>Loading...</text> 520 + </Match> 521 + <Match when={status() === "error"}> 522 + <text fg="red">Error!</text> 523 + </Match> 524 + <Match when={status() === "success"}> 525 + <text fg="green">Success!</text> 526 + </Match> 527 + </Switch> 528 + ``` 529 + 530 + ### Index 531 + 532 + ```tsx 533 + import { Index } from "solid-js" 534 + 535 + <Index each={items()}> 536 + {(item, index) => ( 537 + <text>{index}: {item().name}</text> 538 + )} 539 + </Index> 540 + ``` 541 + 542 + ## Special Components 543 + 544 + ### Portal 545 + 546 + ```tsx 547 + import { Portal } from "@opentui/solid" 548 + 549 + <Portal mount={targetNode}> 550 + <box>Portal content</box> 551 + </Portal> 552 + ``` 553 + 554 + ### Dynamic 555 + 556 + ```tsx 557 + import { Dynamic } from "@opentui/solid" 558 + 559 + <Dynamic 560 + component={isMultiline() ? "textarea" : "input"} 561 + placeholder="Enter text..." 562 + focused 563 + /> 564 + ```
+316
skills/opentui/references/solid/configuration.md
··· 1 + # Solid Configuration 2 + 3 + ## Project Setup 4 + 5 + ### Quick Start 6 + 7 + ```bash 8 + bunx create-tui@latest -t solid my-app 9 + cd my-app && bun install 10 + ``` 11 + 12 + The CLI creates the `my-app` directory for you - it must **not already exist**. 13 + 14 + Options: `--no-git` (skip git init), `--no-install` (skip bun install) 15 + 16 + ### Manual Setup 17 + 18 + ```bash 19 + mkdir my-tui && cd my-tui 20 + bun init 21 + bun install @opentui/solid @opentui/core solid-js 22 + ``` 23 + 24 + ## TypeScript Configuration 25 + 26 + ### tsconfig.json 27 + 28 + ```json 29 + { 30 + "compilerOptions": { 31 + "lib": ["ESNext"], 32 + "target": "ESNext", 33 + "module": "NodeNext", 34 + "moduleResolution": "NodeNext", 35 + 36 + "jsx": "preserve", 37 + "jsxImportSource": "@opentui/solid", 38 + 39 + "strict": true, 40 + "skipLibCheck": true, 41 + "noEmit": true, 42 + "types": ["bun-types"] 43 + }, 44 + "include": ["src/**/*"] 45 + } 46 + ``` 47 + 48 + **Critical settings:** 49 + - `jsx: "preserve"` - Let Solid's compiler handle JSX 50 + - `jsxImportSource: "@opentui/solid"` - Import JSX runtime from OpenTUI Solid 51 + - `module` / `moduleResolution: "NodeNext"` - Recommended for OpenTUI compatibility 52 + 53 + ## Bun Configuration 54 + 55 + ### bunfig.toml 56 + 57 + **Required** for the Solid compiler: 58 + 59 + ```toml 60 + preload = ["@opentui/solid/preload"] 61 + ``` 62 + 63 + This loads the Solid JSX transform before your code runs. 64 + 65 + ## Package Configuration 66 + 67 + ### package.json 68 + 69 + ```json 70 + { 71 + "name": "my-tui-app", 72 + "type": "module", 73 + "scripts": { 74 + "start": "bun run src/index.tsx", 75 + "dev": "bun --watch run src/index.tsx", 76 + "test": "bun test", 77 + "build": "bun run build.ts" 78 + }, 79 + "dependencies": { 80 + "@opentui/core": "latest", 81 + "@opentui/solid": "latest", 82 + "solid-js": "latest" 83 + }, 84 + "devDependencies": { 85 + "@types/bun": "latest", 86 + "typescript": "latest" 87 + } 88 + } 89 + ``` 90 + 91 + ## Project Structure 92 + 93 + Recommended structure: 94 + 95 + ``` 96 + my-tui-app/ 97 + ├── src/ 98 + │ ├── components/ 99 + │ │ ├── Header.tsx 100 + │ │ ├── Sidebar.tsx 101 + │ │ └── MainContent.tsx 102 + │ ├── stores/ 103 + │ │ └── appStore.ts 104 + │ ├── App.tsx 105 + │ └── index.tsx 106 + ├── bunfig.toml # Required! 107 + ├── package.json 108 + └── tsconfig.json 109 + ``` 110 + 111 + ### Entry Point (src/index.tsx) 112 + 113 + ```tsx 114 + import { render } from "@opentui/solid" 115 + import { App } from "./App" 116 + 117 + render(() => <App />) 118 + ``` 119 + 120 + ### App Component (src/App.tsx) 121 + 122 + ```tsx 123 + import { Header } from "./components/Header" 124 + import { Sidebar } from "./components/Sidebar" 125 + import { MainContent } from "./components/MainContent" 126 + 127 + export function App() { 128 + return ( 129 + <box flexDirection="column" width="100%" height="100%"> 130 + <Header /> 131 + <box flexDirection="row" flexGrow={1}> 132 + <Sidebar /> 133 + <MainContent /> 134 + </box> 135 + </box> 136 + ) 137 + } 138 + ``` 139 + 140 + ## Renderer Configuration 141 + 142 + ### render() Options 143 + 144 + ```tsx 145 + import { render } from "@opentui/solid" 146 + import { ConsolePosition } from "@opentui/core" 147 + 148 + render(() => <App />, { 149 + // Rendering 150 + targetFPS: 60, 151 + 152 + // Behavior 153 + exitOnCtrlC: true, 154 + autoFocus: true, // Auto-focus elements on click (default: true) 155 + useMouse: true, // Enable mouse support (default: true) 156 + 157 + // Debug console 158 + consoleOptions: { 159 + position: ConsolePosition.BOTTOM, 160 + sizePercent: 30, 161 + startInDebugMode: false, 162 + }, 163 + 164 + // Cleanup 165 + onDestroy: () => { 166 + // Cleanup code 167 + }, 168 + }) 169 + ``` 170 + 171 + ### Using Existing Renderer 172 + 173 + ```tsx 174 + import { render } from "@opentui/solid" 175 + import { createCliRenderer } from "@opentui/core" 176 + 177 + const renderer = await createCliRenderer({ 178 + exitOnCtrlC: false, 179 + }) 180 + 181 + render(() => <App />, renderer) 182 + ``` 183 + 184 + ## Building for Distribution 185 + 186 + ### Build Script (build.ts) 187 + 188 + ```typescript 189 + import solidPlugin from "@opentui/solid/bun-plugin" 190 + 191 + await Bun.build({ 192 + entrypoints: ["./src/index.tsx"], 193 + outdir: "./dist", 194 + target: "bun", 195 + minify: true, 196 + plugins: [solidPlugin], 197 + }) 198 + 199 + console.log("Build complete!") 200 + ``` 201 + 202 + Run: `bun run build.ts` 203 + 204 + ### Creating Executables 205 + 206 + ```typescript 207 + import solidPlugin from "@opentui/solid/bun-plugin" 208 + 209 + await Bun.build({ 210 + entrypoints: ["./src/index.tsx"], 211 + target: "bun", 212 + plugins: [solidPlugin], 213 + compile: { 214 + target: "bun-darwin-arm64", // or bun-linux-x64, etc. 215 + outfile: "my-app", 216 + }, 217 + }) 218 + ``` 219 + 220 + **Available targets:** 221 + - `bun-darwin-arm64` - macOS Apple Silicon 222 + - `bun-darwin-x64` - macOS Intel 223 + - `bun-linux-x64` - Linux x64 224 + - `bun-linux-arm64` - Linux ARM64 225 + - `bun-windows-x64` - Windows x64 226 + 227 + ## Environment Variables 228 + 229 + Create `.env` for development: 230 + 231 + ```env 232 + # Debug settings 233 + OTUI_SHOW_STATS=false 234 + SHOW_CONSOLE=false 235 + 236 + # App settings 237 + API_URL=https://api.example.com 238 + ``` 239 + 240 + Bun auto-loads `.env` files: 241 + 242 + ```tsx 243 + const apiUrl = process.env.API_URL 244 + ``` 245 + 246 + ## Testing Configuration 247 + 248 + ### Test Setup 249 + 250 + ```typescript 251 + // src/test-utils.tsx 252 + import { testRender } from "@opentui/solid" 253 + 254 + export async function renderForTest( 255 + Component: () => JSX.Element, 256 + options = { width: 80, height: 24 } 257 + ) { 258 + return await testRender(Component, options) 259 + } 260 + ``` 261 + 262 + ### Test Example 263 + 264 + ```typescript 265 + // src/components/Counter.test.tsx 266 + import { test, expect } from "bun:test" 267 + import { renderForTest } from "../test-utils" 268 + import { Counter } from "./Counter" 269 + 270 + test("Counter renders initial value", async () => { 271 + const { snapshot } = await renderForTest(() => <Counter initialValue={5} />) 272 + expect(snapshot()).toContain("Count: 5") 273 + }) 274 + ``` 275 + 276 + ## Common Configuration Issues 277 + 278 + ### Missing bunfig.toml 279 + 280 + **Symptom**: JSX not transformed, syntax errors 281 + 282 + **Fix**: Create `bunfig.toml` with preload: 283 + 284 + ```toml 285 + preload = ["@opentui/solid/preload"] 286 + ``` 287 + 288 + ### Wrong JSX Settings 289 + 290 + **Symptom**: JSX compiles to React calls 291 + 292 + **Fix**: Ensure tsconfig has: 293 + 294 + ```json 295 + { 296 + "compilerOptions": { 297 + "jsx": "preserve", 298 + "jsxImportSource": "@opentui/solid" 299 + } 300 + } 301 + ``` 302 + 303 + ### Build Missing Plugin 304 + 305 + **Symptom**: Built output has untransformed JSX 306 + 307 + **Fix**: Add Solid plugin to build: 308 + 309 + ```typescript 310 + import solidPlugin from "@opentui/solid/bun-plugin" 311 + 312 + await Bun.build({ 313 + // ... 314 + plugins: [solidPlugin], 315 + }) 316 + ```
+427
skills/opentui/references/solid/gotchas.md
··· 1 + # Solid Gotchas 2 + 3 + ## Critical 4 + 5 + ### Never use `process.exit()` directly 6 + 7 + **This is the most common mistake.** Using `process.exit()` leaves the terminal in a broken state (cursor hidden, raw mode, alternate screen). 8 + 9 + ```tsx 10 + // WRONG - Terminal left in broken state 11 + process.exit(0) 12 + 13 + // CORRECT - Use renderer.destroy() 14 + import { useRenderer } from "@opentui/solid" 15 + 16 + function App() { 17 + const renderer = useRenderer() 18 + 19 + const handleExit = () => { 20 + renderer.destroy() // Cleans up and exits properly 21 + } 22 + } 23 + ``` 24 + 25 + `renderer.destroy()` restores the terminal (exits alternate screen, restores cursor, etc.) before exiting. 26 + 27 + ## Configuration Issues 28 + 29 + ### Missing bunfig.toml 30 + 31 + **Symptom**: JSX syntax errors, components not rendering 32 + 33 + ``` 34 + SyntaxError: Unexpected token '<' 35 + ``` 36 + 37 + **Fix**: Create `bunfig.toml` in project root: 38 + 39 + ```toml 40 + preload = ["@opentui/solid/preload"] 41 + ``` 42 + 43 + ### Wrong JSX Settings 44 + 45 + **Symptom**: JSX compiles to React, errors about React not found 46 + 47 + **Fix**: Ensure tsconfig.json has: 48 + 49 + ```json 50 + { 51 + "compilerOptions": { 52 + "jsx": "preserve", 53 + "jsxImportSource": "@opentui/solid" 54 + } 55 + } 56 + ``` 57 + 58 + ### Build Without Plugin 59 + 60 + **Symptom**: Built bundle has raw JSX 61 + 62 + **Fix**: Add Solid plugin to build: 63 + 64 + ```typescript 65 + import solidPlugin from "@opentui/solid/bun-plugin" 66 + 67 + await Bun.build({ 68 + // ... 69 + plugins: [solidPlugin], 70 + }) 71 + ``` 72 + 73 + ## Reactivity Issues 74 + 75 + ### Accessing Signals Without Calling 76 + 77 + **Symptom**: Value never updates, shows `[Function]` 78 + 79 + ```tsx 80 + // WRONG - Missing () 81 + const [count, setCount] = createSignal(0) 82 + <text>Count: {count}</text> // Shows [Function] 83 + 84 + // CORRECT 85 + <text>Count: {count()}</text> 86 + ``` 87 + 88 + ### Breaking Reactivity with Destructuring 89 + 90 + **Symptom**: Props stop being reactive 91 + 92 + ```tsx 93 + // WRONG - Breaks reactivity 94 + function Component(props: { value: number }) { 95 + const { value } = props // Destructured once, never updates! 96 + return <text>{value}</text> 97 + } 98 + 99 + // CORRECT - Keep props reactive 100 + function Component(props: { value: number }) { 101 + return <text>{props.value}</text> 102 + } 103 + 104 + // OR use splitProps 105 + function Component(props: { value: number; other: string }) { 106 + const [local, rest] = splitProps(props, ["value"]) 107 + return <text>{local.value}</text> 108 + } 109 + ``` 110 + 111 + ### Effects Not Running 112 + 113 + **Symptom**: createEffect doesn't trigger 114 + 115 + ```tsx 116 + // WRONG - Signal not accessed in effect 117 + const [count, setCount] = createSignal(0) 118 + 119 + createEffect(() => { 120 + console.log("Count changed") // Never runs after initial! 121 + }) 122 + 123 + // CORRECT - Access the signal 124 + createEffect(() => { 125 + console.log("Count:", count()) // Runs when count changes 126 + }) 127 + ``` 128 + 129 + ## HTML Entity Decoding 130 + 131 + Solid's reconciler automatically decodes HTML entities in JSX text content. This means `&lt;`, `&gt;`, `&amp;`, etc. render as their literal characters: 132 + 133 + ```tsx 134 + // These render correctly in Solid 135 + <text>Use &lt;box&gt; for containers</text> // Displays: Use <box> for containers 136 + <text>A &amp; B</text> // Displays: A & B 137 + ``` 138 + 139 + This applies to text nodes, the `content` prop, and the `text` prop. 140 + 141 + ## Component Naming 142 + 143 + ### Underscore vs Hyphen 144 + 145 + Solid uses underscores for multi-word component names: 146 + 147 + ```tsx 148 + // WRONG - React-style naming 149 + <tab-select /> // Error! 150 + <ascii-font /> // Error! 151 + <line-number /> // Error! 152 + 153 + // CORRECT - Solid naming 154 + <tab_select /> 155 + <ascii_font /> 156 + <line_number /> 157 + ``` 158 + 159 + **Component mapping:** 160 + | Concept | React | Solid | 161 + |---------|-------|-------| 162 + | Tab Select | `<tab-select>` | `<tab_select>` | 163 + | ASCII Font | `<ascii-font>` | `<ascii_font>` | 164 + | Line Number | `<line-number>` | `<line_number>` | 165 + 166 + ## Focus Issues 167 + 168 + ### Focus Not Working 169 + 170 + Components need explicit focus: 171 + 172 + ```tsx 173 + // WRONG 174 + <input placeholder="Type here..." /> 175 + 176 + // CORRECT 177 + <input placeholder="Type here..." focused /> 178 + ``` 179 + 180 + ### Select Not Responding 181 + 182 + ```tsx 183 + // WRONG 184 + <select options={["a", "b"]} /> 185 + 186 + // CORRECT 187 + <select 188 + options={[ 189 + { name: "A", description: "Option A", value: "a" }, 190 + { name: "B", description: "Option B", value: "b" }, 191 + ]} 192 + onSelect={(index, option) => { 193 + // Called when Enter is pressed 194 + console.log("Selected:", option.name) 195 + }} 196 + focused 197 + /> 198 + ``` 199 + 200 + ### Select Events Confusion 201 + 202 + Remember: `onSelect` fires on Enter (selection confirmed), `onChange` fires on navigation: 203 + 204 + ```tsx 205 + // WRONG - expecting onChange to fire on Enter 206 + <select 207 + options={options()} 208 + onChange={(i, opt) => submitForm(opt)} // This fires on arrow keys! 209 + /> 210 + 211 + // CORRECT 212 + <select 213 + options={options()} 214 + onSelect={(i, opt) => submitForm(opt)} // Enter pressed - submit 215 + onChange={(i, opt) => showPreview(opt)} // Arrow keys - preview 216 + /> 217 + ``` 218 + 219 + ## Control Flow Issues 220 + 221 + ### For vs Index 222 + 223 + Use `For` for arrays of objects, `Index` for primitives: 224 + 225 + ```tsx 226 + // For objects - item is reactive 227 + <For each={objects()}> 228 + {(obj) => <text>{obj.name}</text>} 229 + </For> 230 + 231 + // For primitives - use Index, item() is reactive 232 + <Index each={strings()}> 233 + {(str, index) => <text>{index}: {str()}</text>} 234 + </Index> 235 + ``` 236 + 237 + ### Missing Fallback 238 + 239 + Show requires fallback for proper rendering: 240 + 241 + ```tsx 242 + // May cause issues 243 + <Show when={data()}> 244 + <Component /> 245 + </Show> 246 + 247 + // Better - explicit fallback 248 + <Show when={data()} fallback={<text>Loading...</text>}> 249 + <Component /> 250 + </Show> 251 + ``` 252 + 253 + ## Cleanup Issues 254 + 255 + ### Forgetting onCleanup 256 + 257 + **Symptom**: Memory leaks, multiple intervals running 258 + 259 + ```tsx 260 + // WRONG - Interval never cleared 261 + function Timer() { 262 + const [time, setTime] = createSignal(0) 263 + 264 + setInterval(() => setTime(t => t + 1), 1000) 265 + 266 + return <text>{time()}</text> 267 + } 268 + 269 + // CORRECT 270 + function Timer() { 271 + const [time, setTime] = createSignal(0) 272 + 273 + const interval = setInterval(() => setTime(t => t + 1), 1000) 274 + onCleanup(() => clearInterval(interval)) 275 + 276 + return <text>{time()}</text> 277 + } 278 + ``` 279 + 280 + ### Effect Cleanup 281 + 282 + ```tsx 283 + createEffect(() => { 284 + const subscription = subscribe(data()) 285 + 286 + // WRONG - No cleanup 287 + // subscription stays active 288 + 289 + // CORRECT 290 + onCleanup(() => subscription.unsubscribe()) 291 + }) 292 + ``` 293 + 294 + ## Store Issues 295 + 296 + ### Mutating Store Directly 297 + 298 + **Symptom**: Changes don't trigger updates 299 + 300 + ```tsx 301 + const [state, setState] = createStore({ items: [] }) 302 + 303 + // WRONG - Direct mutation 304 + state.items.push(newItem) // Won't trigger updates! 305 + 306 + // CORRECT - Use setState 307 + setState("items", items => [...items, newItem]) 308 + ``` 309 + 310 + ### Nested Updates 311 + 312 + ```tsx 313 + const [state, setState] = createStore({ 314 + user: { profile: { name: "John" } } 315 + }) 316 + 317 + // WRONG 318 + state.user.profile.name = "Jane" 319 + 320 + // CORRECT 321 + setState("user", "profile", "name", "Jane") 322 + ``` 323 + 324 + ## Debugging 325 + 326 + ### Console Not Visible 327 + 328 + OpenTUI captures console output: 329 + 330 + ```tsx 331 + import { useRenderer } from "@opentui/solid" 332 + import { onMount } from "solid-js" 333 + 334 + function App() { 335 + const renderer = useRenderer() 336 + 337 + onMount(() => { 338 + renderer.console.show() 339 + console.log("Now visible!") 340 + }) 341 + 342 + return <box>{/* ... */}</box> 343 + } 344 + ``` 345 + 346 + ### Tracking Reactivity 347 + 348 + Use `createEffect` to debug: 349 + 350 + ```tsx 351 + createEffect(() => { 352 + console.log("State:", { 353 + count: count(), 354 + items: items(), 355 + }) 356 + }) 357 + ``` 358 + 359 + ## Runtime Issues 360 + 361 + ### Use Bun 362 + 363 + ```bash 364 + # WRONG 365 + node src/index.tsx 366 + npm run start 367 + 368 + # CORRECT 369 + bun run src/index.tsx 370 + bun run start 371 + ``` 372 + 373 + ### Async render() 374 + 375 + The render function is async when creating a renderer: 376 + 377 + ```tsx 378 + // This is fine - Bun supports top-level await 379 + render(() => <App />) 380 + 381 + // If you need the renderer 382 + import { createCliRenderer } from "@opentui/core" 383 + import { render } from "@opentui/solid" 384 + 385 + const renderer = await createCliRenderer() 386 + render(() => <App />, renderer) 387 + ``` 388 + 389 + ## Common Error Messages 390 + 391 + ### "Cannot read properties of undefined" 392 + 393 + Usually a missing reactive access: 394 + 395 + ```tsx 396 + // Check if signal is being called 397 + <text>{count()}</text> // Note the () 398 + 399 + // Check if props are being accessed correctly 400 + <text>{props.value}</text> // Not destructured 401 + ``` 402 + 403 + ### "JSX element has no corresponding closing tag" 404 + 405 + Check component naming: 406 + 407 + ```tsx 408 + // Wrong 409 + <tab-select></tab-select> 410 + 411 + // Correct 412 + <tab_select></tab_select> 413 + ``` 414 + 415 + ### "store is not a function" 416 + 417 + Stores aren't called like signals: 418 + 419 + ```tsx 420 + const [store, setStore] = createStore({ count: 0 }) 421 + 422 + // WRONG 423 + <text>{store().count}</text> 424 + 425 + // CORRECT 426 + <text>{store.count}</text> 427 + ```
+560
skills/opentui/references/solid/patterns.md
··· 1 + # Solid Patterns 2 + 3 + ## Reactive State 4 + 5 + ### Signals 6 + 7 + Basic reactive state with signals: 8 + 9 + ```tsx 10 + import { createSignal } from "solid-js" 11 + 12 + function Counter() { 13 + const [count, setCount] = createSignal(0) 14 + 15 + return ( 16 + <box flexDirection="row" gap={2}> 17 + <text>Count: {count()}</text> 18 + <box border onMouseDown={() => setCount(c => c - 1)}> 19 + <text>-</text> 20 + </box> 21 + <box border onMouseDown={() => setCount(c => c + 1)}> 22 + <text>+</text> 23 + </box> 24 + </box> 25 + ) 26 + } 27 + ``` 28 + 29 + ### Derived State 30 + 31 + Compute values from signals: 32 + 33 + ```tsx 34 + import { createSignal, createMemo } from "solid-js" 35 + 36 + function PriceCalculator() { 37 + const [quantity, setQuantity] = createSignal(1) 38 + const [price, setPrice] = createSignal(9.99) 39 + 40 + // Derived value - only recalculates when dependencies change 41 + const total = createMemo(() => quantity() * price()) 42 + const formatted = createMemo(() => `$${total().toFixed(2)}`) 43 + 44 + return ( 45 + <box flexDirection="column"> 46 + <text>Quantity: {quantity()}</text> 47 + <text>Price: ${price()}</text> 48 + <text>Total: {formatted()}</text> 49 + </box> 50 + ) 51 + } 52 + ``` 53 + 54 + ### Effects 55 + 56 + React to state changes: 57 + 58 + ```tsx 59 + import { createSignal, createEffect, onCleanup } from "solid-js" 60 + 61 + function AutoSave() { 62 + const [content, setContent] = createSignal("") 63 + 64 + createEffect(() => { 65 + const text = content() 66 + 67 + // Debounced save 68 + const timeout = setTimeout(() => { 69 + saveToFile(text) 70 + }, 1000) 71 + 72 + // Cleanup on next run or disposal 73 + onCleanup(() => clearTimeout(timeout)) 74 + }) 75 + 76 + return ( 77 + <textarea 78 + value={content()} 79 + onInput={setContent} 80 + placeholder="Auto-saves after 1 second..." 81 + /> 82 + ) 83 + } 84 + ``` 85 + 86 + ## Stores 87 + 88 + ### createStore for Complex State 89 + 90 + ```tsx 91 + import { createStore } from "solid-js/store" 92 + 93 + interface AppState { 94 + user: { name: string; email: string } | null 95 + items: Array<{ id: number; name: string; done: boolean }> 96 + settings: { theme: "dark" | "light" } 97 + } 98 + 99 + function App() { 100 + const [state, setState] = createStore<AppState>({ 101 + user: null, 102 + items: [], 103 + settings: { theme: "dark" }, 104 + }) 105 + 106 + const addItem = (name: string) => { 107 + setState("items", items => [ 108 + ...items, 109 + { id: Date.now(), name, done: false } 110 + ]) 111 + } 112 + 113 + const toggleItem = (id: number) => { 114 + setState("items", item => item.id === id, "done", done => !done) 115 + } 116 + 117 + const setTheme = (theme: "dark" | "light") => { 118 + setState("settings", "theme", theme) 119 + } 120 + 121 + return ( 122 + <box backgroundColor={state.settings.theme === "dark" ? "#1a1a2e" : "#f0f0f0"}> 123 + <For each={state.items}> 124 + {(item) => ( 125 + <text 126 + fg={item.done ? "#888" : "#fff"} 127 + onMouseDown={() => toggleItem(item.id)} 128 + > 129 + {item.done ? "[x]" : "[ ]"} {item.name} 130 + </text> 131 + )} 132 + </For> 133 + </box> 134 + ) 135 + } 136 + ``` 137 + 138 + ### Store with Context 139 + 140 + Share state across components: 141 + 142 + ```tsx 143 + import { createStore } from "solid-js/store" 144 + import { createContext, useContext, ParentComponent } from "solid-js" 145 + 146 + interface Store { 147 + count: number 148 + items: string[] 149 + } 150 + 151 + type StoreContextValue = [ 152 + Store, 153 + { 154 + increment: () => void 155 + addItem: (item: string) => void 156 + } 157 + ] 158 + 159 + const StoreContext = createContext<StoreContextValue>() 160 + 161 + const StoreProvider: ParentComponent = (props) => { 162 + const [state, setState] = createStore<Store>({ 163 + count: 0, 164 + items: [], 165 + }) 166 + 167 + const actions = { 168 + increment: () => setState("count", c => c + 1), 169 + addItem: (item: string) => setState("items", i => [...i, item]), 170 + } 171 + 172 + return ( 173 + <StoreContext.Provider value={[state, actions]}> 174 + {props.children} 175 + </StoreContext.Provider> 176 + ) 177 + } 178 + 179 + function useStore() { 180 + const context = useContext(StoreContext) 181 + if (!context) throw new Error("useStore must be used within StoreProvider") 182 + return context 183 + } 184 + 185 + // Usage 186 + function Counter() { 187 + const [state, { increment }] = useStore() 188 + return ( 189 + <box onMouseDown={increment}> 190 + <text>Count: {state.count}</text> 191 + </box> 192 + ) 193 + } 194 + ``` 195 + 196 + ## Control Flow 197 + 198 + ### Conditional Rendering with Show 199 + 200 + ```tsx 201 + import { Show, createSignal } from "solid-js" 202 + 203 + function ToggleableContent() { 204 + const [visible, setVisible] = createSignal(false) 205 + 206 + return ( 207 + <box flexDirection="column"> 208 + <box border onMouseDown={() => setVisible(v => !v)}> 209 + <text>Toggle</text> 210 + </box> 211 + 212 + <Show 213 + when={visible()} 214 + fallback={<text fg="#888">Content is hidden</text>} 215 + > 216 + <text fg="#0f0">Content is visible!</text> 217 + </Show> 218 + </box> 219 + ) 220 + } 221 + ``` 222 + 223 + ### Lists with For 224 + 225 + ```tsx 226 + import { For, createSignal } from "solid-js" 227 + 228 + function TodoList() { 229 + const [todos, setTodos] = createSignal([ 230 + { id: 1, text: "Learn Solid", done: false }, 231 + { id: 2, text: "Build TUI", done: false }, 232 + ]) 233 + 234 + const toggle = (id: number) => { 235 + setTodos(todos => 236 + todos.map(t => 237 + t.id === id ? { ...t, done: !t.done } : t 238 + ) 239 + ) 240 + } 241 + 242 + return ( 243 + <box flexDirection="column"> 244 + <For each={todos()}> 245 + {(todo) => ( 246 + <box onMouseDown={() => toggle(todo.id)}> 247 + <text fg={todo.done ? "#888" : "#fff"}> 248 + {todo.done ? "[x]" : "[ ]"} {todo.text} 249 + </text> 250 + </box> 251 + )} 252 + </For> 253 + </box> 254 + ) 255 + } 256 + ``` 257 + 258 + ### Index for Primitive Arrays 259 + 260 + Use `Index` when array items are primitives: 261 + 262 + ```tsx 263 + import { Index, createSignal } from "solid-js" 264 + 265 + function StringList() { 266 + const [items, setItems] = createSignal(["apple", "banana", "cherry"]) 267 + 268 + return ( 269 + <box flexDirection="column"> 270 + <Index each={items()}> 271 + {(item, index) => ( 272 + <text>{index}: {item()}</text> 273 + )} 274 + </Index> 275 + </box> 276 + ) 277 + } 278 + ``` 279 + 280 + ### Switch/Match for Multiple Conditions 281 + 282 + ```tsx 283 + import { Switch, Match, createSignal } from "solid-js" 284 + 285 + type Status = "idle" | "loading" | "success" | "error" 286 + 287 + function StatusDisplay() { 288 + const [status, setStatus] = createSignal<Status>("idle") 289 + 290 + return ( 291 + <Switch> 292 + <Match when={status() === "idle"}> 293 + <text>Ready</text> 294 + </Match> 295 + <Match when={status() === "loading"}> 296 + <text fg="#ff0">Loading...</text> 297 + </Match> 298 + <Match when={status() === "success"}> 299 + <text fg="#0f0">Success!</text> 300 + </Match> 301 + <Match when={status() === "error"}> 302 + <text fg="#f00">Error occurred</text> 303 + </Match> 304 + </Switch> 305 + ) 306 + } 307 + ``` 308 + 309 + ## Focus Management 310 + 311 + ### Focus State 312 + 313 + ```tsx 314 + import { createSignal } from "solid-js" 315 + import { useKeyboard } from "@opentui/solid" 316 + 317 + function FocusableForm() { 318 + const [focusIndex, setFocusIndex] = createSignal(0) 319 + const fields = ["name", "email", "message"] 320 + 321 + useKeyboard((key) => { 322 + if (key.name === "tab") { 323 + setFocusIndex(i => (i + 1) % fields.length) 324 + } 325 + if (key.shift && key.name === "tab") { 326 + setFocusIndex(i => (i - 1 + fields.length) % fields.length) 327 + } 328 + }) 329 + 330 + return ( 331 + <box flexDirection="column" gap={1}> 332 + <Index each={fields}> 333 + {(field, i) => ( 334 + <input 335 + placeholder={`Enter ${field()}...`} 336 + focused={i === focusIndex()} 337 + /> 338 + )} 339 + </Index> 340 + </box> 341 + ) 342 + } 343 + ``` 344 + 345 + ## Keyboard Navigation 346 + 347 + ### Global Shortcuts 348 + 349 + ```tsx 350 + import { useKeyboard } from "@opentui/solid" 351 + 352 + function App() { 353 + const renderer = useRenderer() 354 + 355 + useKeyboard((key) => { 356 + if (key.name === "escape") { 357 + renderer.destroy() // Never use process.exit() directly! 358 + } 359 + 360 + if (key.ctrl && key.name === "s") { 361 + save() 362 + } 363 + 364 + // Vim-style 365 + if (key.name === "j") moveDown() 366 + if (key.name === "k") moveUp() 367 + }) 368 + 369 + return <box>{/* ... */}</box> 370 + } 371 + ``` 372 + 373 + ## Responsive Design 374 + 375 + ### Terminal-size Responsive 376 + 377 + ```tsx 378 + import { useTerminalDimensions } from "@opentui/solid" 379 + 380 + function ResponsiveLayout() { 381 + const dims = useTerminalDimensions() 382 + 383 + return ( 384 + <box flexDirection={dims().width > 80 ? "row" : "column"}> 385 + <box flexGrow={1}> 386 + <text>Panel 1</text> 387 + </box> 388 + <box flexGrow={1}> 389 + <text>Panel 2</text> 390 + </box> 391 + </box> 392 + ) 393 + } 394 + ``` 395 + 396 + ## Async Data 397 + 398 + ### Resources 399 + 400 + ```tsx 401 + import { createResource, Suspense } from "solid-js" 402 + 403 + async function fetchData() { 404 + const response = await fetch("https://api.example.com/data") 405 + return response.json() 406 + } 407 + 408 + function DataDisplay() { 409 + const [data] = createResource(fetchData) 410 + 411 + return ( 412 + <Suspense fallback={<text>Loading...</text>}> 413 + <Show when={data()}> 414 + {(items) => ( 415 + <For each={items()}> 416 + {(item) => <text>{item.name}</text>} 417 + </For> 418 + )} 419 + </Show> 420 + </Suspense> 421 + ) 422 + } 423 + ``` 424 + 425 + ### Error Handling 426 + 427 + ```tsx 428 + import { createResource, Show, ErrorBoundary } from "solid-js" 429 + 430 + function SafeDataDisplay() { 431 + const [data] = createResource(fetchData) 432 + 433 + return ( 434 + <ErrorBoundary fallback={(err) => <text fg="red">Error: {err.message}</text>}> 435 + <Show 436 + when={!data.loading} 437 + fallback={<text>Loading...</text>} 438 + > 439 + <Show 440 + when={!data.error} 441 + fallback={<text fg="red">Failed to load</text>} 442 + > 443 + <For each={data()}> 444 + {(item) => <text>{item.name}</text>} 445 + </For> 446 + </Show> 447 + </Show> 448 + </ErrorBoundary> 449 + ) 450 + } 451 + ``` 452 + 453 + ## Component Composition 454 + 455 + ### Props and Children 456 + 457 + ```tsx 458 + import { ParentComponent, JSX } from "solid-js" 459 + 460 + interface PanelProps { 461 + title: string 462 + children: JSX.Element 463 + } 464 + 465 + const Panel: ParentComponent<{ title: string }> = (props) => { 466 + return ( 467 + <box border padding={1} flexDirection="column"> 468 + <text fg="#0ff">{props.title}</text> 469 + <box marginTop={1}> 470 + {props.children} 471 + </box> 472 + </box> 473 + ) 474 + } 475 + 476 + // Usage 477 + <Panel title="Settings"> 478 + <text>Panel content here</text> 479 + </Panel> 480 + ``` 481 + 482 + ### Spread Props 483 + 484 + ```tsx 485 + import { splitProps } from "solid-js" 486 + 487 + interface ButtonProps { 488 + label: string 489 + onClick: () => void 490 + // ...rest goes to box 491 + } 492 + 493 + function Button(props: ButtonProps) { 494 + const [local, rest] = splitProps(props, ["label", "onClick"]) 495 + 496 + return ( 497 + <box border onMouseDown={local.onClick} {...rest}> 498 + <text>{local.label}</text> 499 + </box> 500 + ) 501 + } 502 + ``` 503 + 504 + ## Animation 505 + 506 + ### With Timeline 507 + 508 + ```tsx 509 + import { createSignal, onMount } from "solid-js" 510 + import { useTimeline } from "@opentui/solid" 511 + 512 + function AnimatedProgress() { 513 + const [width, setWidth] = createSignal(0) 514 + 515 + const timeline = useTimeline({ 516 + duration: 2000, 517 + }) 518 + 519 + onMount(() => { 520 + timeline.add( 521 + { value: 0 }, 522 + { 523 + value: 50, 524 + duration: 2000, 525 + ease: "easeOutQuad", 526 + onUpdate: (anim) => { 527 + setWidth(Math.round(anim.targets[0].value)) 528 + }, 529 + } 530 + ) 531 + }) 532 + 533 + return ( 534 + <box flexDirection="column" gap={1}> 535 + <text>Progress: {width()}%</text> 536 + <box width={50} height={1} backgroundColor="#333"> 537 + <box width={width()} height={1} backgroundColor="#0f0" /> 538 + </box> 539 + </box> 540 + ) 541 + } 542 + ``` 543 + 544 + ### Interval-based 545 + 546 + ```tsx 547 + import { createSignal, onCleanup } from "solid-js" 548 + 549 + function Clock() { 550 + const [time, setTime] = createSignal(new Date()) 551 + 552 + const interval = setInterval(() => { 553 + setTime(new Date()) 554 + }, 1000) 555 + 556 + onCleanup(() => clearInterval(interval)) 557 + 558 + return <text>{time().toLocaleTimeString()}</text> 559 + } 560 + ```
+614
skills/opentui/references/testing/REFERENCE.md
··· 1 + # Testing OpenTUI Applications 2 + 3 + How to test terminal user interfaces built with OpenTUI. 4 + 5 + ## Overview 6 + 7 + OpenTUI provides: 8 + - **Test Renderer**: Headless renderer for testing 9 + - **Snapshot Testing**: Verify visual output 10 + - **Interaction Testing**: Simulate user input 11 + 12 + ## When to Use 13 + 14 + Use this reference when you need snapshot tests, interaction testing, or renderer-based regression checks. 15 + 16 + ## Test Setup 17 + 18 + ### Bun Test Runner 19 + 20 + OpenTUI uses Bun's built-in test runner: 21 + 22 + ```typescript 23 + import { test, expect, beforeEach, afterEach } from "bun:test" 24 + ``` 25 + 26 + ### Test Renderer 27 + 28 + Create a test renderer for headless testing: 29 + 30 + ```typescript 31 + import { createTestRenderer } from "@opentui/core/testing" 32 + 33 + const testSetup = await createTestRenderer({ 34 + width: 80, // Terminal width 35 + height: 24, // Terminal height 36 + }) 37 + ``` 38 + 39 + ## Core Testing 40 + 41 + ### Basic Test 42 + 43 + ```typescript 44 + import { test, expect } from "bun:test" 45 + import { createTestRenderer } from "@opentui/core/testing" 46 + import { TextRenderable } from "@opentui/core" 47 + 48 + test("renders text", async () => { 49 + const testSetup = await createTestRenderer({ 50 + width: 40, 51 + height: 10, 52 + }) 53 + 54 + const text = new TextRenderable(testSetup.renderer, { 55 + id: "greeting", 56 + content: "Hello, World!", 57 + }) 58 + 59 + testSetup.renderer.root.add(text) 60 + await testSetup.renderOnce() 61 + 62 + expect(testSetup.captureCharFrame()).toContain("Hello, World!") 63 + }) 64 + ``` 65 + 66 + ### Snapshot Testing 67 + 68 + ```typescript 69 + import { test, expect, afterEach } from "bun:test" 70 + import { createTestRenderer } from "@opentui/core/testing" 71 + import { BoxRenderable, TextRenderable } from "@opentui/core" 72 + 73 + let testSetup: Awaited<ReturnType<typeof createTestRenderer>> 74 + 75 + afterEach(() => { 76 + if (testSetup) { 77 + testSetup.renderer.destroy() 78 + } 79 + }) 80 + 81 + test("component matches snapshot", async () => { 82 + testSetup = await createTestRenderer({ 83 + width: 40, 84 + height: 10, 85 + }) 86 + 87 + const box = new BoxRenderable(testSetup.renderer, { 88 + id: "box", 89 + border: true, 90 + width: 20, 91 + height: 5, 92 + }) 93 + box.add(new TextRenderable(testSetup.renderer, { 94 + content: "Content", 95 + })) 96 + 97 + testSetup.renderer.root.add(box) 98 + await testSetup.renderOnce() 99 + 100 + expect(testSetup.captureCharFrame()).toMatchSnapshot() 101 + }) 102 + ``` 103 + 104 + ## React Testing 105 + 106 + ### Test Utilities 107 + 108 + React provides a built-in `testRender` utility via the `@opentui/react/test-utils` subpath export: 109 + 110 + ```tsx 111 + import { testRender } from "@opentui/react/test-utils" 112 + ``` 113 + 114 + This utility: 115 + - Creates a headless test renderer 116 + - Sets up the React Act environment automatically 117 + - Handles proper unmounting on destroy 118 + - Returns the standard test setup object 119 + 120 + ### Basic Component Test 121 + 122 + ```tsx 123 + import { test, expect } from "bun:test" 124 + import { testRender } from "@opentui/react/test-utils" 125 + 126 + function Greeting({ name }: { name: string }) { 127 + return <text>Hello, {name}!</text> 128 + } 129 + 130 + test("Greeting renders name", async () => { 131 + const testSetup = await testRender( 132 + <Greeting name="World" />, 133 + { width: 80, height: 24 } 134 + ) 135 + 136 + await testSetup.renderOnce() 137 + const frame = testSetup.captureCharFrame() 138 + 139 + expect(frame).toContain("Hello, World!") 140 + }) 141 + ``` 142 + 143 + ### Snapshot Testing 144 + 145 + ```tsx 146 + import { test, expect, afterEach } from "bun:test" 147 + import { testRender } from "@opentui/react/test-utils" 148 + 149 + let testSetup: Awaited<ReturnType<typeof testRender>> 150 + 151 + afterEach(() => { 152 + if (testSetup) { 153 + testSetup.renderer.destroy() 154 + } 155 + }) 156 + 157 + test("component matches snapshot", async () => { 158 + testSetup = await testRender( 159 + <box style={{ width: 20, height: 5, border: true }}> 160 + <text>Content</text> 161 + </box>, 162 + { width: 25, height: 8 } 163 + ) 164 + 165 + await testSetup.renderOnce() 166 + const frame = testSetup.captureCharFrame() 167 + 168 + expect(frame).toMatchSnapshot() 169 + }) 170 + ``` 171 + 172 + ### State Testing 173 + 174 + ```tsx 175 + import { test, expect, afterEach } from "bun:test" 176 + import { useState } from "react" 177 + import { testRender } from "@opentui/react/test-utils" 178 + 179 + let testSetup: Awaited<ReturnType<typeof testRender>> 180 + 181 + afterEach(() => { 182 + if (testSetup) { 183 + testSetup.renderer.destroy() 184 + } 185 + }) 186 + 187 + function Counter() { 188 + const [count, setCount] = useState(0) 189 + return ( 190 + <box> 191 + <text>Count: {count}</text> 192 + </box> 193 + ) 194 + } 195 + 196 + test("Counter shows initial value", async () => { 197 + testSetup = await testRender( 198 + <Counter />, 199 + { width: 20, height: 5 } 200 + ) 201 + 202 + await testSetup.renderOnce() 203 + const frame = testSetup.captureCharFrame() 204 + 205 + expect(frame).toContain("Count: 0") 206 + }) 207 + ``` 208 + 209 + ### Test Setup/Teardown Pattern 210 + 211 + For multiple tests, use beforeEach/afterEach to manage the renderer lifecycle: 212 + 213 + ```tsx 214 + import { describe, test, expect, beforeEach, afterEach } from "bun:test" 215 + import { testRender } from "@opentui/react/test-utils" 216 + 217 + let testSetup: Awaited<ReturnType<typeof testRender>> 218 + 219 + describe("MyComponent", () => { 220 + beforeEach(async () => { 221 + if (testSetup) { 222 + testSetup.renderer.destroy() 223 + } 224 + }) 225 + 226 + afterEach(() => { 227 + if (testSetup) { 228 + testSetup.renderer.destroy() 229 + } 230 + }) 231 + 232 + test("renders correctly", async () => { 233 + testSetup = await testRender(<MyComponent />, { 234 + width: 40, 235 + height: 10, 236 + }) 237 + 238 + await testSetup.renderOnce() 239 + const frame = testSetup.captureCharFrame() 240 + expect(frame).toMatchSnapshot() 241 + }) 242 + }) 243 + ``` 244 + 245 + ### Test Setup Return Object 246 + 247 + The `testRender` function returns a test setup object with these properties: 248 + 249 + | Property | Type | Description | 250 + |----------|------|-------------| 251 + | `renderer` | `Renderer` | The headless renderer instance | 252 + | `renderOnce` | `() => Promise<void>` | Triggers a single render cycle | 253 + | `captureCharFrame` | `() => string` | Captures current output as text | 254 + | `resize` | `(width, height) => void` | Resize the virtual terminal | 255 + 256 + ## Solid Testing 257 + 258 + ### Test Utilities 259 + 260 + Solid exports `testRender` directly from the main package: 261 + 262 + ```tsx 263 + import { testRender } from "@opentui/solid" 264 + ``` 265 + 266 + Note: Unlike React, Solid's `testRender` takes a **function component** (not a JSX element). 267 + 268 + ### Basic Component Test 269 + 270 + ```tsx 271 + import { test, expect } from "bun:test" 272 + import { testRender } from "@opentui/solid" 273 + 274 + function Greeting(props: { name: string }) { 275 + return <text>Hello, {props.name}!</text> 276 + } 277 + 278 + test("Greeting renders name", async () => { 279 + const testSetup = await testRender( 280 + () => <Greeting name="World" />, 281 + { width: 80, height: 24 } 282 + ) 283 + 284 + await testSetup.renderOnce() 285 + const frame = testSetup.captureCharFrame() 286 + 287 + expect(frame).toContain("Hello, World!") 288 + }) 289 + ``` 290 + 291 + ### Snapshot Testing 292 + 293 + ```tsx 294 + import { test, expect, afterEach } from "bun:test" 295 + import { testRender } from "@opentui/solid" 296 + 297 + let testSetup: Awaited<ReturnType<typeof testRender>> 298 + 299 + afterEach(() => { 300 + if (testSetup) { 301 + testSetup.renderer.destroy() 302 + } 303 + }) 304 + 305 + test("component matches snapshot", async () => { 306 + testSetup = await testRender( 307 + () => ( 308 + <box style={{ width: 20, height: 5, border: true }}> 309 + <text>Content</text> 310 + </box> 311 + ), 312 + { width: 25, height: 8 } 313 + ) 314 + 315 + await testSetup.renderOnce() 316 + const frame = testSetup.captureCharFrame() 317 + 318 + expect(frame).toMatchSnapshot() 319 + }) 320 + ``` 321 + 322 + ## Snapshot Format 323 + 324 + Snapshots capture the rendered terminal output as text: 325 + 326 + ``` 327 + ┌──────────────────┐ 328 + │ Hello, World! │ 329 + │ │ 330 + └──────────────────┘ 331 + ``` 332 + 333 + ### Updating Snapshots 334 + 335 + ```bash 336 + bun test --update-snapshots 337 + ``` 338 + 339 + ## Interaction Testing 340 + 341 + ### Simulating Key Presses 342 + 343 + ```typescript 344 + import { test, expect, afterEach } from "bun:test" 345 + import { createTestRenderer } from "@opentui/core/testing" 346 + 347 + let testSetup: Awaited<ReturnType<typeof createTestRenderer>> 348 + 349 + afterEach(() => { 350 + if (testSetup) { 351 + testSetup.renderer.destroy() 352 + } 353 + }) 354 + 355 + test("responds to keyboard", async () => { 356 + testSetup = await createTestRenderer({ 357 + width: 40, 358 + height: 10, 359 + }) 360 + 361 + // Create component that responds to keys 362 + // ... 363 + 364 + // Simulate keypress 365 + testSetup.renderer.keyInput.emit("keypress", { 366 + name: "enter", 367 + sequence: "\r", 368 + ctrl: false, 369 + shift: false, 370 + meta: false, 371 + option: false, 372 + eventType: "press", 373 + repeated: false, 374 + }) 375 + 376 + // Render after the keypress 377 + await testSetup.renderOnce() 378 + 379 + expect(testSetup.captureCharFrame()).toContain("Selected") 380 + }) 381 + ``` 382 + 383 + ### Testing Focus 384 + 385 + ```typescript 386 + import { test, expect, afterEach } from "bun:test" 387 + import { createTestRenderer } from "@opentui/core/testing" 388 + import { InputRenderable } from "@opentui/core" 389 + 390 + let testSetup: Awaited<ReturnType<typeof createTestRenderer>> 391 + 392 + afterEach(() => { 393 + if (testSetup) { 394 + testSetup.renderer.destroy() 395 + } 396 + }) 397 + 398 + test("input receives focus", async () => { 399 + testSetup = await createTestRenderer({ 400 + width: 40, 401 + height: 10, 402 + }) 403 + 404 + const input = new InputRenderable(testSetup.renderer, { 405 + id: "test-input", 406 + placeholder: "Type here", 407 + }) 408 + testSetup.renderer.root.add(input) 409 + 410 + input.focus() 411 + 412 + expect(input.isFocused()).toBe(true) 413 + }) 414 + ``` 415 + 416 + ## Test Organization 417 + 418 + ### File Structure 419 + 420 + ``` 421 + src/ 422 + ├── components/ 423 + │ ├── Button.tsx 424 + │ └── Button.test.tsx 425 + ├── hooks/ 426 + │ ├── useCounter.ts 427 + │ └── useCounter.test.ts 428 + └── test-utils.tsx 429 + ``` 430 + 431 + ### Running Tests 432 + 433 + ```bash 434 + # Run all tests 435 + bun test 436 + 437 + # Run specific test file 438 + bun test src/components/Button.test.tsx 439 + 440 + # Run with filter 441 + bun test --filter "Button" 442 + 443 + # Watch mode 444 + bun test --watch 445 + ``` 446 + 447 + ## Patterns 448 + 449 + ### Testing Conditional Rendering (React) 450 + 451 + ```tsx 452 + import { test, expect, afterEach } from "bun:test" 453 + import { testRender } from "@opentui/react/test-utils" 454 + 455 + let testSetup: Awaited<ReturnType<typeof testRender>> 456 + 457 + afterEach(() => { 458 + if (testSetup) { 459 + testSetup.renderer.destroy() 460 + } 461 + }) 462 + 463 + test("shows loading state", async () => { 464 + testSetup = await testRender( 465 + <DataLoader loading={true} />, 466 + { width: 40, height: 10 } 467 + ) 468 + 469 + await testSetup.renderOnce() 470 + expect(testSetup.captureCharFrame()).toContain("Loading...") 471 + }) 472 + 473 + test("shows data when loaded", async () => { 474 + testSetup = await testRender( 475 + <DataLoader loading={false} data={["Item 1", "Item 2"]} />, 476 + { width: 40, height: 10 } 477 + ) 478 + 479 + await testSetup.renderOnce() 480 + const frame = testSetup.captureCharFrame() 481 + expect(frame).toContain("Item 1") 482 + expect(frame).toContain("Item 2") 483 + }) 484 + ``` 485 + 486 + ### Testing Lists 487 + 488 + ```tsx 489 + test("renders all items", async () => { 490 + const items = ["Apple", "Banana", "Cherry"] 491 + 492 + testSetup = await testRender( 493 + <ItemList items={items} />, 494 + { width: 40, height: 10 } 495 + ) 496 + 497 + await testSetup.renderOnce() 498 + const frame = testSetup.captureCharFrame() 499 + 500 + items.forEach(item => { 501 + expect(frame).toContain(item) 502 + }) 503 + }) 504 + ``` 505 + 506 + ### Testing Layouts 507 + 508 + ```tsx 509 + test("matches layout snapshot", async () => { 510 + testSetup = await testRender( 511 + <AppLayout />, 512 + { width: 120, height: 40 } // Larger viewport 513 + ) 514 + 515 + await testSetup.renderOnce() 516 + expect(testSetup.captureCharFrame()).toMatchSnapshot() 517 + }) 518 + ``` 519 + 520 + ## Debugging Tests 521 + 522 + ### Print Frame Output 523 + 524 + ```tsx 525 + import { testRender } from "@opentui/react/test-utils" 526 + 527 + test("debug output", async () => { 528 + const testSetup = await testRender( 529 + <MyComponent />, 530 + { width: 40, height: 10 } 531 + ) 532 + 533 + await testSetup.renderOnce() 534 + const frame = testSetup.captureCharFrame() 535 + 536 + // Print to see what's rendered 537 + console.log(frame) 538 + 539 + expect(frame).toContain("expected") 540 + }) 541 + ``` 542 + 543 + ### Verbose Mode 544 + 545 + ```bash 546 + bun test --verbose 547 + ``` 548 + 549 + ## Gotchas 550 + 551 + ### Async Rendering 552 + 553 + Always call `renderOnce()` after setting up your component to ensure rendering is complete: 554 + 555 + ```typescript 556 + const testSetup = await testRender(<MyComponent />, { width: 40, height: 10 }) 557 + await testSetup.renderOnce() // Required before capturing frame 558 + const frame = testSetup.captureCharFrame() 559 + ``` 560 + 561 + ### Test Isolation and Cleanup 562 + 563 + Always destroy the renderer after each test to avoid resource leaks: 564 + 565 + ```typescript 566 + import { afterEach } from "bun:test" 567 + 568 + let testSetup: Awaited<ReturnType<typeof testRender>> 569 + 570 + afterEach(() => { 571 + if (testSetup) { 572 + testSetup.renderer.destroy() 573 + } 574 + }) 575 + 576 + test("test 1", async () => { 577 + testSetup = await testRender(<Component1 />, { width: 40, height: 10 }) 578 + // ... 579 + }) 580 + 581 + test("test 2", async () => { 582 + testSetup = await testRender(<Component2 />, { width: 40, height: 10 }) 583 + // ... 584 + }) 585 + ``` 586 + 587 + ### Snapshot Dimensions 588 + 589 + Be consistent with test dimensions for stable snapshots: 590 + 591 + ```typescript 592 + const testSetup = await createTestRenderer({ 593 + width: 80, // Standard width 594 + height: 24, // Standard height 595 + }) 596 + ``` 597 + 598 + ### Running from Package Directory 599 + 600 + Run tests from the package directory: 601 + 602 + ```bash 603 + cd packages/core 604 + bun test 605 + 606 + # Not from repo root for package-specific tests 607 + ``` 608 + 609 + ## See Also 610 + 611 + - [Core API](../core/api.md) - `createTestRenderer` and renderable classes 612 + - [React Configuration](../react/configuration.md) - React test setup 613 + - [Solid Configuration](../solid/configuration.md) - Solid test setup 614 + - [Keyboard](../keyboard/REFERENCE.md) - Simulating key events in tests
+265
skills/optimize/SKILL.md
··· 1 + --- 2 + name: optimize 3 + description: "Diagnoses and fixes UI performance across loading speed, rendering, animations, images, and bundle size. Use when the user mentions slow, laggy, janky, performance, bundle size, load time, or wants a faster, smoother experience." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Identify and fix performance issues to create faster, smoother user experiences. 9 + 10 + ## Assess Performance Issues 11 + 12 + Understand current performance and identify problems. 13 + 14 + ### 1. Measure current state 15 + - **Core Web Vitals**: LCP, FID/INP, CLS scores 16 + - **Load time**: Time to interactive, first contentful paint 17 + - **Bundle size**: JavaScript, CSS, image sizes 18 + - **Runtime performance**: Frame rate, memory usage, CPU usage 19 + - **Network**: Request count, payload sizes, waterfall 20 + 21 + ### 2. Identify bottlenecks 22 + - What is slow? (Initial load? Interactions? Animations?) 23 + - What is causing it? (Large images? Expensive JavaScript? Layout thrashing?) 24 + - How bad is it? (Perceivable? Annoying? Blocking?) 25 + - Who is affected? (All users? Mobile only? Slow connections?) 26 + 27 + **CRITICAL**: Measure before and after. Premature optimization wastes time. Optimize what actually matters. 28 + 29 + ## Optimization Strategy 30 + 31 + Create systematic improvement plan. 32 + 33 + ### Loading Performance 34 + 35 + **Optimize Images**: 36 + - Use modern formats (WebP, AVIF) 37 + - Proper sizing (do not load 3000px image for 300px display) 38 + - Lazy loading for below-fold images 39 + - Responsive images (srcset, picture element) 40 + - Compress images (80-85% quality is usually imperceptible) 41 + - Use CDN for faster delivery 42 + 43 + ```html 44 + <img 45 + src="hero.webp" 46 + srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w" 47 + sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px" 48 + loading="lazy" 49 + alt="Hero image" 50 + /> 51 + ``` 52 + 53 + **Reduce JavaScript Bundle**: 54 + - Code splitting (route-based, component-based) 55 + - Tree shaking (remove unused code) 56 + - Remove unused dependencies 57 + - Lazy load non-critical code 58 + - Use dynamic imports for large components 59 + 60 + ```javascript 61 + // Lazy load heavy component 62 + const HeavyChart = lazy(() => import('./HeavyChart')); 63 + ``` 64 + 65 + **Optimize CSS**: 66 + - Remove unused CSS 67 + - Critical CSS inline, rest async 68 + - Minimize CSS files 69 + - Use CSS containment for independent regions 70 + 71 + **Optimize Fonts**: 72 + - Use font-display: swap or optional 73 + - Subset fonts (only characters you need) 74 + - Preload critical fonts 75 + - Use system fonts when appropriate 76 + - Limit font weights loaded 77 + 78 + ```css 79 + @font-face { 80 + font-family: 'CustomFont'; 81 + src: url('/fonts/custom.woff2') format('woff2'); 82 + font-display: swap; /* Show fallback immediately */ 83 + unicode-range: U+0020-007F; /* Basic Latin only */ 84 + } 85 + ``` 86 + 87 + **Optimize Loading Strategy**: 88 + - Critical resources first (async or defer non-critical) 89 + - Preload critical assets 90 + - Prefetch likely next pages 91 + - Service worker for offline or caching 92 + - HTTP/2 or HTTP/3 for multiplexing 93 + 94 + ### Rendering Performance 95 + 96 + **Avoid Layout Thrashing**: 97 + ```javascript 98 + // Bad: Alternating reads and writes (causes reflows) 99 + elements.forEach(el => { 100 + const height = el.offsetHeight; // Read (forces layout) 101 + el.style.height = height * 2; // Write 102 + }); 103 + 104 + // Good: Batch reads, then batch writes 105 + const heights = elements.map(el => el.offsetHeight); // All reads 106 + elements.forEach((el, i) => { 107 + el.style.height = heights[i] * 2; // All writes 108 + }); 109 + ``` 110 + 111 + **Optimize Rendering**: 112 + - Use CSS contain property for independent regions 113 + - Minimize DOM depth (flatter is faster) 114 + - Reduce DOM size (fewer elements) 115 + - Use content-visibility: auto for long lists 116 + - Virtual scrolling for very long lists (react-window, react-virtualized) 117 + 118 + **Reduce Paint and Composite**: 119 + - Use transform and opacity for animations (GPU-accelerated) 120 + - Avoid animating layout properties (width, height, top, left) 121 + - Use will-change sparingly for known expensive operations 122 + - Minimize paint areas (smaller is faster) 123 + 124 + ### Animation Performance 125 + 126 + **GPU Acceleration**: 127 + ```css 128 + /* Good: GPU-accelerated (fast) */ 129 + .animated { 130 + transform: translateX(100px); 131 + opacity: 0.5; 132 + } 133 + 134 + /* Bad: CPU-bound (slow) */ 135 + .animated { 136 + left: 100px; 137 + width: 300px; 138 + } 139 + ``` 140 + 141 + **Smooth 60fps**: 142 + - Target 16ms per frame (60fps) 143 + - Use requestAnimationFrame for JS animations 144 + - Debounce or throttle scroll handlers 145 + - Use CSS animations when possible 146 + - Avoid long-running JavaScript during animations 147 + 148 + **Intersection Observer**: 149 + ```javascript 150 + // Efficiently detect when elements enter viewport 151 + const observer = new IntersectionObserver((entries) => { 152 + entries.forEach(entry => { 153 + if (entry.isIntersecting) { 154 + // Element is visible, lazy load or animate 155 + } 156 + }); 157 + }); 158 + ``` 159 + 160 + ### React or Framework Optimization 161 + 162 + **React-specific**: 163 + - Use memo() for expensive components 164 + - useMemo() and useCallback() for expensive computations 165 + - Virtualize long lists 166 + - Code split routes 167 + - Avoid inline function creation in render 168 + - Use React DevTools Profiler 169 + 170 + **Framework-agnostic**: 171 + - Minimize re-renders 172 + - Debounce expensive operations 173 + - Memoize computed values 174 + - Lazy load routes and components 175 + 176 + ### Network Optimization 177 + 178 + **Reduce Requests**: 179 + - Combine small files 180 + - Use SVG sprites for icons 181 + - Inline small critical assets 182 + - Remove unused third-party scripts 183 + 184 + **Optimize APIs**: 185 + - Use pagination (do not load everything) 186 + - GraphQL to request only needed fields 187 + - Response compression (gzip, brotli) 188 + - HTTP caching headers 189 + - CDN for static assets 190 + 191 + **Optimize for Slow Connections**: 192 + - Adaptive loading based on connection (navigator.connection) 193 + - Optimistic UI updates 194 + - Request prioritization 195 + - Progressive enhancement 196 + 197 + ## Core Web Vitals Optimization 198 + 199 + ### Largest Contentful Paint (LCP < 2.5s) 200 + - Optimize hero images 201 + - Inline critical CSS 202 + - Preload key resources 203 + - Use CDN 204 + - Server-side rendering 205 + 206 + ### First Input Delay (FID < 100ms) or INP (< 200ms) 207 + - Break up long tasks 208 + - Defer non-critical JavaScript 209 + - Use web workers for heavy computation 210 + - Reduce JavaScript execution time 211 + 212 + ### Cumulative Layout Shift (CLS < 0.1) 213 + - Set dimensions on images and videos 214 + - Do not inject content above existing content 215 + - Use aspect-ratio CSS property 216 + - Reserve space for ads and embeds 217 + - Avoid animations that cause layout shifts 218 + 219 + ```css 220 + /* Reserve space for image */ 221 + .image-container { 222 + aspect-ratio: 16 / 9; 223 + } 224 + ``` 225 + 226 + ## Performance Monitoring 227 + 228 + **Tools to use**: 229 + - Chrome DevTools (Lighthouse, Performance panel) 230 + - WebPageTest 231 + - Core Web Vitals (Chrome UX Report) 232 + - Bundle analyzers (webpack-bundle-analyzer) 233 + - Performance monitoring (Sentry, DataDog, New Relic) 234 + 235 + **Key metrics**: 236 + - LCP, FID/INP, CLS (Core Web Vitals) 237 + - Time to Interactive (TTI) 238 + - First Contentful Paint (FCP) 239 + - Total Blocking Time (TBT) 240 + - Bundle size 241 + - Request count 242 + 243 + **IMPORTANT**: Measure on real devices with real network conditions. Desktop Chrome with fast connection is not representative. 244 + 245 + **NEVER**: 246 + - Optimize without measuring (premature optimization) 247 + - Sacrifice accessibility for performance 248 + - Break functionality while optimizing 249 + - Use will-change everywhere (creates new layers, uses memory) 250 + - Lazy load above-fold content 251 + - Optimize micro-optimizations while ignoring major issues (optimize the biggest bottleneck first) 252 + - Forget about mobile performance (often slower devices, slower connections) 253 + 254 + ## Verify Improvements 255 + 256 + Test that optimizations worked. 257 + 258 + - **Before and after metrics**: Compare Lighthouse scores 259 + - **Real user monitoring**: Track improvements for real users 260 + - **Different devices**: Test on low-end Android, not just flagship iPhone 261 + - **Slow connections**: Throttle to 3G, test experience 262 + - **No regressions**: Ensure functionality still works 263 + - **User perception**: Does it feel faster? 264 + 265 + Remember: Performance is a feature. Fast experiences feel more responsive, more polished, more professional. Optimize systematically, measure ruthlessly, and prioritize user-perceived performance.
+141
skills/overdrive/SKILL.md
··· 1 + --- 2 + name: overdrive 3 + description: "Pushes interfaces past conventional limits with technically ambitious implementations — shaders, spring physics, scroll-driven reveals, 60fps animations. Use when the user wants to wow, impress, go all-out, or make something that feels extraordinary." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Start your response with: 9 + 10 + ``` 11 + ───────────── ⚡ OVERDRIVE ───────────── 12 + 》》》 Entering overdrive mode... 13 + ``` 14 + 15 + Push an interface past conventional limits. This is not just about visual effects. It is about using the full power of the browser to make any part of an interface feel extraordinary: a table that handles a million rows, a dialog that morphs from its trigger, a form that validates in real-time with streaming feedback, a page transition that feels cinematic. 16 + 17 + ## MANDATORY PREPARATION 18 + 19 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 20 + 21 + **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That is extraordinary too. Understand the project personality and goals before deciding what is appropriate. 22 + 23 + ### Propose Before Building 24 + 25 + This skill has the highest potential to misfire. Do NOT jump straight into implementation. You MUST: 26 + 27 + 1. **Think through 2-3 different directions**. Consider different techniques, levels of ambition, and aesthetic approaches. For each direction, briefly describe what the result would look and feel like. 28 + 2. **{{ask_instruction}}** to present these directions and get the user pick before writing any code. Explain trade-offs (browser support, performance cost, complexity). 29 + 3. Only proceed with the direction the user confirms. 30 + 31 + Skipping this step risks building something embarrassing that needs to be thrown away. 32 + 33 + ### Iterate with Browser Automation 34 + 35 + Technically ambitious effects almost never work on the first try. You MUST actively use browser automation tools to preview your work, visually verify the result, and iterate. Do not assume the effect looks right. Check it. Expect multiple rounds of refinement. The gap between "technically works" and "looks extraordinary" is closed through visual iteration, not code alone. 36 + 37 + --- 38 + 39 + ## Assess What "Extraordinary" Means Here 40 + 41 + The right kind of technical ambition depends entirely on what you are working with. Before choosing a technique, ask: **what would make a user of THIS specific interface say "wow, that is nice"?** 42 + 43 + ### For visual or marketing surfaces 44 + Pages, hero sections, landing pages, portfolios. The "wow" is often sensory: a scroll-driven reveal, a shader background, a cinematic page transition, generative art that responds to the cursor. 45 + 46 + ### For functional UI 47 + Tables, forms, dialogs, navigation. The "wow" is in how it FEELS: a dialog that morphs from the button that triggered it via View Transitions, a data table that renders 100k rows at 60fps via virtual scrolling, a form with streaming validation that feels instant, drag-and-drop with spring physics. 48 + 49 + ### For performance-critical UI 50 + The "wow" is invisible but felt: a search that filters 50k items without a flicker, a complex form that never blocks the main thread, an image editor that processes in near-real-time. The interface just never hesitates. 51 + 52 + ### For data-heavy interfaces 53 + Charts and dashboards. The "wow" is in fluidity: GPU-accelerated rendering via Canvas or WebGL for massive datasets, animated transitions between data states, force-directed graph layouts that settle naturally. 54 + 55 + **The common thread**: something about the implementation goes beyond what users expect from a web interface. The technique serves the experience, not the other way around. 56 + 57 + ## The Toolkit 58 + 59 + Organized by what you are trying to achieve, not by technology name. 60 + 61 + ### Make transitions feel cinematic 62 + - **View Transitions API** (same-document: all browsers; cross-document: no Firefox). Shared element morphing between states. A list item expanding into a detail page. A button morphing into a dialog. This is the closest thing to native FLIP animations. 63 + - **@starting-style** (all browsers). Animate elements from display: none to visible with CSS only, including entry keyframes. 64 + - **Spring physics**. Natural motion with mass, tension, and damping instead of cubic-bezier. Libraries: motion (formerly Framer Motion), GSAP, or roll your own spring solver. 65 + 66 + ### Tie animation to scroll position 67 + - **Scroll-driven animations** (animation-timeline: scroll()). CSS-only, no JS. Parallax, progress bars, reveal sequences all driven by scroll position. (Chrome, Edge, Safari; Firefox: flag only. Always provide a static fallback) 68 + 69 + ### Render beyond CSS 70 + - **WebGL** (all browsers). Shader effects, post-processing, particle systems. Libraries: Three.js, OGL (lightweight), regl. Use for effects CSS cannot express. 71 + - **WebGPU** (Chrome, Edge; Safari partial; Firefox: flag only). Next-gen GPU compute. More powerful than WebGL but limited browser support. Always fall back to WebGL2. 72 + - **Canvas 2D or OffscreenCanvas**. Custom rendering, pixel manipulation, or moving heavy rendering off the main thread entirely via Web Workers and OffscreenCanvas. 73 + - **SVG filter chains**. Displacement maps, turbulence, morphology for organic distortion effects. CSS-animatable. 74 + 75 + ### Make data feel alive 76 + - **Virtual scrolling**. Render only visible rows for tables or lists with tens of thousands of items. No library required for simple cases. TanStack Virtual for complex ones. 77 + - **GPU-accelerated charts**. Canvas or WebGL-rendered data visualization for datasets too large for SVG or DOM. Libraries: deck.gl, regl-based custom renderers. 78 + - **Animated data transitions**. Morph between chart states rather than replacing. D3 transition() or View Transitions for DOM-based charts. 79 + 80 + ### Animate complex properties 81 + - **@property** (all browsers). Register custom CSS properties with types, enabling animation of gradients, colors, and complex values that CSS cannot normally interpolate. 82 + - **Web Animations API** (all browsers). JavaScript-driven animations with the performance of CSS. Composable, cancellable, reversible. The foundation for complex choreography. 83 + 84 + ### Push performance boundaries 85 + - **Web Workers**. Move computation off the main thread. Heavy data processing, image manipulation, search indexing. Anything that would cause jank. 86 + - **OffscreenCanvas**. Render in a Worker thread. The main thread stays free while complex visuals render in the background. 87 + - **WASM**. Near-native performance for computation-heavy features. Image processing, physics simulations, codecs. 88 + 89 + ### Interact with the device 90 + - **Web Audio API**. Spatial audio, audio-reactive visualizations, sonic feedback. Requires user gesture to start. 91 + - **Device APIs**. Orientation, ambient light, geolocation. Use sparingly and always with user permission. 92 + 93 + **NOTE**: This skill is about enhancing how an interface FEELS, not changing what a product DOES. Adding real-time collaboration, offline support, or new backend capabilities are product decisions, not UI enhancements. Focus on making existing features feel extraordinary. 94 + 95 + ## Implement with Discipline 96 + 97 + ### Progressive enhancement is non-negotiable 98 + 99 + Every technique must degrade gracefully. The experience without the enhancement must still be good. 100 + 101 + ```css 102 + @supports (animation-timeline: scroll()) { 103 + .hero { animation-timeline: scroll(); } 104 + } 105 + ``` 106 + 107 + ```javascript 108 + if ('gpu' in navigator) { /* WebGPU */ } 109 + else if (canvas.getContext('webgl2')) { /* WebGL2 fallback */ } 110 + /* CSS-only fallback must still look good */ 111 + ``` 112 + 113 + ### Performance rules 114 + 115 + - Target 60fps. If dropping below 50, simplify. 116 + - Respect prefers-reduced-motion. Always. Provide a beautiful static alternative. 117 + - Lazy-initialize heavy resources (WebGL contexts, WASM modules) only when near viewport. 118 + - Pause off-screen rendering. Kill what you cannot see. 119 + - Test on real mid-range devices, not just your development machine. 120 + 121 + ### Polish is the difference 122 + 123 + The gap between "cool" and "extraordinary" is in the last 20% of refinement: the easing curve on a spring animation, the timing offset in a staggered reveal, the subtle secondary motion that makes a transition feel physical. Do not ship the first version that works. Ship the version that feels inevitable. 124 + 125 + **NEVER**: 126 + - Ignore prefers-reduced-motion. This is an accessibility requirement, not a suggestion 127 + - Ship effects that cause jank on mid-range devices 128 + - Use bleeding-edge APIs without a functional fallback 129 + - Add sound without explicit user opt-in 130 + - Use technical ambition to mask weak design fundamentals. Fix those first with other skills. 131 + - Layer multiple competing extraordinary moments. Focus creates impact, excess creates noise. 132 + 133 + ## Verify the Result 134 + 135 + - **The wow test**: Show it to someone who has not seen it. Do they react? 136 + - **The removal test**: Take it away. Does the experience feel diminished, or does nobody notice? 137 + - **The device test**: Run it on a phone, a tablet, a Chromebook. Still smooth? 138 + - **The accessibility test**: Enable reduced motion. Still beautiful? 139 + - **The context test**: Does this make sense for THIS brand and audience? 140 + 141 + Remember: "Technically extraordinary" is not about using the newest API. It is about making an interface do something users did not think a website could do.
+191
skills/polish/SKILL.md
··· 1 + --- 2 + name: polish 3 + description: "Performs a final quality pass fixing alignment, spacing, consistency, and micro-detail issues before shipping. Use when the user mentions polish, finishing touches, pre-launch review, something looks off, or wants to go from good to great." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + ## MANDATORY PREPARATION 9 + 10 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). 11 + 12 + --- 13 + 14 + Perform a meticulous final pass to catch all the small details that separate good work from great work. The difference between shipped and polished. 15 + 16 + ## Pre-Polish Assessment 17 + 18 + Understand the current state and goals. 19 + 20 + ### 1. Review completeness 21 + - Is it functionally complete? 22 + - Are there known issues to preserve (mark with TODOs)? 23 + - What is the quality bar? (MVP vs flagship feature?) 24 + - When does it ship? (How much time for polish?) 25 + 26 + ### 2. Identify polish areas 27 + - Visual inconsistencies 28 + - Spacing and alignment issues 29 + - Interaction state gaps 30 + - Copy inconsistencies 31 + - Edge cases and error states 32 + - Loading and transition smoothness 33 + 34 + **CRITICAL**: Polish is the last step, not the first. Do not polish work that is not functionally complete. 35 + 36 + ## Polish Systematically 37 + 38 + Work through these dimensions methodically. 39 + 40 + ### Visual Alignment and Spacing 41 + - **Pixel-perfect alignment**: Everything lines up to grid 42 + - **Consistent spacing**: All gaps use spacing scale (no random 13px gaps) 43 + - **Optical alignment**: Adjust for visual weight (icons may need offset for optical centering) 44 + - **Responsive consistency**: Spacing and alignment work at all breakpoints 45 + - **Grid adherence**: Elements snap to baseline grid 46 + 47 + **Check**: 48 + - Enable grid overlay and verify alignment 49 + - Check spacing with browser inspector 50 + - Test at multiple viewport sizes 51 + - Look for elements that "feel" off 52 + 53 + ### Typography Refinement 54 + - **Hierarchy consistency**: Same elements use same sizes and weights throughout 55 + - **Line length**: 45-75 characters for body text 56 + - **Line height**: Appropriate for font size and context 57 + - **Widows and orphans**: No single words on last line 58 + - **Hyphenation**: Appropriate for language and column width 59 + - **Kerning**: Adjust letter spacing where needed (especially headlines) 60 + - **Font loading**: No FOUT or FOIT flashes 61 + 62 + ### Color and Contrast 63 + - **Contrast ratios**: All text meets WCAG standards 64 + - **Consistent token usage**: No hard-coded colors, all use design tokens 65 + - **Theme consistency**: Works in all theme variants 66 + - **Color meaning**: Same colors mean same things throughout 67 + - **Accessible focus**: Focus indicators visible with sufficient contrast 68 + - **Tinted neutrals**: No pure gray or pure black. Add subtle color tint (0.01 chroma). 69 + - **Gray on color**: Never put gray text on colored backgrounds. Use a shade of that color or transparency. 70 + 71 + ### Interaction States 72 + 73 + Every interactive element needs all states. 74 + 75 + - **Default**: Resting state 76 + - **Hover**: Subtle feedback (color, scale, shadow) 77 + - **Focus**: Keyboard focus indicator (never remove without replacement) 78 + - **Active**: Click or tap feedback 79 + - **Disabled**: Clearly non-interactive 80 + - **Loading**: Async action feedback 81 + - **Error**: Validation or error state 82 + - **Success**: Successful completion 83 + 84 + **Missing states create confusion and broken experiences**. 85 + 86 + ### Micro-interactions and Transitions 87 + - **Smooth transitions**: All state changes animated appropriately (150-300ms) 88 + - **Consistent easing**: Use ease-out-quart, quint, or expo for natural deceleration. Never bounce or elastic. They feel dated. 89 + - **No jank**: 60fps animations, only animate transform and opacity 90 + - **Appropriate motion**: Motion serves purpose, not decoration 91 + - **Reduced motion**: Respects prefers-reduced-motion 92 + 93 + ### Content and Copy 94 + - **Consistent terminology**: Same things called same names throughout 95 + - **Consistent capitalization**: Title Case vs Sentence case applied consistently 96 + - **Grammar and spelling**: No typos 97 + - **Appropriate length**: Not too wordy, not too terse 98 + - **Punctuation consistency**: Periods on sentences, not on labels (unless all labels have them) 99 + 100 + ### Icons and Images 101 + - **Consistent style**: All icons from same family or matching style 102 + - **Appropriate sizing**: Icons sized consistently for context 103 + - **Proper alignment**: Icons align with adjacent text optically 104 + - **Alt text**: All images have descriptive alt text 105 + - **Loading states**: Images do not cause layout shift, proper aspect ratios 106 + - **Retina support**: 2x assets for high-DPI screens 107 + 108 + ### Forms and Inputs 109 + - **Label consistency**: All inputs properly labeled 110 + - **Required indicators**: Clear and consistent 111 + - **Error messages**: Helpful and consistent 112 + - **Tab order**: Logical keyboard navigation 113 + - **Auto-focus**: Appropriate (do not overuse) 114 + - **Validation timing**: Consistent (on blur vs on submit) 115 + 116 + ### Edge Cases and Error States 117 + - **Loading states**: All async actions have loading feedback 118 + - **Empty states**: Helpful empty states, not just blank space 119 + - **Error states**: Clear error messages with recovery paths 120 + - **Success states**: Confirmation of successful actions 121 + - **Long content**: Handles very long names, descriptions, and so on 122 + - **No content**: Handles missing data gracefully 123 + - **Offline**: Appropriate offline handling (if applicable) 124 + 125 + ### Responsiveness 126 + - **All breakpoints**: Test mobile, tablet, desktop 127 + - **Touch targets**: 44x44px minimum on touch devices 128 + - **Readable text**: No text smaller than 14px on mobile 129 + - **No horizontal scroll**: Content fits viewport 130 + - **Appropriate reflow**: Content adapts logically 131 + 132 + ### Performance 133 + - **Fast initial load**: Optimize critical path 134 + - **No layout shift**: Elements do not jump after load (CLS) 135 + - **Smooth interactions**: No lag or jank 136 + - **Optimized images**: Appropriate formats and sizes 137 + - **Lazy loading**: Off-screen content loads lazily 138 + 139 + ### Code Quality 140 + - **Remove console logs**: No debug logging in production 141 + - **Remove commented code**: Clean up dead code 142 + - **Remove unused imports**: Clean up unused dependencies 143 + - **Consistent naming**: Variables and functions follow conventions 144 + - **Type safety**: No TypeScript any or ignored errors 145 + - **Accessibility**: Proper ARIA labels and semantic HTML 146 + 147 + ## Polish Checklist 148 + 149 + Go through systematically. 150 + 151 + - [ ] Visual alignment perfect at all breakpoints 152 + - [ ] Spacing uses design tokens consistently 153 + - [ ] Typography hierarchy consistent 154 + - [ ] All interactive states implemented 155 + - [ ] All transitions smooth (60fps) 156 + - [ ] Copy is consistent and polished 157 + - [ ] Icons are consistent and properly sized 158 + - [ ] All forms properly labeled and validated 159 + - [ ] Error states are helpful 160 + - [ ] Loading states are clear 161 + - [ ] Empty states are welcoming 162 + - [ ] Touch targets are 44x44px minimum 163 + - [ ] Contrast ratios meet WCAG AA 164 + - [ ] Keyboard navigation works 165 + - [ ] Focus indicators visible 166 + - [ ] No console errors or warnings 167 + - [ ] No layout shift on load 168 + - [ ] Works in all supported browsers 169 + - [ ] Respects reduced motion preference 170 + - [ ] Code is clean (no TODOs, console.logs, commented code) 171 + 172 + **IMPORTANT**: Polish is about details. Zoom in. Squint at it. Use it yourself. The little things add up. 173 + 174 + **NEVER**: 175 + - Polish before it is functionally complete 176 + - Spend hours on polish if it ships in 30 minutes (triage) 177 + - Introduce bugs while polishing (test thoroughly) 178 + - Ignore systematic issues (if spacing is off everywhere, fix the system) 179 + - Perfect one thing while leaving others rough (consistent quality level) 180 + 181 + ## Final Verification 182 + 183 + Before marking as done. 184 + 185 + - **Use it yourself**: Actually interact with the feature 186 + - **Test on real devices**: Not just browser DevTools 187 + - **Ask someone else to review**: Fresh eyes catch things 188 + - **Compare to design**: Match intended design 189 + - **Check all states**: Do not just test happy path 190 + 191 + Remember: You have impeccable attention to detail and exquisite taste. Polish until it feels effortless, looks intentional, and works flawlessly. Sweat the details. They matter.
+102
skills/quieter/SKILL.md
··· 1 + --- 2 + name: quieter 3 + description: "Tones down visually aggressive or overstimulating designs, reducing intensity while preserving quality. Use when the user mentions too bold, too loud, overwhelming, aggressive, garish, or wants a calmer, more refined aesthetic." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Reduce visual intensity in designs that are too bold, aggressive, or overstimulating, creating a more refined and approachable aesthetic without losing effectiveness. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 13 + 14 + --- 15 + 16 + ## Assess Current State 17 + 18 + Analyze what makes the design feel too intense. 19 + 20 + ### 1. Identify intensity sources 21 + - **Color saturation**: Overly bright or saturated colors 22 + - **Contrast extremes**: Too much high-contrast juxtaposition 23 + - **Visual weight**: Too many bold, heavy elements competing 24 + - **Animation excess**: Too much motion or overly dramatic effects 25 + - **Complexity**: Too many visual elements, patterns, or decorations 26 + - **Scale**: Everything is large and loud with no hierarchy 27 + 28 + ### 2. Understand the context 29 + - What is the purpose? (Marketing vs tool vs reading experience) 30 + - Who is the audience? (Some contexts need energy) 31 + - What is working? (Do not throw away good ideas) 32 + - What is the core message? (Preserve what matters) 33 + 34 + If any of these are unclear from the codebase, {{ask_instruction}} 35 + 36 + **CRITICAL**: "Quieter" does not mean boring or generic. It means refined, sophisticated, and easier on the eyes. Think luxury, not laziness. 37 + 38 + ## Plan Refinement 39 + 40 + Create a strategy to reduce intensity while maintaining impact. 41 + 42 + - **Color approach**: Desaturate or shift to more sophisticated tones? 43 + - **Hierarchy approach**: Which elements should stay bold (very few), which should recede? 44 + - **Simplification approach**: What can be removed entirely? 45 + - **Sophistication approach**: How can we signal quality through restraint? 46 + 47 + **IMPORTANT**: Great quiet design is harder than great bold design. Subtlety requires precision. 48 + 49 + ## Refine the Design 50 + 51 + Systematically reduce intensity across these dimensions. 52 + 53 + ### Color Refinement 54 + - **Reduce saturation**: Shift from fully saturated to 70-85% saturation 55 + - **Soften palette**: Replace bright colors with muted, sophisticated tones 56 + - **Reduce color variety**: Use fewer colors more thoughtfully 57 + - **Neutral dominance**: Let neutrals do more work, use color as accent (10% rule) 58 + - **Gentler contrasts**: High contrast only where it matters most 59 + - **Tinted grays**: Use warm or cool tinted grays instead of pure gray. Adds sophistication without loudness. 60 + - **Never gray on color**: If you have gray text on a colored background, use a darker shade of that color or transparency instead. 61 + 62 + ### Visual Weight Reduction 63 + - **Typography**: Reduce font weights (900 to 600, 700 to 500), decrease sizes where appropriate 64 + - **Hierarchy through subtlety**: Use weight, size, and space instead of color and boldness 65 + - **White space**: Increase breathing room, reduce density 66 + - **Borders and lines**: Reduce thickness, decrease opacity, or remove entirely 67 + 68 + ### Simplification 69 + - **Remove decorative elements**: Gradients, shadows, patterns, textures that do not serve purpose 70 + - **Simplify shapes**: Reduce border radius extremes, simplify custom shapes 71 + - **Reduce layering**: Flatten visual hierarchy where possible 72 + - **Clean up effects**: Reduce or remove blur effects, glows, multiple shadows 73 + 74 + ### Motion Reduction 75 + - **Reduce animation intensity**: Shorter distances (10-20px instead of 40px), gentler easing 76 + - **Remove decorative animations**: Keep functional motion, remove flourishes 77 + - **Subtle micro-interactions**: Replace dramatic effects with gentle feedback 78 + - **Refined easing**: Use ease-out-quart for smooth, understated motion. Never bounce or elastic. 79 + - **Remove animations entirely** if they are not serving a clear purpose. 80 + 81 + ### Composition Refinement 82 + - **Reduce scale jumps**: Smaller contrast between sizes creates calmer feeling 83 + - **Align to grid**: Bring rogue elements back into systematic alignment 84 + - **Even out spacing**: Replace extreme spacing variations with consistent rhythm 85 + 86 + **NEVER**: 87 + - Make everything the same size or weight (hierarchy still matters) 88 + - Remove all color (quiet equals grayscale) 89 + - Eliminate all personality (maintain character through refinement) 90 + - Sacrifice usability for aesthetics (functional elements still need clear affordances) 91 + - Make everything small and light (some anchors needed) 92 + 93 + ## Verify Quality 94 + 95 + Ensure refinement maintains quality. 96 + 97 + - **Still functional**: Can users still accomplish tasks easily? 98 + - **Still distinctive**: Does it have character, or is it generic now? 99 + - **Better reading**: Is text easier to read for extended periods? 100 + - **Sophistication**: Does it feel more refined and premium? 101 + 102 + Remember: Quiet design is confident design. It does not need to shout. Less is more, but less is also harder. Refine with precision and maintain intentionality.
+71
skills/teach-impeccable/SKILL.md
··· 1 + --- 2 + name: teach-impeccable 3 + description: One-time setup that gathers design context for your project and saves it to your AI config file. Run once to establish persistent design guidelines. 4 + user-invocable: true 5 + --- 6 + 7 + Gather design context for this project, then persist it for all future sessions. 8 + 9 + ## Step 1: Explore the Codebase 10 + 11 + Before asking questions, thoroughly scan the project to discover what you can. 12 + 13 + - **README and docs**: Project purpose, target audience, any stated goals 14 + - **Package.json or config files**: Tech stack, dependencies, existing design libraries 15 + - **Existing components**: Current design patterns, spacing, typography in use 16 + - **Brand assets**: Logos, favicons, color values already defined 17 + - **Design tokens or CSS variables**: Existing color palettes, font stacks, spacing scales 18 + - **Any style guides or brand documentation** 19 + 20 + Note what you have learned and what remains unclear. 21 + 22 + ## Step 2: Ask UX-Focused Questions 23 + 24 + {{ask_instruction}} Focus only on what you could not infer from the codebase. 25 + 26 + ### Users and Purpose 27 + - Who uses this? What is their context when using it? 28 + - What job are they trying to get done? 29 + - What emotions should the interface evoke? (confidence, delight, calm, urgency, etc.) 30 + 31 + ### Brand and Personality 32 + - How would you describe the brand personality in 3 words? 33 + - Any reference sites or apps that capture the right feel? What specifically about them? 34 + - What should this explicitly NOT look like? Any anti-references? 35 + 36 + ### Aesthetic Preferences 37 + - Any strong preferences for visual direction? (minimal, bold, elegant, playful, technical, organic, etc.) 38 + - Light mode, dark mode, or both? 39 + - Any colors that must be used or avoided? 40 + 41 + ### Accessibility and Inclusion 42 + - Specific accessibility requirements? (WCAG level, known user needs) 43 + - Considerations for reduced motion, color blindness, or other accommodations? 44 + 45 + Skip questions where the answer is already clear from the codebase exploration. 46 + 47 + ## Step 3: Write Design Context 48 + 49 + Synthesize your findings and the user answers into a Design Context section. 50 + 51 + ```markdown 52 + ## Design Context 53 + 54 + ### Users 55 + [Who they are, their context, the job to be done] 56 + 57 + ### Brand Personality 58 + [Voice, tone, 3-word personality, emotional goals] 59 + 60 + ### Aesthetic Direction 61 + [Visual tone, references, anti-references, theme] 62 + 63 + ### Design Principles 64 + [3-5 principles derived from the conversation that should guide all design decisions] 65 + ``` 66 + 67 + Write this section to `.impeccable.md` in the project root. If the file already exists, update the Design Context section in place. 68 + 69 + Then {{ask_instruction}} whether they would also like the Design Context appended to {{config_file}}. If yes, append or update the section there as well. 70 + 71 + Confirm completion and summarize the key design principles that will now guide all future work.
+111
skills/typeset/SKILL.md
··· 1 + --- 2 + name: typeset 3 + description: "Improves typography by fixing font choices, hierarchy, sizing, weight, and readability so text feels intentional. Use when the user mentions fonts, type, readability, text hierarchy, sizing looks off, or wants more polished, intentional typography." 4 + argument-hint: "[target]" 5 + user-invocable: true 6 + --- 7 + 8 + Assess and improve typography that feels generic, inconsistent, or poorly structured. Turn default-looking text into intentional, well-crafted type. 9 + 10 + ## MANDATORY PREPARATION 11 + 12 + Invoke {{command_prefix}}frontend-design. It contains design principles, anti-patterns, and the Context Gathering Protocol. Follow the protocol before proceeding. If no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. 13 + 14 + --- 15 + 16 + ## Assess Current Typography 17 + 18 + Analyze what's weak or generic about the current type. 19 + 20 + ### 1. Font choices 21 + - Using invisible defaults? (Inter, Roboto, Arial, Open Sans, system defaults) 22 + - Does the font match the brand personality? (A playful brand should not use a corporate typeface) 23 + - Too many font families? (More than 2-3 is almost always a mess) 24 + 25 + ### 2. Hierarchy 26 + - Can you tell headings from body from captions at a glance? 27 + - Are font sizes too close together? (14px, 15px, 16px = muddy hierarchy) 28 + - Are weight contrasts strong enough? (Medium vs Regular is barely visible) 29 + 30 + ### 3. Sizing and scale 31 + - Is there a consistent type scale, or are sizes arbitrary? 32 + - Does body text meet minimum readability? (16px+) 33 + - Is the sizing strategy appropriate for the context? (Fixed rem scales for app UIs; fluid clamp() for marketing page headings) 34 + 35 + ### 4. Readability 36 + - Are line lengths comfortable? (45-75 characters ideal) 37 + - Is line-height appropriate for the font and context? 38 + - Is there enough contrast between text and background? 39 + 40 + ### 5. Consistency 41 + - Are the same elements styled the same way throughout? 42 + - Are font weights used consistently? (Not bold in one section, semibold in another for the same role) 43 + - Is letter-spacing intentional or default everywhere? 44 + 45 + **CRITICAL**: The goal is not to make text "fancier". It is to make it clearer, more readable, and more intentional. Good typography is invisible. Bad typography is distracting. 46 + 47 + ## Plan Typography Improvements 48 + 49 + Consult the [typography reference](reference/typography.md) from the frontend-design skill for detailed guidance on scales, pairing, and loading strategies. 50 + 51 + Create a systematic plan. 52 + 53 + - **Font selection**: Do fonts need replacing? What fits the brand and context? 54 + - **Type scale**: Establish a modular scale (1.25 ratio) with clear hierarchy 55 + - **Weight strategy**: Which weights serve which roles? (Regular for body, Semibold for labels, Bold for headings) 56 + - **Spacing**: Line-heights, letter-spacing, and margins between typographic elements 57 + 58 + ## Improve Typography Systematically 59 + 60 + ### Font Selection 61 + 62 + If fonts need replacing: 63 + - Choose fonts that reflect the brand personality 64 + - Pair with genuine contrast (serif + sans, geometric + humanist) or use a single family in multiple weights 65 + - Ensure web font loading does not cause layout shift (font-display: swap, metric-matched fallbacks) 66 + 67 + ### Establish Hierarchy 68 + 69 + Build a clear type scale. 70 + - **5 sizes cover most needs**: caption, secondary, body, subheading, heading 71 + - **Use a consistent ratio** between levels (1.25, 1.333, or 1.5) 72 + - **Combine dimensions**: Size + weight + color + space for strong hierarchy. Do not rely on size alone. 73 + - **App UIs**: Use a fixed rem-based type scale, optionally adjusted at 1-2 breakpoints. Fluid sizing undermines the spatial predictability that dense, container-based layouts need. 74 + - **Marketing and content pages**: Use fluid sizing via clamp(min, preferred, max) for headings and display text. Keep body text fixed. 75 + 76 + ### Fix Readability 77 + - Set max-width on text containers using ch units (max-width: 65ch) 78 + - Adjust line-height per context: tighter for headings (1.1-1.2), looser for body (1.5-1.7) 79 + - Increase line-height slightly for light-on-dark text 80 + - Ensure body text is at least 16px / 1rem 81 + 82 + ### Refine Details 83 + - Use tabular-nums for data tables and numbers that should align 84 + - Apply proper letter-spacing: slightly open for small caps and uppercase, default or tight for large display text 85 + - Use semantic token names (--text-body, --text-heading), not value names (--font-16) 86 + - Set font-kerning: normal and consider OpenType features where appropriate 87 + 88 + ### Weight Consistency 89 + - Define clear roles for each weight and stick to them 90 + - Do not use more than 3-4 weights (Regular, Medium, Semibold, Bold is plenty) 91 + - Load only the weights you actually use (each weight adds to page load) 92 + 93 + **NEVER**: 94 + - Use more than 2-3 font families 95 + - Pick sizes arbitrarily. Commit to a scale. 96 + - Set body text below 16px 97 + - Use decorative or display fonts for body text 98 + - Disable browser zoom (user-scalable=no) 99 + - Use px for font sizes. Use rem to respect user settings. 100 + - Default to Inter/Roboto/Open Sans when personality matters 101 + - Pair fonts that are similar but not identical (two geometric sans-serifs) 102 + 103 + ## Verify Typography Improvements 104 + - **Hierarchy**: Can you identify heading vs body vs caption instantly? 105 + - **Readability**: Is body text comfortable to read in long passages? 106 + - **Consistency**: Are same-role elements styled identically throughout? 107 + - **Personality**: Does the typography reflect the brand? 108 + - **Performance**: Are web fonts loading efficiently without layout shift? 109 + - **Accessibility**: Does text meet WCAG contrast ratios? Is it zoomable to 200%? 110 + 111 + Remember: Typography is the foundation of interface design. It carries the majority of information. Getting it right is the highest-leverage improvement you can make.
+268
skills/ulw/SKILL.md
··· 1 + --- 2 + name: ulw 3 + description: Load this skill when the user mentions `ulw` or `ultrawork`. 4 + --- 5 + **MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable. 6 + 7 + [CODE RED] Maximum precision required. Think deeply before acting. 8 + 9 + ## **ABSOLUTE CERTAINTY REQUIRED DO NOT SKIP THIS** 10 + 11 + **YOU MUST NOT START ANY IMPLEMENTATION UNTIL YOU ARE 100% CERTAIN.** 12 + 13 + | **BEFORE YOU WRITE A SINGLE LINE OF CODE, YOU MUST:** | 14 + |-------------------------------------------------------| 15 + | **FULLY UNDERSTAND** what the user ACTUALLY wants (not what you ASSUME they want) | 16 + | **EXPLORE** the codebase to understand existing patterns, architecture, and context | 17 + | **HAVE A CRYSTAL CLEAR WORK PLAN** if your plan is vague, YOUR WORK WILL FAIL | 18 + | **RESOLVE ALL AMBIGUITY** if ANYTHING is unclear, ASK or INVESTIGATE | 19 + 20 + ### **MANDATORY CERTAINTY PROTOCOL** 21 + 22 + **IF YOU ARE NOT 100% CERTAIN:** 23 + 24 + 1. **THINK DEEPLY** What is the user's TRUE intent? What problem are they REALLY trying to solve? 25 + 2. **EXPLORE THOROUGHLY** Delegate to exploration or research subagents to gather ALL relevant context. 26 + 3. **CONSULT SPECIALISTS** For hard/complex tasks, DO NOT struggle alone. Delegate: 27 + * **Architecture/Logic Specialists**: Conventional problems like architecture, debugging, complex logic. 28 + * **Creative Specialists**: Non-conventional problems where a different approach is needed or unusual constraints exist. 29 + 4. **ASK THE USER** If ambiguity remains after exploration, ASK. Don't guess. 30 + 31 + **SIGNS YOU ARE NOT READY TO IMPLEMENT:** 32 + * You're making assumptions about requirements 33 + * You're unsure which files to modify 34 + * You don't understand how existing code works 35 + * Your plan has "probably" or "maybe" in it 36 + * You can't explain the exact steps you'll take 37 + 38 + **WHEN IN DOUBT:** 39 + Spawn a background exploration subagent designed for codebase search. Prompt it to find specific patterns in the codebase, showing file paths, implementation approaches, and conventions used. Focus on source directories and skip test files unless test patterns are needed. 40 + 41 + Spawn a background research subagent designed for documentation lookup. Prompt it to find official documentation and production-quality examples for the specific library or technology. Request API references, configuration options, recommended patterns, and common pitfalls. Skip beginner tutorials. 42 + 43 + Spawn a blocking specialist subagent designed for architectural review. Prompt it with your architectural plan, describing specific files and changes. Detail your concerns and uncertainties. Ask it to evaluate the correctness of the approach, missing potential issues, and better alternatives. 44 + 45 + **ONLY AFTER YOU HAVE:** 46 + * Gathered sufficient context via subagents 47 + * Resolved all ambiguities 48 + * Created a precise, step-by-step work plan 49 + * Achieved 100% confidence in your understanding 50 + 51 + **...THEN AND ONLY THEN MAY YOU BEGIN IMPLEMENTATION.** 52 + 53 + *** 54 + 55 + ## **NO EXCUSES. NO COMPROMISES. DELIVER WHAT WAS ASKED.** 56 + 57 + **THE USER'S ORIGINAL REQUEST IS SACRED. YOU MUST FULFILL IT EXACTLY.** 58 + 59 + | VIOLATION | CONSEQUENCE | 60 + |-----------|-------------| 61 + | "I couldn't because..." | **UNACCEPTABLE.** Find a way or ask for help. | 62 + | "This is a simplified version..." | **UNACCEPTABLE.** Deliver the FULL implementation. | 63 + | "You can extend this later..." | **UNACCEPTABLE.** Finish it NOW. | 64 + | "Due to limitations..." | **UNACCEPTABLE.** Use subagents, tools, whatever it takes. | 65 + | "I made some assumptions..." | **UNACCEPTABLE.** You should have asked FIRST. | 66 + 67 + **THERE ARE NO VALID EXCUSES FOR:** 68 + * Delivering partial work 69 + * Changing scope without explicit user approval 70 + * Making unauthorized simplifications 71 + * Stopping before the task is 100% complete 72 + * Compromising on any stated requirement 73 + 74 + **IF YOU ENCOUNTER A BLOCKER:** 75 + 1. **DO NOT** give up 76 + 2. **DO NOT** deliver a compromised version 77 + 3. **DO** consult specialized subagents (logic/architecture for conventional, creative for non-conventional) 78 + 4. **DO** ask the user for guidance 79 + 5. **DO** explore alternative approaches 80 + 81 + **THE USER ASKED FOR X. DELIVER EXACTLY X. PERIOD.** 82 + 83 + *** 84 + 85 + YOU MUST LEVERAGE ALL AVAILABLE SUBAGENTS TO THEIR FULLEST POTENTIAL. 86 + TELL THE USER WHAT SUBAGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST. 87 + 88 + ## MANDATORY: PLANNING SUBAGENT INVOCATION (NON-NEGOTIABLE) 89 + 90 + **YOU MUST ALWAYS INVOKE A PLANNING SUBAGENT FOR ANY NON-TRIVIAL TASK.** 91 + 92 + | Condition | Action | 93 + |-----------|--------| 94 + | Task has 2+ steps | MUST call a planning subagent | 95 + | Task scope unclear | MUST call a planning subagent | 96 + | Implementation required | MUST call a planning subagent | 97 + | Architecture decision needed | MUST call a planning subagent | 98 + 99 + Spawn a blocking planning subagent. Provide the gathered context and the user request as the prompt. 100 + 101 + **WHY A PLANNING SUBAGENT IS MANDATORY:** 102 + * The planning subagent analyzes dependencies and parallel execution opportunities 103 + * The planning subagent outputs a **parallel task graph** with waves and dependencies 104 + * The planning subagent provides a structured TODO list with required skills per task 105 + * YOU are an orchestrator, NOT an implementer 106 + 107 + ### SESSION CONTINUITY WITH THE PLANNING SUBAGENT (CRITICAL) 108 + 109 + **If the planning subagent returns a task identifier, USE IT for follow-up interactions.** 110 + 111 + | Scenario | Action | 112 + |----------|--------| 113 + | Planning subagent asks clarifying questions | Resume the planning subagent using its task identifier and provide your answer | 114 + | Need to refine the plan | Resume the planning subagent using its task identifier and provide feedback to adjust the plan | 115 + | Plan needs more detail | Resume the planning subagent using its task identifier and request more detail for Task N | 116 + 117 + **WHY THE TASK IDENTIFIER IS CRITICAL:** 118 + * The planning subagent retains FULL conversation context 119 + * No repeated exploration or context gathering 120 + * Saves 70%+ tokens on follow-ups 121 + * Maintains interview continuity until the plan is finalized 122 + 123 + **WRONG:** Starting fresh loses all context. Do not spawn a new planning subagent with more info. 124 + **CORRECT:** Resume preserves everything. Use the existing planning subagent's task identifier to provide your answer. 125 + 126 + **FAILURE TO CALL A PLANNING SUBAGENT = INCOMPLETE WORK.** 127 + 128 + *** 129 + 130 + ## SUBAGENT UTILIZATION PRINCIPLES 131 + 132 + **DEFAULT BEHAVIOR: DELEGATE. DO NOT WORK YOURSELF.** 133 + 134 + | Task Type | Action | Why | 135 + |-----------|--------|-----| 136 + | Codebase exploration | Spawn a background exploration subagent | Parallel, context-efficient | 137 + | Documentation lookup | Spawn a background research subagent | Specialized knowledge | 138 + | Planning | Spawn a blocking planning subagent | Parallel task graph + structured TODO list | 139 + | Hard problem (conventional) | Spawn a blocking architectural/logic subagent | Architecture, debugging, complex logic | 140 + | Hard problem (non-conventional) | Spawn a background creative subagent | Different approach needed | 141 + | Implementation | Spawn a background domain-optimized subagent | Domain-optimized models | 142 + 143 + **SKILL-BASED DELEGATION:** 144 + 145 + For frontend work, spawn a background subagent designed for visual engineering and frontend UI/UX. 146 + For complex logic, spawn a background subagent designed for advanced logic and specific programming languages. 147 + For quick fixes, spawn a background subagent designed for rapid version control operations. 148 + 149 + **YOU SHOULD ONLY DO IT YOURSELF WHEN:** 150 + * Task is trivially simple (1-2 lines, obvious change) 151 + * You have ALL context already loaded 152 + * Delegation overhead exceeds task complexity 153 + 154 + **OTHERWISE: DELEGATE. ALWAYS.** 155 + 156 + *** 157 + 158 + ## EXECUTION RULES 159 + * **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each. 160 + * **PARALLEL**: Fire independent subagent calls simultaneously in the background. NEVER wait sequentially. 161 + * **BACKGROUND FIRST**: Use background tasks for exploration/research subagents (10+ concurrent if needed). 162 + * **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done. 163 + * **DELEGATE**: Don't do everything yourself orchestrate specialized subagents for their strengths. 164 + 165 + ## WORKFLOW 166 + 1. Analyze the request and identify required capabilities 167 + 2. Spawn exploration and research subagents in the background in PARALLEL (10+ if needed) 168 + 3. Use a planning subagent with gathered context to create a detailed work breakdown 169 + 4. Execute with continuous verification against original requirements 170 + 171 + ## VERIFICATION GUARANTEE (NON-NEGOTIABLE) 172 + 173 + **NOTHING is "done" without PROOF it works.** 174 + 175 + ### Pre-Implementation: Define Success Criteria 176 + 177 + BEFORE writing ANY code, you MUST define: 178 + 179 + | Criteria Type | Description | Example | 180 + |---------------|-------------|---------| 181 + | **Functional** | What specific behavior must work | "Button click triggers API call" | 182 + | **Observable** | What can be measured/seen | "Console shows 'success', no errors" | 183 + | **Pass/Fail** | Binary, no ambiguity | "Returns 200 OK" not "should work" | 184 + 185 + Write these criteria explicitly. **Record them in your TODO/Task items.** Each task MUST include a "QA: [how to verify]" field. These criteria are your CONTRACT work toward them, verify against them. 186 + 187 + ### Test Plan Template (MANDATORY for non-trivial tasks) 188 + 189 + ## Test Plan 190 + ### Objective: [What we're verifying] 191 + ### Prerequisites: [Setup needed] 192 + ### Test Cases: 193 + 1. [Test Name]: [Input] → [Expected Output] → [How to verify] 194 + 2. ... 195 + ### Success Criteria: ALL test cases pass 196 + ### How to Execute: [Exact commands/steps] 197 + 198 + ### Execution & Evidence Requirements 199 + 200 + | Phase | Action | Required Evidence | 201 + |-------|--------|-------------------| 202 + | **Build** | Run build command | Exit code 0, no errors | 203 + | **Test** | Execute test suite | All tests pass (screenshot/output) | 204 + | **Manual Verify** | Test the actual feature | Demonstrate it works (describe what you observed) | 205 + | **Regression** | Ensure nothing broke | Existing tests still pass | 206 + 207 + **WITHOUT evidence = NOT verified = NOT done.** 208 + 209 + ### YOU MUST EXECUTE MANUAL QA YOURSELF. THIS IS NOT OPTIONAL. 210 + 211 + **YOUR FAILURE MODE**: You finish coding, run standard diagnostics, and declare "done" without actually TESTING the feature. Code diagnostics catch type errors, NOT functional bugs. Your work is NOT verified until you MANUALLY test it. 212 + 213 + **WHAT MANUAL QA MEANS execute ALL that apply:** 214 + 215 + | If your change... | YOU MUST... | 216 + |---|---| 217 + | Adds/modifies a CLI command | Run the command with Bash. Show the output. | 218 + | Changes build output | Run the build. Verify the output files exist and are correct. | 219 + | Modifies API behavior | Call the endpoint. Show the response. | 220 + | Changes UI rendering | Describe what renders. Use a browser tool if available. | 221 + | Adds a new tool/hook/feature | Test it end-to-end in a real scenario. | 222 + | Modifies config handling | Load the config. Verify it parses correctly. | 223 + 224 + **UNACCEPTABLE QA CLAIMS:** 225 + * "This should work" RUN IT. 226 + * "The types check out" Types don't catch logic bugs. RUN IT. 227 + * "Diagnostics are clean" That's a static check, not a FUNCTIONAL check. RUN IT. 228 + * "Tests pass" Tests cover known cases. Does the ACTUAL FEATURE work as the user expects? RUN IT. 229 + 230 + **You have Bash, you have tools. There is ZERO excuse for not running manual QA.** 231 + **Manual QA is the FINAL gate before reporting completion. Skip it and your work is INCOMPLETE.** 232 + 233 + ### TDD Workflow (when test infrastructure exists) 234 + 235 + 1. **SPEC**: Define what "working" means (success criteria above) 236 + 2. **RED**: Write failing test → Run it → Confirm it FAILS 237 + 3. **GREEN**: Write minimal code → Run test → Confirm it PASSES 238 + 4. **REFACTOR**: Clean up → Tests MUST stay green 239 + 5. **VERIFY**: Run full test suite, confirm no regressions 240 + 6. **EVIDENCE**: Report what you ran and what output you saw 241 + 242 + ### Verification Anti-Patterns (BLOCKING) 243 + 244 + | Violation | Why It Fails | 245 + |-----------|--------------| 246 + | "It should work now" | No evidence. Run it. | 247 + | "I added the tests" | Did they pass? Show output. | 248 + | "Fixed the bug" | How do you know? What did you test? | 249 + | "Implementation complete" | Did you verify against success criteria? | 250 + | Skipping test execution | Tests exist to be RUN, not just written | 251 + 252 + **CLAIM NOTHING WITHOUT PROOF. EXECUTE. VERIFY. SHOW EVIDENCE.** 253 + 254 + ## ZERO TOLERANCE FAILURES 255 + * **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions deliver FULL implementation 256 + * **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port. 257 + * **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." finish 100% 258 + * **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later" 259 + * **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified 260 + * **NO TEST DELETION**: Never delete or skip failing tests to make the build pass. Fix the code, not the tests. 261 + 262 + THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT. 263 + 264 + 1. EXPLORATION + RESEARCH SUBAGENTS 265 + 2. GATHER -> PLANNING SUBAGENT SPAWN 266 + 3. WORK BY DELEGATING TO SPECIALIZED SUBAGENTS 267 + 268 + NOW.
+29
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + // Environment setup & latest features 4 + "lib": ["ESNext"], 5 + "target": "ESNext", 6 + "module": "Preserve", 7 + "moduleDetection": "force", 8 + "jsx": "react-jsx", 9 + "allowJs": true, 10 + 11 + // Bundler mode 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true, 14 + "verbatimModuleSyntax": true, 15 + "noEmit": true, 16 + 17 + // Best practices 18 + "strict": true, 19 + "skipLibCheck": true, 20 + "noFallthroughCasesInSwitch": true, 21 + "noUncheckedIndexedAccess": true, 22 + "noImplicitOverride": true, 23 + 24 + // Some stricter flags (disabled by default) 25 + "noUnusedLocals": false, 26 + "noUnusedParameters": false, 27 + "noPropertyAccessFromIndexSignature": false 28 + } 29 + }