This guide will help you complete the Rust widget renderer implementation. The main issues are:
- Missing
modal_classvariable - Template references a variable that doesn't exist - Incomplete JavaScript template - Only ~180 lines of an 853-line template is implemented
- Missing stub implementations - Several functions return empty strings
- Docker Rust environment - Rust toolchain not properly installed in Docker
- ✅ Rust extension structure is set up (
ext/widget_renderer/) - ✅ FormData struct parses Ruby hash data
- ✅ Basic template rendering framework exists
- ❌ JavaScript template is incomplete (only first 180 lines of 853)
- ❌ Missing variables in template (
modal_class) - ❌ Empty stub functions (
render_form_options, etc.) - ❌ Rust not available in Docker container
ext/widget_renderer/
├── Cargo.toml # Dependencies (already configured)
├── src/
│ ├── lib.rs # Entry point (working)
│ ├── form_data.rs # Data parsing (working)
│ └── template_renderer.rs # NEEDS FIXING
Reference file:
app/views/components/widget/_fba.js.erb- Original complete template (853 lines)
Line 153 of template_renderer.rs references {modal_class} but it's not defined.
this.dialogEl.setAttribute('class', "{modal_class} fba-modal");Add modal_class variable to the render_fba_form_function method.
- Open
ext/widget_renderer/src/template_renderer.rs - Find the function
fn render_fba_form_function(&self, form: &FormData) -> String - After the
quill_cssvariable (around line 44), add:
let modal_class = if form.kind == "recruitment" {
"usa-modal usa-modal--lg"
} else {
"usa-modal"
};- In the
format!(r#"...section at the bottom (around line 180), update the format parameters:
BEFORE:
"#,
turnstile_init = turnstile_init,
quill_init = quill_init,
quill_css = quill_css
)AFTER:
"#,
turnstile_init = turnstile_init,
quill_init = quill_init,
quill_css = quill_css,
modal_class = modal_class
)- Add
modal_classfield toFormDatastruct inform_data.rs:
In ext/widget_renderer/src/form_data.rs:
Find the struct definition and add:
pub struct FormData {
pub short_uuid: String,
pub modal_button_text: String,
// ... existing fields ...
pub kind: String, // Already exists
// ... rest of fields ...
}The template is incomplete - it ends at line 177 but should be 853 lines.
Look at app/views/components/widget/_fba.js.erb - this is the complete template.
You have TWO options:
Copy the remaining JavaScript from the ERB template and convert ERB syntax to Rust.
ERB to Rust Conversion Rules:
- ERB:
<%= value %>→ Rust:{value} - ERB:
<%- if condition %>→ Rust:{conditional_var}(pre-computed) - ERB double braces
{{→ Rust:{{{{(escape for format! macro) - ERB single braces
{→ Rust:{{
Example:
// ERB version
<%= form.modal_button_text %>// Rust version in format! macro
{modal_button_text}Start with a minimal working version, then add features incrementally.
Phase 1 - Minimal Working Widget:
- Just render the form initialization
- Skip complex features (pagination, validation, etc.)
- Test that it compiles and runs
Phase 2 - Add Core Features:
- Form submission
- Event listeners
- Basic validation
Phase 3 - Add Advanced Features:
- Pagination
- Turnstile/reCAPTCHA
- Rich text (Quill)
- Local storage
-
First, just make it compile by completing the basic structure
-
Add minimal
render_form_optionsimplementation:
fn render_form_options(&self, form: &FormData) -> String {
format!(r#"
var touchpointFormOptions{uuid} = {{
'formId': "{uuid}",
'modalButtonText': "{button_text}",
'elementSelector': "{selector}",
'deliveryMethod': "{delivery_method}",
'loadCSS': {load_css},
'suppressSubmitButton': {suppress_submit},
'verifyCsrf': {verify_csrf}
}};
"#,
uuid = form.short_uuid,
button_text = form.modal_button_text,
selector = form.element_selector,
delivery_method = form.delivery_method,
load_css = form.load_css,
suppress_submit = form.suppress_submit_button,
verify_csrf = form.verify_csrf
)
}- Add minimal
render_form_initialization:
fn render_form_initialization(&self, form: &FormData) -> String {
format!(r#"
window.touchpointForm{uuid} = new FBAform(document, window);
window.touchpointForm{uuid}.init(touchpointFormOptions{uuid});
"#,
uuid = form.short_uuid
)
}- Add minimal USWDS stubs:
fn render_uswds_bundle(&self) -> String {
r#"
// USWDS bundle would be loaded here
"#.to_string()
}
fn render_uswds_initialization(&self, _form: &FormData) -> String {
r#"
// USWDS initialization would be here
"#.to_string()
}Note: Use _form instead of form to suppress unused variable warnings.
Docker container doesn't have Rust installed, so compilation fails.
Update the Dockerfile to include Rust toolchain.
-
Open
Dockerfile -
Find the Ruby installation section (usually near the top)
-
Add Rust installation AFTER system dependencies:
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Verify Rust installation
RUN rustc --version && cargo --version- Alternative: Add to existing RUN command (more efficient):
# Install system dependencies and Rust
RUN apt-get update -qq && \
apt-get install -y build-essential curl && \
# ... other dependencies ... && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.cargo/bin:${PATH}"- Rebuild Docker image:
docker compose build webapp
docker compose up -d webapp- Verify Rust is available:
docker compose exec webapp rustc --version
docker compose exec webapp cargo --version- Enter the widget_renderer directory:
cd ext/widget_renderer- Build the Rust extension:
cargo build --release- Check for compilation errors:
- Read error messages carefully
- Most errors will be about missing variables in templates
- Fix them one by one
- Test in Ruby:
Create a test script: test/test_rust_widget.rb
require_relative '../ext/widget_renderer/src/lib'
form_data = {
short_uuid: 'test123',
modal_button_text: 'Click me',
element_selector: 'touchpoints-form',
delivery_method: 'modal',
load_css: true,
kind: 'survey',
enable_turnstile: false,
has_rich_text_questions: false,
verify_csrf: true,
prefix: '/touchpoints',
questions: []
}
result = WidgetRenderer.render(form_data)
puts result- Run the test:
ruby test/test_rust_widget.rb- Update Rails to use Rust renderer:
In your controller (probably app/controllers/widgets_controller.rb):
def show
form = Form.find_by(short_uuid: params[:id])
# Try Rust renderer first, fall back to Ruby
begin
form_data = prepare_form_data(form)
@widget_js = WidgetRenderer.render(form_data)
rescue => e
Rails.logger.error "Rust renderer failed: #{e.message}"
# Fall back to ERB rendering
@widget_js = render_to_string(
partial: 'components/widget/fba',
locals: { form: form }
)
end
render js: @widget_js
end- Test in development:
rails server
# Visit: http://localhost:3000/touchpoints/YOUR_FORM_ID.js- Compare outputs:
- Generate widget with Rust
- Generate widget with ERB
- They should produce identical JavaScript
require 'benchmark'
require_relative '../ext/widget_renderer/src/lib'
form = Form.first # or specific form
iterations = 1000
Benchmark.bmbm do |x|
x.report("ERB rendering:") do
iterations.times do
ApplicationController.render(
partial: 'components/widget/fba',
locals: { form: form }
)
end
end
x.report("Rust rendering:") do
iterations.times do
form_data = prepare_form_data(form)
WidgetRenderer.render(form_data)
end
end
endExpected results:
- Rust should be 10-100x faster
- Lower memory usage
- Consistent performance
Cause: Missing "#) at end of raw string
Fix: Make sure every format!(r#" has matching "#) or "#,
Cause: Variable referenced in template but not passed to format!
Fix: Add variable to the format! parameters list
Cause: Function parameter not used
Fix: Prefix with underscore: _form: &FormData
Cause: Rust not installed in Docker Fix: Follow Step 4 to update Dockerfile
Cause: JavaScript braces not properly escaped for Rust's format! macro
Fix:
- Single brace in output: Use
{{or}} - Literal brace in Rust template: Use
{{{{or}}}}
- Rust code compiles without errors
- Docker container has Rust installed
- Extension builds successfully
- Generated JavaScript is valid
- Widget loads in browser
- Form submission works
- Modal opens/closes correctly
- Performance is better than ERB
- No memory leaks
- Error handling works
-
Add remaining JavaScript methods from the ERB template:
loadButton()handleOtherOption()handlePhoneInput()submitForm()textCounter()- Full pagination logic
- Turnstile integration
- Quill rich text editor
-
Add CSS rendering - currently returns empty string
-
Add HTML rendering - form body generation
-
Optimize:
- Cache compiled templates
- Minimize string allocations
- Use &str instead of String where possible
-
Production hardening:
- Better error messages
- Input validation
- XSS protection
- Logging
✅ Minimum Viable Product:
- Widget JavaScript generates correctly
- No compilation errors
- 10x faster than ERB rendering
- Works for basic form display
✅ Production Ready:
- All features from ERB version
- Comprehensive tests
- Error handling
- Documentation
- Monitoring/logging
- Rust format! macro: https://doc.rust-lang.org/std/macro.format.html
- Raw strings in Rust: https://doc.rust-lang.org/reference/tokens.html#raw-string-literals
- Rutie documentation: https://github.com/danielpclark/rutie
Common issues:
- "Where do I add the modal_class variable?" → See STEP 2
- "How much of the template do I need?" → Start with STEP 3, Option B (minimal)
- "Rust won't compile in Docker" → Follow STEP 4 completely
- "How do I test this?" → Follow STEP 5 and STEP 6
Good luck! Start with getting the basics working, then incrementally add features. Don't try to implement everything at once.