This is a start app kit template analogous to Jumpstart Pro or BulletTrain, but using Svelte and Inertia.js for the frontend, with Ruby on Rails as the backend, and including a number of other useful libraries and tools.
/docs/
to enable Claude Code to perform at its best.git clone https://github.com/<youruser>/<your_repo>
cd <your-repo>
bundle install
npm install
rails db:create:all
rails db:setup db:prepare
rails db:migrate:cache db:migrate:queue db:migrate:cable
rails db:schema:dump:cable db:schema:dump:cache db:schema:dump:queue
Check that the solid* databases have been created by checking db/cable_schema.rb
, db/cache_schema.rb
, and db/queue_schema.rb
and seeing that they contain a comment at the top about auto-generation.config/master.key
from a colleague, or rails credentials:edit
and add the following credentials:aws:
access_key_id: ...
s3_bucket: ...
s3_region: ...
secret_access_key: ...
ai:
claude:
api_token: ...
open_ai:
api_token: ...
openrouter:
api_token: ...
honeybadger:
api_key: ...
bin/dev
Necessary for Claude Code to be full featured.
claude mcp add --scope=local playwright npx @executeautomation/playwright-mcp-server
claude mcp add --scope=local snap-happy npx @mariozechner/snap-happy
This template integrates Svelte with Rails using Inertia.js to manage front-end routing while keeping Rails' backend structure. It uses Vite for asset bundling, and all frontend code is located in the app/frontend
directory. Place assets such as images and fonts inside the app/frontend/assets
folder.
Feel free to fork this repository and submit pull requests with improvements, fixes, or additional features.
This application includes a powerful real-time synchronization system that automatically updates Svelte components when Rails models change, using ActionCable and Inertia.js partial reloads.
Rails Side:
app/channels/sync_channel.rb
- ActionCable channel with authorizationapp/models/concerns/broadcastable.rb
- Model concern for automatic broadcastingapp/models/concerns/sync_authorizable.rb
- Authorization logic for sync accessapp/channels/application_cable/connection.rb
- WebSocket authenticationJavaScript/Svelte Side:
app/frontend/lib/cable.js
- Core ActionCable subscription managementapp/frontend/lib/use-sync.js
- Svelte hook for easy integration1. Add to your Rails model:
class Account < ApplicationRecord
include SyncAuthorizable
include Broadcastable
# Configure what to broadcast to
broadcasts_to :all # Broadcast to admin collection (for index pages)
end
class AccountUser < ApplicationRecord
include Broadcastable
belongs_to :account
belongs_to :user
# Broadcast changes to the parent account
broadcasts_to :account
end
class User < ApplicationRecord
include Broadcastable
has_many :accounts
# Broadcast changes to all associated accounts (uses Rails reflection)
broadcasts_to :accounts
end
Understanding broadcasts_to
:
:all
- Broadcasts to a collection channel (typically for admin index pages)belongs_to
/has_one
: Broadcasts to the single associated recordhas_many
/has_and_belongs_to_many
: Broadcasts to each record in the collection2. Use in your Svelte component:
For static subscriptions:
<script>
import { useSync } from '$lib/use-sync';
let { accounts = [] } = $props();
// Simple static subscriptions
useSync({
'Account:all': 'accounts', // Updates when any account changes
});
</script>
For dynamic subscriptions (when the subscribed objects can change):
<script>
import { createDynamicSync } from '$lib/use-sync';
let { accounts = [], selected_account = null } = $props();
// Create dynamic sync handler
const updateSync = createDynamicSync();
// Update subscriptions when selected_account changes
$effect(() => {
const subs = { 'Account:all': 'accounts' };
if (selected_account) {
subs[`Account:${selected_account.id}`] = 'selected_account';
}
updateSync(subs);
});
</script>
That's it! Your component will now automatically update when the data changes on the server.
Here's how all the pieces work together:
Rails Controller:
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
def index
render inertia: "Dashboard", props: {
current_user: current_user.as_json,
account: current_account.as_json,
notifications: current_user.notifications
}
end
end
Rails Models:
# app/models/notification.rb
class Notification < ApplicationRecord
include Broadcastable
belongs_to :user
# Broadcast changes to the parent user
broadcasts_to :user
end
# app/models/user.rb
class User < ApplicationRecord
include Broadcastable
has_many :accounts, through: :account_users
# Broadcast changes to all associated accounts
broadcasts_to :accounts
end
Svelte Component:
<script>
import { useSync } from '$lib/use-sync';
// These prop names match what the controller sends
let { current_user, account, notifications } = $props();
// Subscribe to updates - map channels to props to reload
useSync({
[`User:${current_user.id}`]: 'current_user',
[`Account:${account.id}`]: 'account',
[`Notification:all`]: 'notifications'
});
</script>
The key insight: The model just broadcasts its identity (e.g., "User:123"), and the Svelte component decides which props need reloading based on its subscriptions.
account
property: Accessible by all users in that accountaccount
property: Admin-only access:all
collections for any modelRun the synchronization tests:
rails test test/channels/sync_channel_test.rb
rails test test/models/concerns/broadcastable_test.rb
See the in-app documentation for more detailed information and advanced usage.
This application includes a powerful convention for controlling how Rails models are serialized to JSON, with automatic ID obfuscation for better security and cleaner URLs.
The json_attributes
concern provides a declarative way to specify which attributes and methods should be included when a model is converted to JSON (for Inertia props or API responses). It also automatically obfuscates model IDs using to_param
.
to_param
?
have the ?
removed in JSON (e.g., admin?
becomes admin
)current_user
) through nested associationsclass User < ApplicationRecord
include JsonAttributes
# Specify what to include in JSON, excluding sensitive fields
json_attributes :full_name, :site_admin, except: [:password_digest]
end
class Account < ApplicationRecord
include JsonAttributes
# Include boolean methods (the ? will be stripped in JSON)
json_attributes :personal?, :team?, :active?, :is_site_admin, :name
end
class AccountUser < ApplicationRecord
include JsonAttributes
# Include associations with their json_attributes
json_attributes :role, :confirmed_at, include: { user: {}, account: {} }
end
class AccountsController < ApplicationController
def show
@account = current_user.accounts.find(params[:id])
render inertia: "accounts/show", props: {
# as_json automatically uses json_attributes configuration
account: @account.as_json,
# Pass current_user context for authorization in nested associations
members: @account.account_users.as_json(current_user: current_user)
}
end
end
password_digest
are never accidentally exposedSee the in-app documentation for more detailed information and advanced usage.
This project is open-source and available under the MIT License.