{"id":42856794,"url":"https://github.com/brianium/tsain","last_synced_at":"2026-01-30T12:25:11.855Z","repository":{"id":333572427,"uuid":"1137683939","full_name":"brianium/tsain","owner":"brianium","description":"An experiment with REPL driven UI and the sandestin ecosystem","archived":false,"fork":false,"pushed_at":"2026-01-20T02:49:20.000Z","size":165,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-20T05:23:27.294Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-19T17:36:15.000Z","updated_at":"2026-01-20T02:49:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/brianium/tsain","commit_stats":null,"previous_names":["brianium/tsain"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/brianium/tsain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Ftsain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Ftsain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Ftsain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Ftsain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianium","download_url":"https://codeload.github.com/brianium/tsain/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Ftsain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28912909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T12:13:43.263Z","status":"ssl_error","status_checked_at":"2026-01-30T12:13:22.389Z","response_time":66,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-30T12:25:08.118Z","updated_at":"2026-01-30T12:25:11.850Z","avatar_url":"https://github.com/brianium.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ascolais/tsain\n\nA REPL-driven component sandbox for Clojure + Datastar applications. Design, iterate, and persist hiccup components with live browser preview and hot CSS reload.\n\n## Getting Started\n\n### 1. Add Dependency\n\n```clojure\n;; deps.edn\n{:deps {io.github.brianium/tsain {:git/tag \"v0.4.0\" :git/sha \"3d9bcc9\"}}}\n```\n\n### 2. Create Configuration\n\nCreate `tsain.edn` at your project root:\n\n```clojure\n{:ui-namespace myapp.ui                        ;; Where chassis aliases live\n :database-file \"tsain.db\"                     ;; Component library (SQLite)\n :stylesheet \"dev/resources/public/styles.css\" ;; CSS for hot reload\n :port 3000}\n```\n\nLegacy EDN storage is also supported:\n```clojure\n{:components-file \"resources/components.edn\"} ;; Instead of :database-file\n```\n\n### 3. Create UI Namespace\n\nCreate your UI namespace for chassis aliases:\n\n```clojure\n(ns myapp.ui\n  (:require [dev.onionpancakes.chassis.core :as c]))\n\n;; Define component structure with aliases\n(defmethod c/resolve-alias ::card [_ attrs _]\n  (let [{:card/keys [title body]} attrs]\n    [:div.card attrs\n     [:h2.card-title title]\n     [:p.card-body body]]))\n```\n\n### 4. Wire Up the System\n\n```clojure\n(ns dev\n  (:require [ascolais.sandestin :as s]\n            [ascolais.tsain :as tsain]\n            [ascolais.tsain.routes :as tsain.routes]\n            [ascolais.twk :as twk]\n            [ascolais.sfere :as sfere]\n            [reitit.ring :as rr]\n            [ring.middleware.resource :refer [wrap-resource]]\n            [ring.middleware.params :refer [wrap-params]]\n            [starfederation.datastar.clojure.adapter.http-kit :as ds-hk]\n            [org.httpkit.server :as hk]\n            myapp.ui))  ;; Require to register aliases\n\n(defn start []\n  (let [;; Create tsain registry (reads tsain.edn)\n        tsain-reg  (tsain/registry)\n        state-atom (::tsain/state tsain-reg)\n        config     (::tsain/config tsain-reg)\n\n        ;; Create connection store and dispatch\n        store    (sfere/store {:type :caffeine :duration-ms 30000})\n        dispatch (s/create-dispatch [(twk/registry)\n                                     (sfere/registry store)\n                                     tsain-reg])\n\n        ;; Create router with tsain routes\n        router (rr/router\n                (tsain.routes/routes dispatch state-atom config)\n                {:data {:middleware [(fn [h] (fn [r] (h (assoc r :dispatch dispatch))))\n                                     (twk/with-datastar ds-hk/-\u003esse-response dispatch)]}})\n\n        ;; Create app\n        app (-\u003e (rr/ring-handler router (rr/create-default-handler))\n                wrap-params\n                (wrap-resource \"public\"))\n\n        ;; Start server\n        server (hk/run-server app {:port (:port config)})]\n\n    (println (str \"Sandbox at http://localhost:\" (:port config) \"/sandbox\"))\n    {:dispatch dispatch :server server}))\n```\n\n### 5. Start Iterating\n\n```clojure\n(def system (start))\n\n;; Preview a component\n((:dispatch system) [[::tsain/preview\n                      [:myapp.ui/card {:card/title \"Hello\" :card/body \"World\"}]]])\n\n;; Discover available effects\n(s/describe (:dispatch system))\n```\n\nOpen `http://localhost:3000/sandbox` in your browser to see live updates.\n\n## Features\n\n### Live Preview\nPush hiccup from REPL → instant browser update across all connected devices via SSE.\n\n```clojure\n(dispatch [[::tsain/preview [:div.card [:h2 \"Title\"] [:p \"Content\"]]]])\n```\n\n### Component Library\nCommit, browse, and copy components with a storybook-style sidebar.\n\n```clojure\n;; Commit with dark/light variants\n(dispatch [[::tsain/commit :my-card\n            {:description \"Card component\"\n             :examples [{:label \"Dark\" :hiccup [:myapp.ui/card {...}]}\n                        {:label \"Light\" :hiccup [:div.theme-light [:myapp.ui/card {...}]]}]}]])\n\n;; Browse library\n(dispatch [[::tsain/show-components :my-card]])\n```\n\n### Discoverable API\n\nTsain provides component discovery functions:\n\n```clojure\n(tsain/describe)                         ;; List all components with schemas\n(tsain/describe :myapp.ui/card)          ;; Get details for specific component\n(tsain/grep \"button\")                    ;; Search by keyword\n(tsain/props :variant)                   ;; Find components with specific prop\n```\n\nEffects are also discoverable via sandestin:\n\n```clojure\n(s/describe dispatch)                    ;; List all effects\n(s/describe dispatch ::tsain/preview)    ;; Inspect specific effect\n(s/sample dispatch ::tsain/preview)      ;; Generate example invocation\n```\n\n### CSS Hot Reload\nEdit your stylesheet, save → browser updates automatically (requires file watcher).\n\n### Chassis Aliases\nComponent structure lives in code, configuration in data:\n\n```clojure\n;; Structure in myapp/ui.clj\n(defmethod c/resolve-alias ::game-card [_ attrs _]\n  (let [{:game-card/keys [title cost attack]} attrs]\n    [:div.game-card\n     [:div.game-card-cost cost]\n     [:div.game-card-title title]]))\n\n;; Usage - lean config props\n[:myapp.ui/game-card\n {:game-card/title \"Neural Phantom\"\n  :game-card/cost \"4\"\n  :game-card/attack \"3\"}]\n```\n\nNamespaced attributes (`:game-card/title`) are elided from HTML output - they're config for the alias, not HTML attributes.\n\n## Effect Reference\n\n| Effect | Purpose |\n|--------|---------|\n| `[::tsain/preview hiccup]` | Replace preview with content |\n| `[::tsain/preview-append hiccup]` | Append to preview |\n| `[::tsain/preview-clear]` | Clear preview |\n| `[::tsain/commit :name opts]` | Save to library |\n| `[::tsain/uncommit :name]` | Remove from library |\n| `[::tsain/show-components :name]` | View component with sidebar |\n| `[::tsain/show-preview]` | Return to preview view |\n| `[::tsain/patch-signals {:key val}]` | Test Datastar signals |\n\n## Sample Files\n\nThe `sample/` directory contains starter templates:\n\n- `sample/CLAUDE.md` - Ecosystem documentation for Claude Code\n- `sample/tsain.edn` - Documented configuration\n- `sample/ui.clj` - Starter chassis alias namespace\n\nCopy and adapt these for your project.\n\n## Claude Code Skill\n\nTsain includes a skill for REPL-driven component development workflows:\n\n- `/tsain iterate` - Direct component iteration workflow\n- `/tsain implement` - Spec-driven implementation workflow\n\nInstall by copying from this repo:\n\n```bash\ncp -r path/to/tsain/.claude/skills/tsain .claude/skills/\n```\n\n## Tech Stack\n\n| Library | Purpose |\n|---------|---------|\n| [sandestin](https://github.com/brianium/sandestin) | Effect dispatch with schema-driven discoverability |\n| [twk](https://github.com/brianium/twk) | Datastar SSE integration |\n| [sfere](https://github.com/brianium/sfere) | Connection management and broadcasting |\n| [chassis](https://github.com/onionpancakes/chassis) | Hiccup aliases for component structure |\n| [Datastar](https://data-star.dev/) | Frontend reactivity via HTML attributes |\n\n## Development (Contributing to tsain)\n\n### Prerequisites\n\n- [Clojure CLI](https://clojure.org/guides/install_clojure) 1.11+\n- [Babashka](https://github.com/babashka/babashka) v1.12.212+\n- [bbin](https://github.com/babashka/bbin)\n\n### Setup\n\n```bash\n# Install clojure-mcp-light tools (for Claude Code)\nbbin install https://github.com/bhauman/clojure-mcp-light.git --tag v0.2.1\nbbin install https://github.com/bhauman/clojure-mcp-light.git --tag v0.2.1 \\\n  --as clj-nrepl-eval --main-opts '[\"-m\" \"clojure-mcp-light.nrepl-eval\"]'\n```\n\n### Running\n\n```bash\nclj -M:dev\n```\n\n```clojure\n(dev)       ;; Switch to dev namespace\n(start)     ;; Start sandbox at localhost:3000\n(reload)    ;; Reload after code changes\n```\n\n### Testing\n\n```bash\nclj -X:test\n```\n\n### Project Structure\n\n```\ntsain.edn                           # Configuration\nsrc/clj/ascolais/tsain.clj          # Registry factory\nsrc/clj/ascolais/tsain/routes.clj   # Route factory\nsrc/clj/ascolais/tsain/views.clj    # View rendering\nresources/tsain/sandbox.css         # Sandbox chrome (classpath)\nsample/                             # Starter templates for consumers\n.claude/skills/tsain/               # Claude Code skill for component workflow\ndev/src/clj/sandbox/                # Development sandbox (dogfooding)\nspecs/                              # Living specifications\n```\n\n## Specifications\n\nSee [specs/README.md](specs/README.md) for living documentation.\n\n## License\n\nCopyright © 2026\n\nDistributed under the Eclipse Public License version 1.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Ftsain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianium%2Ftsain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Ftsain/lists"}