Chisle
The coding-agent plugin that bills 52% of a bare model across 20 live tasks. Terse persona + tool-output compressor + context diet.
翻訳待ち
the elegant TypeScript UI framework
翻訳待ち
Loading the full README. The saved excerpt is shown below.
Ripple is a TypeScript-first UI framework built around .tsrx files, fine-grained
reactivity, scoped styles, and a small runtime. It pairs the authoring feel of JSX
with template-native control flow and TypeScript setup that can live right beside
the UI it feeds.
Created by @trueadm, who has contributed to
Inferno,
React,
Lexical, and
Svelte 5.
.tsrx is also a standalone language. The shared TSRX compiler stack can target
React, Preact, Solid, Vue, and Ripple. Ripple is the runtime-focused target with
track(), reactive collections, server modules, hydration, and DOM helpers.Read the full README ↗Ripple is a TypeScript-first UI framework built around .tsrx files, fine-grained
reactivity, scoped styles, and a small runtime. It pairs the authoring feel of JSX
with template-native control flow and TypeScript setup that can live right beside
the UI it feeds.
Created by @trueadm, who has contributed to Inferno, React, Lexical, and Svelte 5.
.tsrxis also a standalone language. The shared TSRX compiler stack can target React, Preact, Solid, Vue, and Ripple. Ripple is the runtime-focused target withtrack(), reactive collections, server modules, hydration, and DOM helpers.
Ripple Docs | Ripple Playground | TSRX Website
track() and .value.RippleArray, RippleObject, RippleMap, and RippleSet.@if, @for, @switch, and @try.@{...}).<style> blocks with automatic class hashing.npx create-ripple
cd my-app
npm install
npm run dev
npx degit Ripple-TS/ripple/templates/basic my-app
cd my-app
npm install
npm run dev
npm install ripple @ripple-ts/vite-plugin
Use npm, pnpm, yarn, or bun, matching your project.
// index.ts
import { mount } from 'ripple';
import { App } from './App.tsrx';
mount(App, {
props: { title: 'Hello world!' },
target: document.getElementById('root'),
});
Components are ordinary TypeScript functions. Return a JSX element directly when
the component has one root, and use a JSX statement container (@{...}) when
setup statements or multiple rendered siblings belong next to the UI.
type ButtonProps = {
text: string;
onClick: () => void;
};
export function Button({ text, onClick }: ButtonProps) {
return <button class="button" {onClick}>{text}</button>;
}
export function App() {
return <Button text="Click me" onClick={() => console.log('Clicked!')} />;
}
Fragments are still useful when the component really returns multiple siblings,
such as markup plus a scoped <style> block.
Plain JSX children are text, elements, comments, and {...} expression
containers. When a scope needs TypeScript setup before rendering, use a JSX
statement container: @{...}. Setup comes first and the container finishes with
exactly one output node: a JSX element, JSX fragment, or JSX control-flow
expression. If the output needs text, expression containers, or multiple siblings
after setup, wrap them in a fragment.
Text such as x = 123 between tags is JSX text, not JavaScript, unless it is
inside a statement container.
import { track } from 'ripple';
export function Counter() @{
const count = track(0);
const increment = () => count.value++;
<button onClick={increment}>Count:{count.value}</button>
}
The same rule applies in nested scopes:
export function Cart({ items }: { items: Item[] }) @{
<div class="cart">@{
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const discount =
subtotal > 100 ? 0.1 : 0;
<>
<p>Subtotal: ${subtotal}</p>
<p>Save: ${(subtotal * discount).toFixed(2)}</p>
</>
}</div>
}
JavaScript comments are allowed between template children and are not rendered.
Static text is JSX text. Dynamic values use normal JSX expression containers.
export function Greeting({ name }: { name?: string }) @{
@if (name) {
<p>Hello,{name}</p>
} @else {
<p>Hello, stranger</p>
}
}
Rendered control flow uses directive-prefixed expressions:
import { RippleArray, track } from 'ripple';
type Item = { id: number; name: string; done?: boolean };
export function TodoList() @{
const items = new RippleArray<Item>({ id: 1, name: 'Plan the work' }, {
id: 2,
name: 'Ship the work',
});
const showDone = track(true);
const visibleItems = () => items.filter((item) => showDone.value || !item.done);
<ul>
@for (const item of visibleItems(); index i; key item.id) {
<li>
{i + 1}
.
{item.name}
</li>
} @empty {
<li>No todos to show</li>
}
</ul>
}
Use ordinary return for real function exits in TypeScript setup. Use @if for
conditional rendering; direct return, continue, and break statements are not
valid inside @if template branches.
export function Dashboard({ user }: { user: User | null }) @{
if (!user) {
return null;
}
<>
<h1>Welcome,{user.name}</h1>
<p>Here is your dashboard.</p>
</>
}
@try supports error and pending UI:
export function ProfilePanel() @{
@try {
<UserProfile />
} @pending {
<p>Loading...</p>
} @catch (error, reset) {
<div>
<p>Error:{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
}
}
Create state with track() and read or write it through .value. Reads in
templates and effects stay reactive, and writes update everything that depends on
them.
This is the beginning of the README. Read the rest at the original. Read the full README ↗