|
| 1 | +# Plan: Rails / ERB runtime |
| 2 | + |
| 3 | +**Status:** later backlog (after Jinja2 J0–J4) |
| 4 | +**Goal:** ninth emit target — idiomatic **ERB** templates from the same brick |
| 5 | +defs as Twig/Latte/Jinja, with DOM parity. |
| 6 | + |
| 7 | +**Primary consumer:** full **Rails** apps (sqlite-first SSR). |
| 8 | +**Secondary consumers:** light Ruby stacks (**Sinatra or Roda + ERB**) — same |
| 9 | +templates, thinner host. |
| 10 | + |
| 11 | +BuildY owns runners/images; this repo owns the emitter + Ruby helpers + |
| 12 | +parity. Product track: BuildY `.project/plan/ruby-runners.md` (R0–R4; ERB |
| 13 | +emitter is **R4**, optional, after light template R1). |
| 14 | + |
| 15 | +## Why Rails / ERB (and when) |
| 16 | + |
| 17 | +| | | |
| 18 | +|--|--| |
| 19 | +| **Why ERB** | Default Rails view language; also used by Sinatra/Roda light templates. One emitter serves both levels. | |
| 20 | +| **Why not first** | BuildY ships PHP/Python runners (T0–T3) and Jinja codegen (**J0–J4**) first. Ruby is the **last** template-runner track. | |
| 21 | +| **Gate** | Start Er0 only after Jinja is first-class *and* BuildY has at least a light Ruby preview path (R1) **or** explicit product demand for ui8kit bricks in ERB (BuildY R4). | |
| 22 | + |
| 23 | +Mirror Jinja/Twig lessons: HTML-string children, structural skip predicate, |
| 24 | +class helpers for unsupported parts. No brick-def changes solely for ERB. |
| 25 | + |
| 26 | +## Composition model (expected) |
| 27 | + |
| 28 | +Same SSR class as Twig / Latte / Jinja: |
| 29 | + |
| 30 | +| Concern | Approach | |
| 31 | +|---------|----------| |
| 32 | +| Children | Pre-rendered HTML string, printed unescaped (`<%= children.html_safe %>` / `raw`) | |
| 33 | +| Nesting | Capture (`<% capture do %>…<% end %>`) or partials with a `children:` local | |
| 34 | +| Attrs | Caller hash merged last (Ruby helper, Twig-style) | |
| 35 | +| Class merge | Ruby `cn()` (plain concat, same as PHP `Rt::cn` / Python) | |
| 36 | +| Variants | Colocated `*.variants.json` or shared recipe helper | |
| 37 | + |
| 38 | +Likely output: `ui/<brick>/_part.html.erb` or `ui/<brick>/part.html.erb` — |
| 39 | +pick one partial naming convention in **Er0** and stick to it (Rails partial |
| 40 | +prefix `_` vs Sinatra flat views). |
| 41 | + |
| 42 | +## Coverage |
| 43 | + |
| 44 | +Start with the **same structural predicate** as Latte/Twig/Jinja |
| 45 | +(`phpSupported()` → rename toward `ssrTemplateSupported` when Jinja lands): |
| 46 | + |
| 47 | +- Target **60 / 63** parts initially. |
| 48 | +- Skip `breadcrumb/Breadcrumb`, `select/Select`, `icon/Icon` for the same |
| 49 | + reasons (forEach / branched root / branched slots). |
| 50 | +- Emit Ruby class helpers for **all** parts with a classes contract |
| 51 | + (mirror `Classes.php` / Python helpers). |
| 52 | + |
| 53 | +## Work items |
| 54 | + |
| 55 | +### Er0 — Spike + naming |
| 56 | + |
| 57 | +- [ ] Confirm ERB partial layout, Rails `render` locals, and Sinatra/Roda |
| 58 | + include path (one layout, both hosts). |
| 59 | +- [ ] Decide Ruby package layout under `generated/` (e.g. `ruby/lib/ui8kit/` |
| 60 | + with `cn`, attr helpers; optional Rails engine vs plain gem). |
| 61 | +- [ ] Document composition snippet (welcome-card nest) for ERB. |
| 62 | +- [ ] Asset story for examples: **importmap / Propshaft** preferred (align |
| 63 | + BuildY R2); no mandatory Node pipeline in the reference. |
| 64 | + |
| 65 | +### Er1 — Emitter |
| 66 | + |
| 67 | +- [ ] `RuntimeName` += `"erb"` (or `"rails"` only if CLI must be product-named — |
| 68 | + prefer **`erb`**: language is the emit target). |
| 69 | +- [ ] Register in `ALL_RUNTIMES` / CLI `--runtimes`. |
| 70 | +- [ ] `src/emitters/erb.ts` (+ expr printer; reuse `php-common` / Twig / Jinja |
| 71 | + analysis where safe). |
| 72 | +- [ ] Wire `generate.ts`: copy `runtime/ruby/*`, emit helpers, no duplicate paths. |
| 73 | +- [ ] Prop naming: HTML-native (same as Svelte/Vue/PHP/Jinja). |
| 74 | + |
| 75 | +### Er2 — Parity + deps |
| 76 | + |
| 77 | +- [ ] Gemspec / Bundler pin for parity harness (MRI 3.3+). |
| 78 | +- [ ] `cmd/parity-erb/` (or `parity-rails/`) harness: JSON in → HTML out; |
| 79 | + skip cleanly if `ruby` / `bundle` missing. |
| 80 | +- [ ] `tests/parity.erb.test.ts` (Bun-driven like PHP suites). |
| 81 | +- [ ] Extend `emitters.test.ts` structural checks. |
| 82 | + |
| 83 | +### Er3 — Examples |
| 84 | + |
| 85 | +- [ ] `examples/erb-rails/` — minimal **Rails** welcome twin (sqlite optional / |
| 86 | + no external DB on default route); shared `data/welcome.json` + static CSS. |
| 87 | +- [ ] Optional light twin: `examples/erb-sinatra/` **or** document that the |
| 88 | + same `ui/` partials render under Sinatra/Roda (one welcome is enough for |
| 89 | + Er3 if partials are host-agnostic). |
| 90 | +- [ ] `bun run dev:rails` (or `dev:erb`) + docs in `examples/README.md` / |
| 91 | + `docs/runtimes.md`. |
| 92 | + |
| 93 | +### Er4 — Docs + optional bundle |
| 94 | + |
| 95 | +- [ ] Update overview, architecture, runtimes, contract, testing, |
| 96 | + getting-started (“eight” → “nine” where accurate, after Jinja ships). |
| 97 | +- [ ] Optional: `generate:erb-bundle` (ERB/Ruby-only package, latte-bundle |
| 98 | + analogue) for Rails engines / gems. |
| 99 | + |
| 100 | +## Explicit non-goals (this plan) |
| 101 | + |
| 102 | +- Shipping ERB **before** Jinja2 (J0–J4 remains nearest backlog). |
| 103 | +- BuildY runner images / registry (`buildy-runner-ruby`, templates) — product |
| 104 | + repo owns those (BuildY `plan/ruby-runners.md`). |
| 105 | +- Haml / Slim-ruby emitters (BuildY R4 mentions Slim only as a later option). |
| 106 | +- Full 63/63 complex parts before ERB ships. |
| 107 | +- Mandatory Postgres / Redis / Node asset pipeline for the welcome example. |
| 108 | +- Changing brick defs solely for Rails. |
| 109 | + |
| 110 | +## Exit criteria |
| 111 | + |
| 112 | +- `bun run generate --runtimes erb` writes ERB partials + Ruby helpers. |
| 113 | +- Parity suite green when Ruby/Bundler is on PATH (skip cleanly when absent). |
| 114 | +- Rails welcome example renders the same layout tokens as other runtimes. |
| 115 | +- Docs list ERB as a first-class runtime; BuildY can consume partials in |
| 116 | + `ruby-rails` and light Sinatra/Roda templates without a second emitter. |
| 117 | + |
| 118 | +## Mapping to BuildY |
| 119 | + |
| 120 | +```text |
| 121 | +BuildY R0–R1 runner + light Sinatra/Roda+ERB (no codegen required) |
| 122 | +BuildY R2 full Rails template (manual/LLM ERB OK) |
| 123 | +BuildY R4 optional ui8kit ERB emitter ←→ this plan Er0–Er4 |
| 124 | +``` |
| 125 | + |
| 126 | +Codegen does **not** block BuildY R1/R2. Er* starts when Jinja is done and |
| 127 | +R4 demand (or an explicit “emit for Rails bricks” decision) is open. |
| 128 | + |
| 129 | +## References |
| 130 | + |
| 131 | +- BuildY Ruby track: `@buildy-ui/.project/plan/ruby-runners.md` |
| 132 | +- Jinja plan (prior SSR template runtime): [jinja2-runtime.md](./jinja2-runtime.md) |
| 133 | +- Latte/Twig composition: [docs/runtimes.md](../../docs/runtimes.md) |
| 134 | +- PHP skips: [docs/php-complex-parts.md](../../docs/php-complex-parts.md) |
| 135 | +- Add-runtime checklist: [docs/architecture.md](../../docs/architecture.md) |
0 commit comments