A string manipulation toolbox, featuring a string formatter (inspired by sprintf), a variable inspector (output featuring ANSI colors and HTML) and various escape functions (shell argument, regexp, html, etc).
[](https://www.npmjs.com/package/string-kit-js) [](https://opensource.org/licenses/ISC)
Codecs for strings of different sizes and encodings
Utilities for building ESLint plugins.
Core types and helpers for encoding and decoding byte arrays on Solana
dnd kit – a lightweight React library for building performant and accessible drag and drop experiences
A generic toolkit to help with accessibility
sprintf parser and basic formatter
Internal utilities to bee shared between `@dnd-kit` packages
Official sortable preset and sensors for dnd kit
> Utility kit for DevTools.
Helpers for generating and transforming key material
Helpers for generating account addresses
A toolkit for easy Babel AST generation and manipulation.
starter kit for tiptap
Default linter plugin for oas-validator
EXPERIMENTAL - USE WITH CAUTION - Launch latest Edge with the Devtools Protocol port open
Common utility functions for oas-kit
QuickMarkup (Terminal Kit, String Kit) text renderer for the Book Source parser, a lightweight markup language, inspired by Markdown.
Type definitions for values used in the Solana RPC, and helper functions for working with them
Type definitions for metro serializer esbuild
Drizzle Kit is a CLI migrator tool for Drizzle ORM. It is probably the one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. <https://github
Managing and serializing Rust-like Option types in JavaScript
Tree utilities which provides a full-featured extend and object-cloning facility, and various tools to deal with nested object structures.
String case conversion, slug generation, transliteration, padding, HTML stripping, whitespace normalization, word counting, reading time estimation, excerpt extraction, indentation, and more.
The affixapi.com API documentation. # Introduction Affix API is an OAuth 2.1 application that allows developers to access customer data, without developers needing to manage or maintain integrations; or collect login credentials or API keys from users for these third party systems. # OAuth 2.1 Affix API follows the [OAuth 2.1 spec](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-08). As an OAuth application, Affix API handles not only both the collection of sensitive user credentials or API keys, but also builds and maintains the integrations with the providers, so you don't have to. # How to obtain an access token in order to get started, you must: - register a `client_id` - direct your user to the sign in flow (`https://connect.affixapi.com` [with the appropriate query parameters](https://github.com/affixapi/starter-kit/tree/master/connect)) - capture `authorization_code` we will send to your redirect URI after the sign in flow is complete and exchange that `authorization_code` for a Bearer token # Sandbox keys (developer mode) ### dev ``` eyJhbGciOiJFUzI1NiIsImtpZCI6Ims5RmxwSFR1YklmZWNsUU5QRVZzeFcxazFZZ0Zfbk1BWllOSGVuOFQxdGciLCJ0eXAiOiJKV1MifQ.eyJwcm92aWRlciI6InNhbmRib3giLCJzY29wZXMiOlsiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2NvbXBhbnkiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWUiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWVzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2lkZW50aXR5IiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3BheXJ1bnMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvcGF5cnVucy86cGF5cnVuX2lkIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWJhbGFuY2VzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWVudHJpZXMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvdGltZXNoZWV0cyJdLCJ0b2tlbiI6ImQ1OTZhMmYzLWYzNzktNGE1ZC1hMmRhLTk4OWJmYWViYTg1ZCIsImlhdCI6MTcwMjkyMDkwMywiaXNzIjoicHVibGljYXBpLWludGVybWVkaWF0ZS5kZXYuZW5naW5lZXJpbmcuYWZmaXhhcGkuY29tIiwic3ViIjoiZGV2ZWxvcGVyIiwiYXVkIjoiM0ZEQUVERjktMURDQTRGNTQtODc5NDlGNkEtNDEwMjc2NDMifQ.VLWYjCQvBS0C3ZA6_J3-U-idZj5EYI2IlDdTjAWBxSIHGufp6cqaVodKsF2BeIqcIeB3P0lW-KL9mY3xGd7ckQ ``` #### `employees` endpoint sample: ``` curl --fail \ -X GET \ -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsImtpZCI6Ims5RmxwSFR1YklmZWNsUU5QRVZzeFcxazFZZ0Zfbk1BWllOSGVuOFQxdGciLCJ0eXAiOiJKV1MifQ.eyJwcm92aWRlciI6InNhbmRib3giLCJzY29wZXMiOlsiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2NvbXBhbnkiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWUiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWVzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2lkZW50aXR5IiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3BheXJ1bnMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvcGF5cnVucy86cGF5cnVuX2lkIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWJhbGFuY2VzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWVudHJpZXMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvdGltZXNoZWV0cyJdLCJ0b2tlbiI6ImQ1OTZhMmYzLWYzNzktNGE1ZC1hMmRhLTk4OWJmYWViYTg1ZCIsImlhdCI6MTcwMjkyMDkwMywiaXNzIjoicHVibGljYXBpLWludGVybWVkaWF0ZS5kZXYuZW5naW5lZXJpbmcuYWZmaXhhcGkuY29tIiwic3ViIjoiZGV2ZWxvcGVyIiwiYXVkIjoiM0ZEQUVERjktMURDQTRGNTQtODc5NDlGNkEtNDEwMjc2NDMifQ.VLWYjCQvBS0C3ZA6_J3-U-idZj5EYI2IlDdTjAWBxSIHGufp6cqaVodKsF2BeIqcIeB3P0lW-KL9mY3xGd7ckQ' \ 'https://dev.api.affixapi.com/2023-03-01/developer/employees' ``` ### prod ``` eyJhbGciOiJFUzI1NiIsImtpZCI6Ims5RmxwSFR1YklmZWNsUU5QRVZzeFcxazFZZ0Zfbk1BWllOSGVuOFQxdGciLCJ0eXAiOiJKV1MifQ.eyJwcm92aWRlciI6InNhbmRib3giLCJzY29wZXMiOlsiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2NvbXBhbnkiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWUiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWVzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2lkZW50aXR5IiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3BheXJ1bnMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvcGF5cnVucy86cGF5cnVuX2lkIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWJhbGFuY2VzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWVudHJpZXMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvdGltZXNoZWV0cyJdLCJ0b2tlbiI6IjI5YjFjYTg4LWNlNjktNDgyZC1iNGZjLTkzMWMzZmJkYWM4ZSIsImlhdCI6MTcwMjkyMTA4MywiaXNzIjoicHVibGljYXBpLWludGVybWVkaWF0ZS5wcm9kLmVuZ2luZWVyaW5nLmFmZml4YXBpLmNvbSIsInN1YiI6ImRldmVsb3BlciIsImF1ZCI6IjA4QkIwODFFLUQ5QUI0RDE0LThERjk5MjMzLTY2NjE1Q0U5In0.2zdpFAmiyYiYk6MOcbXNUwwR4M1Fextnaac340x54AidiWXCyw-u9KeavbqfYF6q8a9kcDLrxhJ8Wc_3tIzuVw ``` #### `employees` endpoint sample: ``` curl --fail \ -X GET \ -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsImtpZCI6Ims5RmxwSFR1YklmZWNsUU5QRVZzeFcxazFZZ0Zfbk1BWllOSGVuOFQxdGciLCJ0eXAiOiJKV1MifQ.eyJwcm92aWRlciI6InNhbmRib3giLCJzY29wZXMiOlsiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2NvbXBhbnkiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWUiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvZW1wbG95ZWVzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL2lkZW50aXR5IiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3BheXJ1bnMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvcGF5cnVucy86cGF5cnVuX2lkIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWJhbGFuY2VzIiwiLzIwMjMtMDMtMDEvZGV2ZWxvcGVyL3RpbWUtb2ZmLWVudHJpZXMiLCIvMjAyMy0wMy0wMS9kZXZlbG9wZXIvdGltZXNoZWV0cyJdLCJ0b2tlbiI6IjI5YjFjYTg4LWNlNjktNDgyZC1iNGZjLTkzMWMzZmJkYWM4ZSIsImlhdCI6MTcwMjkyMTA4MywiaXNzIjoicHVibGljYXBpLWludGVybWVkaWF0ZS5wcm9kLmVuZ2luZWVyaW5nLmFmZml4YXBpLmNvbSIsInN1YiI6ImRldmVsb3BlciIsImF1ZCI6IjA4QkIwODFFLUQ5QUI0RDE0LThERjk5MjMzLTY2NjE1Q0U5In0.2zdpFAmiyYiYk6MOcbXNUwwR4M1Fextnaac340x54AidiWXCyw-u9KeavbqfYF6q8a9kcDLrxhJ8Wc_3tIzuVw' \ 'https://api.affixapi.com/2023-03-01/developer/employees' ``` # Webhooks An exciting feature for HR/Payroll modes are webhooks. If enabled, your `webhook_uri` is set on your `client_id` for the respective environment: `dev | prod` Webhooks are configured to make live requests to the underlying integration 1x/hr, and if a difference is detected since the last request, we will send a request to your `webhook_uri` with this shape: ``` { added: <api.v20230301.Employees>[ <api.v20230301.Employee>{ ..., date_of_birth: '2010-08-06', display_full_name: 'Daija Rogahn', employee_number: '57993', employment_status: 'pending', employment_type: 'other', employments: [ { currency: 'eur', effective_date: '2022-02-25', employment_type: 'other', job_title: 'Dynamic Implementation Manager', pay_frequency: 'semimonthly', pay_period: 'YEAR', pay_rate: 96000, }, ], first_name: 'Daija', ... } ], removed: [], updated: [ <api.v20230301.Employee>{ ..., date_of_birth: '2009-11-09', display_full_name: 'Lourdes Stiedemann', employee_number: '63189', employment_status: 'leave', employment_type: 'full_time', employments: [ { currency: 'gbp', effective_date: '2023-01-16', employment_type: 'full_time', job_title: 'Forward Brand Planner', pay_frequency: 'semimonthly', pay_period: 'YEAR', pay_rate: 86000, }, ], first_name: 'Lourdes', } ] } ``` the following headers will be sent with webhook requests: ``` x-affix-api-signature: ab8474e609db95d5df3adc39ea3add7a7544bd215c5c520a30a650ae93a2fba7 x-affix-api-origin: webhooks-employees-webhook user-agent: affixapi.com ``` Before trusting the payload, you should sign the payload and verify the signature matches the signature sent by the `affixapi.com` service. This secures that the data sent to your `webhook_uri` is from the `affixapi.com` server. The signature is created by combining the signing secret (your `client_secret`) with the body of the request sent using a standard HMAC-SHA256 keyed hash. The signature can be created via: - create an `HMAC` with your `client_secret` - update the `HMAC` with the payload - get the hex digest -> this is the signature Sample `typescript` code that follows this recipe: ``` import { createHmac } from 'crypto'; export const computeSignature = ({ str, signingSecret, }: { signingSecret: string; str: string; }): string => { const hmac = createHmac('sha256', signingSecret); hmac.update(str); const signature = hmac.digest('hex'); return signature; }; ``` ## Rate limits Open endpoints (not gated by an API key) (applied at endpoint level): - 15 requests every 1 minute (by IP address) - 25 requests every 5 minutes (by IP address) Gated endpoints (require an API key) (applied at endpoint level): - 40 requests every 1 minute (by IP address) - 40 requests every 5 minutes (by `client_id`) Things to keep in mind: - Open endpoints (not gated by an API key) will likely be called by your users, not you, so rate limits generally would not apply to you. - As a developer, rate limits are applied at the endpoint granularity. - For example, say the rate limits below are 10 requests per minute by ip. from that same ip, within 1 minute, you get: - 10 requests per minute on `/orders`, - another 10 requests per minute on `/items`, - and another 10 requests per minute on `/identity`, - for a total of 30 requests per minute.
== Terminal UIs, the Ruby Way RatatuiRuby[https://rubygems.org/gems/ratatui_ruby] is a RubyGem built on Ratatui[https://ratatui.rs], a leading TUI library written in Rust[https://rust-lang.org]. You get native performance with the joy of Ruby. gem install ratatui_ruby {rdoc-image:https://ratatui-ruby.dev/hero.gif}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_cli_rich_moments/README_md.html] === Rich Moments Add a spinner, a progress bar, or an inline menu to your CLI script. No full-screen takeover. Your terminal history stays intact. ==== Inline Viewports Standard TUIs erase themselves on exit. Your carefully formatted CLI output disappears. Users lose their scrollback. <b>Inline viewports</b> solve this. They occupy a fixed number of lines, render rich UI, then leave the output in place when done. Perfect for spinners, menus, progress indicators—any brief moment of richness. require "ratatui_ruby" RatatuiRuby.run(viewport: :inline, height: 1) do |tui| until connected? status = tui.paragraph(text: "\#{spin} Connecting...") tui.draw { |frame| frame.render_widget(status, frame.area) } end end === Build Something Real Full-screen applications with {keyboard and mouse input}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_all_events/README_md.html]. The managed loop sets up the terminal and restores it on exit, even after crashes. RatatuiRuby.run do |tui| loop do tui.draw do |frame| frame.render_widget( tui.paragraph(text: "Hello, RatatuiRuby!", alignment: :center), frame.area ) end case tui.poll_event in { type: :key, code: "q" } then break else nil end end end ==== Widgets included: [Layout] {Block}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_block/README_md.html], {Center}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_center/README_md.html], {Clear (Popup, Modal)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_popup/README_md.html], {Layout (Split, Grid)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_layout_split/README_md.html], {Overlay}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_overlay/README_md.html] [Data] {Bar Chart}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_barchart/README_md.html], {Chart}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_chart/README_md.html], {Gauge}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_gauge/README_md.html], {Line Gauge}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_line_gauge/README_md.html], {Sparkline}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_sparkline/README_md.html], {Table}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_table/README_md.html] [Text] {Cell}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_cell/README_md.html], {List}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_list/README_md.html], {Rich Text (Line, Span)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_rich_text/README_md.html], {Scrollbar (Scroll)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_scrollbar/README_md.html], {Tabs}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_tabs/README_md.html] [Graphics] {Calendar}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_calendar/README_md.html], {Canvas}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_canvas/README_md.html], {Map (World Map)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_map/README_md.html] Need something else? {Build custom widgets}[https://www.ratatui-ruby.dev/docs/v0.10/doc/concepts/custom_widgets_md.html] in Ruby! --- === Testing Built In TUI testing is tedious. You need a headless terminal, event injection, snapshot comparisons, and style assertions. RatatuiRuby bundles all of it. require "ratatui_ruby/test_helper" class TestColorPicker < Minitest::Test include RatatuiRuby::TestHelper def test_swatch_widget with_test_terminal(10, 3) do RatatuiRuby.draw do |frame| frame.render_widget(Swatch.new(:red), frame.area) end assert_cell_style 2, 1, char: "█", bg: :red end end end ==== What's inside: - <b>Headless terminal</b> — No real TTY needed - <b>Snapshots</b> — Plain text and rich (ANSI colors) - <b>Event injection</b> — Keys, mouse, paste, resize - <b>Style assertions</b> — Color, bold, underline at any cell - <b>Test doubles</b> — Mock frames and stub rects - <b>UPDATE_SNAPSHOTS=1</b> — Regenerate baselines in one command --- ==== Inline Menu Example require "ratatui_ruby" # This example renders an inline menu. Arrow keys select, enter confirms. # The menu appears in-place, preserving scrollback. When the user chooses, # the TUI closes and the script continues with the selected value. class RadioMenu CHOICES = ["Production", "Staging", "Development"] # ASCII strings are universally supported. PREFIXES = { active: "●", inactive: "○" } # Some terminals may not support Unicode. CONTROLS = "↑/↓: Select | Enter: Choose | Ctrl+C: Cancel" # Let users know what keys you handle. TITLES = ["Select Environment", # The default title position is top left. { content: CONTROLS, # Multiple titles can save space. position: :bottom, # Titles go on the top or bottom, alignment: :right }] # aligned left, right, or center def call # This method blocks until a choice is made. RatatuiRuby.run(viewport: :inline, height: 5) do |tui| # RatauiRuby.run manages the terminal. @tui = tui # The TUI instance is safe to store. show_menu until chosen? # You can use any loop keyword you like. end # `run` won't return until your block does, RadioMenu::CHOICES[@choice] # so you can use it synchronously. end # Classes like RadioMenu are convenient for private # CLI authors to offer "rich moments." def show_menu = @tui.draw do |frame| # RatatuiRuby gives you low-level access. widget = @tui.paragraph( # But the TUI facade makes it easy to use. text: menu_items, # Text can be spans, lines, or paragraphs. block: @tui.block(borders: :all, titles: TITLES) # Blocks give you boxes and titles, and hold ) # one or more widgets. We only use one here, frame.render_widget(widget, frame.area) # but "area" lets you compose sub-views. end def chosen? # You are responsible for handling input. interaction = @tui.poll_event # Every frame, you receive an event object: return choose if interaction.enter? # Key, Mouse, Resize, Paste, FocusGained, # FocusLost, or None objects. They come with move_by(-1) if interaction.up? # predicates, support pattern matching, and move_by(1) if interaction.down? # can be inspected for properties directly. quit! if interaction.ctrl_c? # Your application must handle every input, false # even interrupts and other exit patterns. end def choose # Here, the loop is about to exit, and the prepare_next_line # block will return. The inline viewport @choice # will be torn down and the terminal will end # be restored, but you are responsible for # positioning the cursor. def prepare_next_line # To ensure the next output is on a new area = @tui.viewport_area # line, query the viewport area and move RatatuiRuby.cursor_position = [0, area.y + area.height] # the cursor to the start of the last line. puts # Then print a newline. end def quit! # All of your familiar Ruby control flow prepare_next_line # keywords work as expected, so we can exit 0 # use them to leave the TUI. end def move_by(line_count) # You are in full control of your UX, so @choice = (@choice + line_count) % CHOICES.size # you can implement any logic you need: end # Would you "wrap around" here, or not? # def menu_items = CHOICES.map.with_index do |choice, i| # Notably, RatatuiRuby has no concept of "\#{prefix_for(i)} \#{choice}" # "menus" or "radio buttons". You are in end # full control, but it also means you must def prefix_for(choice_index) # implement the logic yourself. For larger return PREFIXES[:active] if choice_index == @choice # applications, consider using Rooibos, PREFIXES[:inactive] # an MVU framework built with RatatuiRuby. end # Or, use the upcoming ratatui-ruby-kit, # our object-oriented component library. def initialize = @choice = 0 # However, those are both optional, and end # designed for full-screen Terminal UIs. # RatatuiRuby will always give you the most choice = RadioMenu.new.call # control, and is enough for "rich CLI puts "You chose \#{choice}!" # moments" like this one. --- === Full App Solutions RatatuiRuby renders. For complex applications, add a framework that manages state and composition. ==== Rooibos[https://www.rooibos.run] (Framework) Model-View-Update architecture. Inspired by Elm, Bubble Tea, and React + Redux. Your UI is a pure function of state. - Functional programming with MVU - Commands work off the main thread - Messages, not callbacks, drive updates ==== {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Coming Soon) Component-based architecture. Encapsulate state, input handling, and rendering in reusable pieces. - OOP with stateful components - Separate UI state from domain logic - Built-in focus management & click handling Both use the same widget library and rendering engine. Pick the paradigm that fits your brain. --- === Why RatatuiRuby? Ruby deserves world-class terminal user interfaces. TUI developers deserve a world-class language. RatatuiRuby wraps Rust's Ratatui via native extension. The Rust library handles rendering. Your Ruby code handles design. >>> "Text UIs are seeing a renaissance with many new TUI libraries popping up. The Ratatui bindings have proven to be full featured and stable." — {Mike Perham}[https://www.mikeperham.com/], creator of Sidekiq[https://sidekiq.org/] and Faktory[https://contribsys.com/faktory/] ==== Why Rust? Why Ruby? Rust excels at low-level rendering. Ruby excels at expressing domain logic and UI. RatatuiRuby puts each language where it performs best. ==== Versus CharmRuby CharmRuby[https://charm-ruby.dev/] wraps Charm's Go libraries. Both projects give Ruby developers TUI options. [Integration] CharmRuby: Two runtimes, one process. RatatuiRuby: Native extension in Rust. [Runtime] CharmRuby: Go + Ruby (competing). RatatuiRuby: Ruby (Rust has no runtime). [Memory] CharmRuby: Two uncoordinated GCs. RatatuiRuby: One Garbage Collector. [Style] CharmRuby: The Elm Architecture (TEA). RatatuiRuby: TEA, OOP, or Imperative. --- === Links [Get Started] {Quickstart}[https://www.ratatui-ruby.dev/docs/v0.10/doc/getting_started/quickstart_md.html], {Examples}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_cli_rich_moments/README_md.html], {API Reference}[https://www.ratatui-ruby.dev/docs/v0.10/], {Guides}[https://www.ratatui-ruby.dev/docs/v0.10/doc/index_md.html] [Ecosystem] Rooibos[https://www.rooibos.run], {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Planned), {Framework}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-5-the-framework] (Planned), {UI Widgets}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-6-licensing] (Planned) [Community] {Forum}[https://forum.setdef.com/c/ratatui-ruby/6], {Announcements}[https://forum.setdef.com/tags/c/ratatui-ruby/6/announcement], {Discussion}[https://forum.setdef.com/tags/c/ratatui-ruby/6/discussion], {Bug Tracker}[https://forum.setdef.com/tags/c/ratatui-ruby/6/bug] [Contribute] {Contributing Guide}[https://man.sr.ht/~kerrick/ratatui_ruby/contributing.md], {Code of Conduct}[https://man.sr.ht/~kerrick/ratatui_ruby/code_of_conduct.md], {Project History}[https://man.sr.ht/~kerrick/ratatui_ruby/history/index.md], {Pull Requests}[https://forum.setdef.com/tags/c/ratatui-ruby/6/patch] --- [Website] https://www.ratatui-ruby.dev [Source] https://github.com/setdef/RatatuiRuby [RubyGems] https://rubygems.org/gems/ratatui_ruby [Upstream] https://ratatui.rs [Build Status] https://builds.sr.ht/~kerrick/ratatui_ruby © 2026 Kerrick Long · Library: LGPL-3.0-or-later · Website: CC-BY-NC-ND-4.0 · Snippets: MIT-0
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.