{"body":"#!/usr/bin/env raku\n#\nuse Selkie::UI;\n\n# --- Themes ----------------------------------------------------------------\n\nsub dark-theme(--> Selkie::Theme) {\n    Theme(\n        base              => Style(:fg(0xEEEEEE), :bg(0x16162E)       ),\n        text              => Style(:fg(0xEEEEEE)                      ),\n        text-dim          => Style(:fg(0x888899)                      ),\n        text-highlight    => Style(:fg(0xFFFFFF), :bold               ),\n        border            => Style(:fg(0x444466)                      ),\n        border-focused    => Style(:fg(0x7AA2F7), :bold               ),\n        input             => Style(:fg(0xEEEEEE), :bg(0x1A1A2E)       ),\n        input-focused     => Style(:fg(0xFFFFFF), :bg(0x2A2A3E)       ),\n        input-placeholder => Style(:fg(0x606080), :italic             ),\n        scrollbar-track   => Style(:fg(0x333344)                      ),\n        scrollbar-thumb   => Style(:fg(0x7AA2F7)                      ),\n        divider           => Style(:fg(0x444466)                      ),\n        tab-active        => Style(:fg(0xFFFFFF), :bg(0x7AA2F7), :bold),\n        tab-inactive      => Style(:fg(0x8888A0), :bg(0x16162E)       ),\n    );\n}\n\nsub light-theme(--> Selkie::Theme) {\n    Theme(\n        base              => Style(:fg(0x222222), :bg(0xF5F5F0)       ),\n        text              => Style(:fg(0x222222)                      ),\n        text-dim          => Style(:fg(0x666666)                      ),\n        text-highlight    => Style(:fg(0x000000), :bold               ),\n        border            => Style(:fg(0xCCCCCC)                      ),\n        border-focused    => Style(:fg(0x3366CC), :bold               ),\n        input             => Style(:fg(0x222222), :bg(0xFFFFFF)       ),\n        input-focused     => Style(:fg(0x000000), :bg(0xEEEEFF)       ),\n        input-placeholder => Style(:fg(0x999999), :italic             ),\n        scrollbar-track   => Style(:fg(0xDDDDDD)                      ),\n        scrollbar-thumb   => Style(:fg(0x3366CC)                      ),\n        divider           => Style(:fg(0xCCCCCC)                      ),\n        tab-active        => Style(:fg(0xFFFFFF), :bg(0x3366CC), :bold),\n        tab-inactive      => Style(:fg(0x666688), :bg(0xF5F5F0)       ),\n    );\n}\n\nmy $input;\nmy $app = App(\n    {\n        # --- Store handlers -------------------------------------------------------\n        #\n        # Each message is { speaker => Str, text => Str }.\n\n        Handler 'app/init', -> $st, %ev {\n            (db => {\n                    :messages[\n                        { speaker => 'bot', text => 'Hello! Type something and I will echo it back, transformed.' },\n                    ],\n                    :dark,\n                },);\n        }\n\n        Handler 'chat/send', -> $st, %ev {\n            my $text = (%ev<text> // '').trim;\n            if $text.chars == 0 {\n                ();\n            } else {\n                my @msgs = ($st.get-in('messages') // []).Array;\n                @msgs.push({ speaker => 'you', :$text });\n                # Synthesize a \"bot\" reply by reversing the input\n                my $reply = $text.flip;\n                @msgs.push({ speaker => 'bot', text => \"Reversed: $reply\" });\n                (:db{ :messages[@msgs] },);\n            }\n        }\n\n        Handler 'chat/clear', -> $st, %ev {\n            (:db{ :messages[] },);\n        }\n\n        Handler 'theme/toggle', -> $st, %ev {\n            (:db{ dark => !($st.get-in('dark') // True) },);\n        }\n\n        # --- Widget tree ----------------------------------------------------------\n\n        VBox({\n                Text(\n                    '  Echo Chat  —  Ctrl+Enter send, Ctrl+L clear, Ctrl+T theme, Ctrl+Q quit'\n                ).size(1).style: :fg(0x7AA2F7), :bold;\n\n                # Message list (CardList in a Border so focus is visible)\n                Border( \"Chat\", my $cards = CardList() )\n\n                # Input\n                $input = Border(\n                    'Compose',\n                    MultiLineInput('Type a message — Ctrl+Enter to send').max-lines(5).size: 1\n                ).size: 3;\n\n                # --- Wiring ---------------------------------------------------------------\n\n                $input.on-submit.tap: -> $text {\n                    if $text.chars > 0 {\n                        $app.store.dispatch('chat/send', :$text);\n                        $input.clear;\n                    }\n                };\n\n                # --- Subscriptions --------------------------------------------------------\n\n                # Build a styled card per message and rebuild the CardList when messages\n                # change. For brevity we destroy and recreate cards on every change — fine\n                # at chat-app scale. A higher-volume app would diff and patch.\n                SubscribeWithCallback 'message-cards',\n                -> $s { $s.get-in('messages') // [] },\n                -> @msgs {\n                    $cards.clear-items;\n                    for @msgs -> %m {\n                        my $is-bot = %m<speaker> eq 'bot';\n                        my %name-style = $is-bot\n                        ?? |(:fg(0x9ECE6A), :bold)\n                        !! |(:fg(0x7AA2F7), :bold);\n                        my %body-style = |(:fg(0xEEEEEE));\n\n                        # Wrap in its own Border so cards visually separate.\n                        my $border = Border(\n                            RichText.new(\n                                Span(\"{%m<speaker>}: \").style(%name-style),\n                                Span(%m<text>).style: %body-style\n                            ).flex;\n                        ).flex;\n\n                        $cards.add-item(\n                            $rich,                      # positional: the inner widget\n                            :root($border),             # the rendered root (the Border)\n                            :3height,                   # 1 text line + 2 border rows\n                            :$border,                   # the Border for focus highlighting\n                        );\n                    }\n                    $cards.select-last if @msgs;\n                },\n                $cards,\n                ;\n\n                # Theme toggle: install the appropriate theme on the root each time `dark`\n                # changes. Children inherit by walking up the parent chain.\n                SubscribeWithCallback 'theme',\n                -> $s { $s.get-in('dark') // True },\n                -> Bool $dark {\n                    $root.set-theme: $dark ?? dark-theme() !! light-theme();\n                },\n                $root,\n                ;\n\n                # --- Global keybinds ------------------------------------------------------\n\n                OnKey 'ctrl+q', -> $ { $app.quit };\n                OnKey 'ctrl+l', -> $ {\n                    Dispatch 'chat/clear';\n                    Toast 'Cleared';\n                };\n                OnKey 'ctrl+t', -> $ {\n                    Dispatch 'theme/toggle';\n                    Toast 'Theme toggled';\n                };\n\n        }).name: \"main\";\n\n        Dispatch 'app/init';\n    }\n).theme: dark-theme();\n\n# --- Go -------------------------------------------------------------------\n\n$app.store.tick;\n\n$app.focus($input);\n$app.run;\n","name":"","extension":"raku","url":"https://www.irccloud.com/pastebin/WM2OHNR2","modified":1776644000,"id":"WM2OHNR2","size":7307,"lines":177,"own_paste":false,"theme":"","date":1776643868}