rip-lang 3.14.5 → 3.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/bin/rip +5 -0
- package/docs/AGENTS.md +1 -1
- package/docs/RIP-LANG.md +17 -5
- package/docs/RIP-SCHEMA.md +4 -4
- package/docs/demo/README.md +43 -0
- package/docs/demo/components/_layout.rip +28 -0
- package/docs/demo/components/about.rip +36 -0
- package/docs/demo/components/card.rip +10 -0
- package/docs/demo/components/counter.rip +33 -0
- package/docs/demo/components/index.rip +30 -0
- package/docs/demo/components/todos.rip +48 -0
- package/docs/demo/css/styles.css +472 -0
- package/docs/dist/rip.js +3211 -4619
- package/docs/dist/rip.min.js +270 -683
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +6 -6
- package/docs/extensions/duckdb/index.html +7 -5
- package/docs/extensions/duckdb/manifest.json +1 -1
- package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
- package/package.json +8 -3
- package/src/AGENTS.md +107 -9
- package/src/{ui.rip → app.rip} +24 -2
- package/src/browser.js +154 -37
- package/src/compiler.js +87 -9
- package/src/grammar/grammar.rip +1 -1
- package/src/grammar/solar.rip +0 -1
- package/src/lexer.js +25 -3
- package/src/parser.js +4 -4
- package/src/schema/dts-emit.js +329 -0
- package/src/schema/loader-browser.js +55 -0
- package/src/schema/loader-server.js +65 -0
- package/src/schema/runtime-browser-stubs.js +51 -0
- package/src/schema/runtime-db-naming.js +34 -0
- package/src/schema/runtime-ddl.js +124 -0
- package/src/schema/runtime-orm.js +294 -0
- package/src/schema/runtime-validate.js +816 -0
- package/src/schema/runtime.generated.js +1315 -0
- package/src/{schema.js → schema/schema.js} +43 -1627
- package/src/typecheck.js +3 -2
- package/src/types-emit.js +1021 -0
- package/src/types.js +11 -1035
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.
|
|
12
|
+
<a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.15.1-blue.svg" alt="Version"></a>
|
|
13
13
|
<a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
|
|
14
14
|
<a href="#"><img src="https://img.shields.io/badge/tests-1%2C436%2F1%2C436-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
|
@@ -66,8 +66,10 @@ Pre-built binaries and VS Code / Cursor extensions are published via GitHub Page
|
|
|
66
66
|
|
|
67
67
|
**`ripdb`** — DuckDB extension that exposes a rip-db server as a first-class attached database:
|
|
68
68
|
|
|
69
|
+
```bash
|
|
70
|
+
duckdb -unsigned # required for custom-repo extensions
|
|
71
|
+
```
|
|
69
72
|
```sql
|
|
70
|
-
SET allow_unsigned_extensions = true;
|
|
71
73
|
INSTALL ripdb FROM 'https://shreeve.github.io/rip-lang/extensions/duckdb';
|
|
72
74
|
LOAD ripdb;
|
|
73
75
|
```
|
|
@@ -349,7 +351,7 @@ The **body syntax is declarative**, not general Rip code. Five line forms are le
|
|
|
349
351
|
|
|
350
352
|
**`:model` is where the pieces converge.** One declaration gives you a validator, a class with fields as enumerable own properties and methods/getters on the prototype, a chainable async query builder (`User.where(active: true).order("last_name").all!`), migration DDL that works standalone (`User.toSQL()` never touches the database), belongs-to/has-many accessors that resolve cross-module through the registry, and full shadow TypeScript with `ModelSchema<Instance, Data>` typing that propagates through schema algebra. Hydrated instances carry both snake_case and camelCase aliases on DB-derived columns (`order.user_id` and `order.userId` read the same slot), so raw SQL helpers and ORM access coexist cleanly. A single-function adapter interface (`adapter.query(sql, params)`) routes all database I/O, so tests use in-memory mocks and production uses rip-db without the ORM caring.
|
|
351
353
|
|
|
352
|
-
**Schema algebra** — `.pick`, `.omit`, `.partial`, `.required`, `.extend` — always returns a new `:shape`. Field semantics (type, literal unions, constraints, inline transforms) carry through to the derived shape; instance behavior (methods, computed `~>`, eager-derived `!>`, hooks, and `@ensure` refinements) does not. `User.omit "password"` produces a validator for `User` minus the password field; it won't have `.find()` or the `beforeSave` hook, but field-level transforms (`email, -> it.email.toLowerCase()`) continue to fire on the derived shape exactly as they did on the original. This invariant is enforced both at runtime (ORM methods throw on derived shapes with a targeted diagnostic pointing at query projection) and at the TypeScript level (algebra generics are parameterized over `Data`, not `Instance`, so the derived types correctly omit methods and ORM surface). Internally, the whole feature is a compiler sidecar — 54% of the implementation lives in `src/schema.js` and touches the core compiler in under 100 lines of wiring. A four-layer lazy runtime (raw descriptor → normalized metadata → validator plan → ORM plan / DDL plan) means module load is cheap, migration scripts never build the ORM plan, and validator-only consumers never build the class machinery. The full reference is in [docs/RIP-SCHEMA.md](docs/RIP-SCHEMA.md).
|
|
354
|
+
**Schema algebra** — `.pick`, `.omit`, `.partial`, `.required`, `.extend` — always returns a new `:shape`. Field semantics (type, literal unions, constraints, inline transforms) carry through to the derived shape; instance behavior (methods, computed `~>`, eager-derived `!>`, hooks, and `@ensure` refinements) does not. `User.omit "password"` produces a validator for `User` minus the password field; it won't have `.find()` or the `beforeSave` hook, but field-level transforms (`email, -> it.email.toLowerCase()`) continue to fire on the derived shape exactly as they did on the original. This invariant is enforced both at runtime (ORM methods throw on derived shapes with a targeted diagnostic pointing at query projection) and at the TypeScript level (algebra generics are parameterized over `Data`, not `Instance`, so the derived types correctly omit methods and ORM surface). Internally, the whole feature is a compiler sidecar — 54% of the implementation lives in `src/schema/schema.js` and touches the core compiler in under 100 lines of wiring. A four-layer lazy runtime (raw descriptor → normalized metadata → validator plan → ORM plan / DDL plan) means module load is cheap, migration scripts never build the ORM plan, and validator-only consumers never build the class machinery. The full reference is in [docs/RIP-SCHEMA.md](docs/RIP-SCHEMA.md).
|
|
353
355
|
|
|
354
356
|
---
|
|
355
357
|
|
|
@@ -365,9 +367,11 @@ Rip's reactivity is framework-agnostic — use it with React, Vue, Svelte, or va
|
|
|
365
367
|
|
|
366
368
|
---
|
|
367
369
|
|
|
368
|
-
## Rip
|
|
370
|
+
## Rip App
|
|
371
|
+
|
|
372
|
+
Load `rip.min.js` (~88KB Brotli) — the Rip compiler and Rip App framework in one file. Components are `.rip` source files, compiled on demand, rendered with fine-grained reactivity. No build step. No bundler.
|
|
369
373
|
|
|
370
|
-
|
|
374
|
+
(Rip UI is the separate widget package at `packages/ui/`. The two terms do not overlap.)
|
|
371
375
|
|
|
372
376
|
```html
|
|
373
377
|
<script defer src="rip.min.js" data-mount="Home"></script>
|
package/bin/rip
CHANGED
|
@@ -5,6 +5,11 @@ import { execSync, spawnSync, spawn } from 'child_process';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { dirname, basename, join } from 'path';
|
|
7
7
|
import { Compiler, formatError } from '../src/compiler.js';
|
|
8
|
+
// Side-effect imports — register CLI-side runtime providers. The browser
|
|
9
|
+
// bundle imports a different schema loader so server-only fragments
|
|
10
|
+
// (db-naming/orm/ddl) tree-shake out of docs/dist/rip.min.js.
|
|
11
|
+
import '../src/types-emit.js'; // registers emitTypes for .d.ts output
|
|
12
|
+
import '../src/schema/loader-server.js'; // registers full schema runtime
|
|
8
13
|
import packageJson from '../package.json' with { type: 'json' };
|
|
9
14
|
|
|
10
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
package/docs/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ App.mount()
|
|
|
37
37
|
- `index.html` — playground
|
|
38
38
|
- `demo.html` and `charts.html` — dashboard demos
|
|
39
39
|
- `sierpinski.html` — CDN demo
|
|
40
|
-
- `example/index.html` and `results/index.html` — app launchers / examples
|
|
40
|
+
- `example/index.html` and `results/index.html` — app launchers / examples. `example/index.json` is generated from `docs/demo/` via `bun run bundle:demo` (the source-of-truth lives in `docs/demo/`, the JSON is the deployable artifact).
|
|
41
41
|
- `ui/index.html` — widget gallery
|
|
42
42
|
|
|
43
43
|
Static demos can be opened via `file://`. The playground and example app require `bun run serve`.
|
package/docs/RIP-LANG.md
CHANGED
|
@@ -105,8 +105,18 @@ colors = %w(red green blue) # any delimiter: [] () {} <> || !! etc.
|
|
|
105
105
|
|
|
106
106
|
# Objects
|
|
107
107
|
user = {name: "Alice", age: 30}
|
|
108
|
-
shorthand = {name, age}
|
|
109
|
-
|
|
108
|
+
shorthand = {name, age} # Same as {name: name, age: age}
|
|
109
|
+
|
|
110
|
+
# Compound keys — dot- and hyphen-separated identifier chains collapse
|
|
111
|
+
# into a single flat string key. Useful for config trees and DOM-style
|
|
112
|
+
# attribute names without quote noise.
|
|
113
|
+
config = {api.host: "localhost", api.port: 3000} # → {'api.host': ..., 'api.port': ...}
|
|
114
|
+
attrs = {data-src: "/img.png", aria-label: "logo"} # → {'data-src': ..., 'aria-label': ...}
|
|
115
|
+
hosts = {beta-site.amazon.com: 100} # → {'beta-site.amazon.com': 100}
|
|
116
|
+
|
|
117
|
+
# No whitespace or newline is allowed on either side of `-` in a hyphen
|
|
118
|
+
# key — that's how `data-src` stays a single key while `a - b` stays
|
|
119
|
+
# subtraction. Dots are more permissive (whitespace either side is fine).
|
|
110
120
|
|
|
111
121
|
# Map literals — real JavaScript Map with any key type
|
|
112
122
|
table = *{
|
|
@@ -1460,7 +1470,7 @@ ids = read 'ids', 'ids' # "1,2,3" → [1, 2, 3]
|
|
|
1460
1470
|
slug = read 'slug', 'slug' # URL-safe slug
|
|
1461
1471
|
```
|
|
1462
1472
|
|
|
1463
|
-
## Rip
|
|
1473
|
+
## Rip App — Application Framework (built into rip-lang)
|
|
1464
1474
|
|
|
1465
1475
|
Zero-build reactive framework. Ships the compiler to the browser and compiles `.rip` components on demand. File-based routing, unified reactive stash, and SSE hot reload.
|
|
1466
1476
|
|
|
@@ -2224,8 +2234,10 @@ a[/pat/, 1] # regex extract
|
|
|
2224
2234
|
a? # existence check (a != null)
|
|
2225
2235
|
a ?? b # nullish coalescing
|
|
2226
2236
|
|
|
2227
|
-
#
|
|
2228
|
-
{a.b: 1}
|
|
2237
|
+
# Compound keys — dotted, hyphenated, or mixed (collapse to flat string keys)
|
|
2238
|
+
{a.b: 1} # {'a.b': 1}
|
|
2239
|
+
{data-src: 1} # {'data-src': 1} — no whitespace around `-`
|
|
2240
|
+
{beta-site.amazon.com: 100} # {'beta-site.amazon.com': 100} — mixed
|
|
2229
2241
|
|
|
2230
2242
|
# Word arrays
|
|
2231
2243
|
%w[foo bar baz] # ["foo", "bar", "baz"] — Ruby-style word literal
|
package/docs/RIP-SCHEMA.md
CHANGED
|
@@ -2324,7 +2324,7 @@ through this interface.
|
|
|
2324
2324
|
## 24. Compiler integration
|
|
2325
2325
|
|
|
2326
2326
|
The schema keyword is implemented as a compiler sidecar in
|
|
2327
|
-
`src/schema.js`, alongside the existing type and component sidecars.
|
|
2327
|
+
`src/schema/schema.js`, alongside the existing type and component sidecars.
|
|
2328
2328
|
This isolates the feature from the rest of the compiler: the main Rip
|
|
2329
2329
|
grammar has two productions for the schema keyword (not hundreds), and
|
|
2330
2330
|
the schema-specific body syntax never reaches the main parser.
|
|
@@ -2373,7 +2373,7 @@ binds to the instance correctly without special codegen.
|
|
|
2373
2373
|
|
|
2374
2374
|
| File | Role |
|
|
2375
2375
|
| -------------------- | ------------------------------------------------------------------ |
|
|
2376
|
-
| `src/schema.js` | Sub-parser, `emitSchema`, Layer 1-4 runtime, shadow TS walker, `installSchemaSupport` |
|
|
2376
|
+
| `src/schema/schema.js` | Sub-parser, `emitSchema`, Layer 1-4 runtime, shadow TS walker, `installSchemaSupport` |
|
|
2377
2377
|
| `src/lexer.js` | Hook point — calls `rewriteSchema()`; comment-token fix for `#` modifier |
|
|
2378
2378
|
| `src/grammar/grammar.rip` | The one `Schema` production |
|
|
2379
2379
|
| `src/compiler.js` | Dispatch for the `schema` s-expression head; preamble injection |
|
|
@@ -2381,7 +2381,7 @@ binds to the instance correctly without special codegen.
|
|
|
2381
2381
|
| `src/typecheck.js` | `hasSchemas()` probe so schema-only files aren't `@ts-nocheck`d |
|
|
2382
2382
|
| `test/rip/schema.rip` | The test suite |
|
|
2383
2383
|
|
|
2384
|
-
The total wiring in the core compiler (outside `src/schema.js`) is under
|
|
2384
|
+
The total wiring in the core compiler (outside `src/schema/schema.js`) is under
|
|
2385
2385
|
100 lines. That's the sidecar pattern working — the feature is big, but
|
|
2386
2386
|
its footprint in the main compiler is small.
|
|
2387
2387
|
|
|
@@ -2405,7 +2405,7 @@ transactions, eager loading, scopes, and soft deletes — not yet; see
|
|
|
2405
2405
|
|
|
2406
2406
|
**Does the runtime belong to `schema.js` or is it loaded separately?**
|
|
2407
2407
|
It's inlined. When a file uses `schema`, the compiler injects a small
|
|
2408
|
-
preamble (under `SCHEMA_RUNTIME` in `src/schema.js`) that defines
|
|
2408
|
+
preamble (under `SCHEMA_RUNTIME` in `src/schema/schema.js`) that defines
|
|
2409
2409
|
`SchemaError`, `__SchemaDef`, `__SchemaRegistry`, `Query`, and the
|
|
2410
2410
|
helpers. No import statement, no package dependency, no bootstrap call.
|
|
2411
2411
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Rip App Demo
|
|
2
|
+
|
|
3
|
+
The canonical "everything in one file" demo of the Rip App framework. Six components covering layout, routing, reactive state, list rendering, and reusable component composition.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
docs/demo/
|
|
9
|
+
├── components/
|
|
10
|
+
│ ├── _layout.rip — root layout with nav + error boundary
|
|
11
|
+
│ ├── index.rip — Home page (file-based routing: / → index.rip)
|
|
12
|
+
│ ├── counter.rip — reactive state, := / ~> persistence to stash
|
|
13
|
+
│ ├── todos.rip — list rendering, computed values, event handlers
|
|
14
|
+
│ ├── about.rip — uses the Card component for content sections
|
|
15
|
+
│ └── card.rip — reusable component with @children
|
|
16
|
+
└── css/
|
|
17
|
+
└── styles.css — design tokens + component styles, no Tailwind
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Bundle to one JSON file
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun run bundle:demo
|
|
24
|
+
# wraps:
|
|
25
|
+
# bun scripts/bundle-app.js docs/demo -o docs/example/index.json -t "Rip App Demo"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Output: `docs/example/index.json` — a single ~17 KB file containing every component's raw `.rip` source plus all CSS, ready to ship to any static host. The launcher at `docs/example/index.html` fetches it once and runs the whole app from memory: no bundler, no build step, no per-component network requests.
|
|
29
|
+
|
|
30
|
+
## Architecture
|
|
31
|
+
|
|
32
|
+
This is the "burn a CD" deployment model for a Rip app. The browser loads:
|
|
33
|
+
|
|
34
|
+
1. `docs/dist/rip.min.js` (~80 KB Brotli) — the compiler + framework + reactive runtime
|
|
35
|
+
2. `docs/example/index.json` (~17 KB) — the entire app's source
|
|
36
|
+
|
|
37
|
+
Then `<script type="text/rip">launch bundle: bundle</script>` mounts everything. Routing, reactivity, fine-grained DOM updates, all driven by code that compiles in the browser at mount time.
|
|
38
|
+
|
|
39
|
+
## Iterating
|
|
40
|
+
|
|
41
|
+
Edit any `.rip` file under `components/`, then re-run `bun run bundle:demo` to refresh the bundled JSON. The launcher HTML at `docs/example/` will pick up the new bundle on next load.
|
|
42
|
+
|
|
43
|
+
CSS is concatenated alphabetically by filename. Add `.css` files under `css/` to extend the design.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Root Layout (with error boundary and navigation indicator)
|
|
2
|
+
|
|
3
|
+
export Layout = component
|
|
4
|
+
errorMsg := null
|
|
5
|
+
|
|
6
|
+
~> localStorage.setItem '__rip_route', @router.path
|
|
7
|
+
|
|
8
|
+
onError: (err) ->
|
|
9
|
+
errorMsg = err.message
|
|
10
|
+
|
|
11
|
+
render
|
|
12
|
+
.app-layout
|
|
13
|
+
nav.app-nav
|
|
14
|
+
.nav-inner
|
|
15
|
+
a.nav-brand "Rip App"
|
|
16
|
+
.nav-links
|
|
17
|
+
a.('nav-link', @router.path is '/' and 'active') href: "#/", "Home"
|
|
18
|
+
a.('nav-link', @router.path is '/counter' and 'active') href: "#counter", "Counter"
|
|
19
|
+
a.('nav-link', @router.path is '/todos' and 'active') href: "#todos", "Todos"
|
|
20
|
+
a.('nav-link', @router.path is '/about' and 'active') href: "#about", "About"
|
|
21
|
+
if @router.navigating
|
|
22
|
+
span.nav-loading "Loading..."
|
|
23
|
+
main.app-main
|
|
24
|
+
if errorMsg
|
|
25
|
+
.error-banner
|
|
26
|
+
p "Something went wrong: #{errorMsg}"
|
|
27
|
+
button.btn @click: (-> errorMsg = null), "Dismiss"
|
|
28
|
+
#content
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# About Page — uses the Card component for content sections
|
|
2
|
+
|
|
3
|
+
export About = component
|
|
4
|
+
|
|
5
|
+
render
|
|
6
|
+
div
|
|
7
|
+
.page-header
|
|
8
|
+
h1.page-title "About Rip App"
|
|
9
|
+
p.page-subtitle "Zero-build reactive web apps!"
|
|
10
|
+
|
|
11
|
+
Card title: "The Idea"
|
|
12
|
+
p "Traditional frameworks build and bundle on the server, then ship static artifacts. Rip App ships the 40KB compiler to the browser instead. Components arrive as .rip source files, are compiled on demand, and render with fine-grained reactivity. No build step, no bundler, no configuration files."
|
|
13
|
+
|
|
14
|
+
Card title: "Two Keywords"
|
|
15
|
+
p "The component model adds exactly two keywords to the Rip language: component and render. Everything else — reactive state (:=), computed values (~=), effects (~>), methods, lifecycle hooks — is standard Rip syntax that already exists."
|
|
16
|
+
|
|
17
|
+
Card title: "Architecture"
|
|
18
|
+
p "Components live as source files in the unified stash. The Router maps URLs to components using file-based conventions (like Next.js). When you navigate, the Renderer compiles the matching .rip file and mounts the resulting component. Each reactive binding creates a direct DOM effect — no virtual DOM diffing."
|
|
19
|
+
|
|
20
|
+
Card title: "Stack"
|
|
21
|
+
.btn-group
|
|
22
|
+
.stat
|
|
23
|
+
span "Compiler "
|
|
24
|
+
span.stat-value "40KB"
|
|
25
|
+
.stat
|
|
26
|
+
span "Framework "
|
|
27
|
+
span.stat-value "~8KB"
|
|
28
|
+
.stat
|
|
29
|
+
span "Build step "
|
|
30
|
+
span.stat-value "None"
|
|
31
|
+
.stat
|
|
32
|
+
span "Dependencies "
|
|
33
|
+
span.stat-value "Zero"
|
|
34
|
+
|
|
35
|
+
Card title: "Modules"
|
|
36
|
+
p "The framework lives in one file: app.rip. It uses Rip's built-in reactive primitives directly — the same signals that power := and ~= in components. Deep reactive stash with path navigation. Component store for source management. File-based router mapping URLs to components. Renderer that compiles and mounts components with layout support."
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Counter Page
|
|
2
|
+
|
|
3
|
+
export Counter = component
|
|
4
|
+
count := @app.data.count or 0
|
|
5
|
+
step := 1
|
|
6
|
+
|
|
7
|
+
~> @app.data.count = count
|
|
8
|
+
|
|
9
|
+
increment: -> count += step
|
|
10
|
+
decrement: -> count -= step
|
|
11
|
+
reset: -> count = 0
|
|
12
|
+
|
|
13
|
+
render
|
|
14
|
+
div
|
|
15
|
+
.page-header
|
|
16
|
+
h1.page-title "Counter"
|
|
17
|
+
p.page-subtitle "Reactive state — persists across reload"
|
|
18
|
+
|
|
19
|
+
.card
|
|
20
|
+
.counter-display "#{count}"
|
|
21
|
+
|
|
22
|
+
.counter-controls
|
|
23
|
+
button.btn @click: @decrement, "- #{step}"
|
|
24
|
+
button.btn @click: @reset, "Reset"
|
|
25
|
+
button.btn.btn-primary @click: @increment, "+ #{step}"
|
|
26
|
+
|
|
27
|
+
.card
|
|
28
|
+
.card-title "Step Size"
|
|
29
|
+
.btn-group
|
|
30
|
+
button.btn @click: (-> step = 1), "1"
|
|
31
|
+
button.btn @click: (-> step = 5), "5"
|
|
32
|
+
button.btn @click: (-> step = 10), "10"
|
|
33
|
+
button.btn @click: (-> step = 100), "100"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Home Page
|
|
2
|
+
|
|
3
|
+
export Home = component
|
|
4
|
+
|
|
5
|
+
render
|
|
6
|
+
div
|
|
7
|
+
.page-header
|
|
8
|
+
h1.page-title "Rip App Framework"
|
|
9
|
+
p.page-subtitle "Zero-build reactive web apps"
|
|
10
|
+
|
|
11
|
+
.feature-grid
|
|
12
|
+
.feature-card
|
|
13
|
+
.feature-icon "🗂"
|
|
14
|
+
.feature-name "Component Store"
|
|
15
|
+
.feature-desc "Component source files loaded on demand. Compiled in the browser."
|
|
16
|
+
|
|
17
|
+
.feature-card
|
|
18
|
+
.feature-icon "🔀"
|
|
19
|
+
.feature-name "File-Based Router"
|
|
20
|
+
.feature-desc "URLs map to components. Dynamic segments and nested layouts."
|
|
21
|
+
|
|
22
|
+
.feature-card
|
|
23
|
+
.feature-icon "⚡"
|
|
24
|
+
.feature-name "Reactive Stash"
|
|
25
|
+
.feature-desc "Deep state tree with path navigation. Every property tracked."
|
|
26
|
+
|
|
27
|
+
.feature-card
|
|
28
|
+
.feature-icon "🎯"
|
|
29
|
+
.feature-name "Fine-Grained Rendering"
|
|
30
|
+
.feature-desc "No virtual DOM. Direct DOM updates via reactive effects."
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Todos Page
|
|
2
|
+
|
|
3
|
+
export Todos = component
|
|
4
|
+
newTodo := ''
|
|
5
|
+
todos := @app.data.todos or []
|
|
6
|
+
nextId := (@app.data.nextId or 1)
|
|
7
|
+
|
|
8
|
+
~> @app.data.todos = todos
|
|
9
|
+
~> @app.data.nextId = nextId
|
|
10
|
+
|
|
11
|
+
remaining ~= todos.filter((t) -> not t.done).length
|
|
12
|
+
|
|
13
|
+
addTodo: ->
|
|
14
|
+
if newTodo.trim()
|
|
15
|
+
todos = [...todos, { id: nextId, text: newTodo.trim(), done: false }]
|
|
16
|
+
nextId++
|
|
17
|
+
newTodo = ''
|
|
18
|
+
|
|
19
|
+
deleteTodo: (id) ->
|
|
20
|
+
todos = todos.filter (t) -> t.id isnt id
|
|
21
|
+
|
|
22
|
+
clearAll: ->
|
|
23
|
+
todos = []
|
|
24
|
+
|
|
25
|
+
handleKey: (e) ->
|
|
26
|
+
if e.key is 'Enter'
|
|
27
|
+
@addTodo()
|
|
28
|
+
|
|
29
|
+
render
|
|
30
|
+
div
|
|
31
|
+
.page-header
|
|
32
|
+
h1.page-title "Todos"
|
|
33
|
+
p.page-subtitle "List rendering and state management"
|
|
34
|
+
|
|
35
|
+
.card
|
|
36
|
+
.todo-input-row
|
|
37
|
+
input.input type: "text", value <=> newTodo, placeholder: "What needs to be done?", @keydown: @handleKey
|
|
38
|
+
button.btn.btn-primary @click: @addTodo, "Add"
|
|
39
|
+
|
|
40
|
+
ul.todo-list
|
|
41
|
+
for todo in todos
|
|
42
|
+
li.todo-item
|
|
43
|
+
span.todo-text todo.text
|
|
44
|
+
button.todo-delete @click: (=> @deleteTodo(todo.id)), "x"
|
|
45
|
+
|
|
46
|
+
.todo-stats
|
|
47
|
+
span "#{remaining} remaining"
|
|
48
|
+
button.btn.btn-sm @click: @clearAll, "Clear all"
|