u/erichstark

Recommend tools to create web/mobile design

Hello Designers!

I would like to ask for some recommendations to create design for mobile app.

I am building Lucanto - system for business finance. We have a lot of cool features that save time. Last time we shipped very good developer documentation https://docs.lucanto.eu and now it is time to use it and create our own mobile app 😅

So my idea is to take somehow tokens and design decisions from our web app and transform into mobile design. Last time I have tried Google stitch, but it doesn’t seem to be consistent enough.

Do you have some recommendations, process, skills what I can try? Btw I will build separate native apps. iOS first, then android.

reddit.com
u/erichstark — 3 days ago
▲ 13 r/rails

How we replaced N one-off foreign keys with a single polymorphic link graph (and the STI gotcha it exposed)

We ship invoicing software (Lucanto.eu) in Rails where documents constantly relate to each other: a quote becomes a proforma, a proforma becomes an invoice, an invoice sometimes gets a credit note. For years we modeled this the obvious way - one FK column per relationship:

class Invoice < Document
  belongs_to :proforma, optional: true
  belongs_to :price_offer, optional: true # the quote
end

class CreditNote < Document
  belongs_to :invoice, optional: true
end

Worked fine until we needed genuine many-to-many: one settlement invoice paying off multiple advance/deposit invoices, one collective invoice consolidating several delivery notes. You can't express that with a single FK column, and a join table per relationship type means N new tables for N relationship types.

What we did instead: one polymorphic join table for every relationship, with a typed enum instead of a dedicated table per relationship kind:

class DocumentLink < ApplicationRecord
  belongs_to :source, polymorphic: true
  belongs_to :target, polymorphic: true

  enum :link_type, {
    derives_from: "derives_from", # invoice <- proforma <- quote
    corrects:     "corrects",     # credit_note -> invoice
    settles:      "settles",      # invoice -> advance tax document
    consolidates: "consolidates", # collective_invoice -> many delivery_notes
    paid_via:     "paid_via",
    references:   "references"
  }
end

Gotcha #1 - STI + polymorphic type column. Document has many STI subtypes (Documents::Invoice, Documents::Proforma, ...), and any of them can be either side of a link. Store source_type/target_type as record.class.name and you get "Documents::Invoice", "Documents::Proforma", etc. - and a unique index on [source_type, source_id, target_type, target_id, link_type] stops actually preventing duplicate links between the same two records once they're looked up through different subtype contexts. We store the AR base_class name instead ("Document", never the STI subclass), so the index means what it's supposed to.

Gotcha #2 - polymorphic links silently break tenant scoping. A real FK + belongs_to at least implies "this column only makes sense within one schema." Bare source_id/target_id against a polymorphic type has no such guarantee - nothing stops linking a document in tenant A to a document in tenant B. We actually shipped that as a real IDOR before catching it: a user could mass-assign another workspace's proforma id and have it render in their own sidebar. Fixed with an explicit validation:

validate :source_and_target_share_workspace

def source_and_target_share_workspace
  return unless source && target
  errors.add(:target, "must belong to the same workspace") if source.workspace_id != target.workspace_id
end

plus scoping every lookup path to the current tenant rather than trusting the polymorphic id alone.

Keeping belongs_to ergonomics. We didn't want every call site rewritten to query DocumentLink directly, so a small DSL sits on top of a Linkable concern:

class Documents::Invoice < Document
  link_belongs_to :proforma, link_type: :derives_from
end

invoice.proforma                  # reads like a normal association
invoice.proforma = some_proforma  # writes through to DocumentLink.link!

Walking the graph. The user-facing payoff is a "deal timeline" - open any document, see the whole chain. That's a BFS over DocumentLink, bidirectional, tenant-scoped, with a hard node cap, because link graphs plus bad data can cycle, and an unbounded walk inside a request is a self-inflicted DoS.

Not "abandon FKs for everything" - a real belongs_to is still simpler and faster for a relationship that's truly 1:1 and never expands. But if you can already picture a many-to-many version of a relationship showing up eventually, modeling it as a link graph from day one is a lot cheaper than the migration we ended up doing.

u/erichstark — 3 days ago
▲ 54 r/rubyonrails+1 crossposts

We built a multi-tenant invoicing SaaS - Rails 8 + Hotwire, PSD2 banking, AI document extraction, and an MCP platform coming in June

Hey community,

We've been building Lucanto - an invoicing and accounting SaaS for small European businesses. Sharing some of the more interesting Rails decisions we made along the way.

Stack overview:

  • Rails 8.1 + Hotwire (Turbo + Stimulus)
  • PostgreSQL 18, solid_queue, solid_cache
  • Bootstrap 5.3 with a fully custom design system on top
  • esbuild for JS bundling
  • Currently hosted on Render, migrating to Hetzner bare metal + Kamal. Self-hosting testing environment with Kamal has been surprisingly smooth for a small team.

A few things worth talking about:

1. Hotwire handles more complexity than people expect

We have a document editor with a live sidebar: real-time status badges, file upload with instant preview, PDF rendering, drag-and-drop, bank transaction matching. All of it is Turbo Frames + Streams + Stimulus, no SPA framework involved. The pattern that made this manageable:

Turbo Frames for isolated region updates, broadcast refresh for server-triggered refreshes, and Stimulus controllers kept narrow and composable. We've never felt the pull toward React.

2. PSD2 bank sync - direct integration vs. aggregators

We built a direct PSD2 integration with Tatra Bank (SK/CZ market) for automatic bank sync and transaction matching. It works well for our market, but PSD2's dirty secret is that the EU directive mandates the concept, not a standard API. Every bank ships its own auth flow, data model, and error format. Direct integration took weeks for one bank. For EU-wide coverage you essentially need an aggregator - is here someone who has some experience with some API that supports multiple European banks? Curious about real-world reliability.

3. AI invoices and receipts extraction

Users upload a receipt or contract, we extract and back-fill the form automatically. We build this as separate FastAPI python service. Not public one for now, but we are thinking about separate product.

4. CanCanCan for role-based permissions

We use CanCanCan for role-based access control within each tenant - owner, accountant, read-only viewer, etc. Multi-tenancy itself is handled via account scoping at the model layer (every query scoped to current_account). Clean separation: tenancy = scope, authorization = ability.

5. Custom design system on Bootstrap

Rather than going full custom CSS or switching to Tailwind, we built a design system layer on top of Bootstrap 5 - custom SCSS tokens, component overrides, dark/light theme switching via CSS variables. Bootstrap gives you the structural scaffolding; our layer controls every visual decision. 50+ component files so far and it's stayed maintainable. We are thinking to use view_component, but it was not a priority for now.

6. Hetzner + Kamal for self-hosting

We're moving off Render to our own Hetzner servers. Kamal makes this surprisingly low-ops for a small team - Docker-based deploys, health checks, zero-downtime rolling restarts, and it's all in version-controlled config. And it seems it will be also lower cost than managed service.

What's next: API + MCP platform

We're launching a full REST API + MCP (Model Context Protocol) server by end of June. The MCP layer is interesting - it means AI agents can create invoices, query accounting data, and trigger workflows directly from tools like Claude. We build it on top of API, running inside of Rails, official Ruby SDK. First of this kind for the CZ/SK market as far as we know.

Happy to go deeper on any of these. What would you have done differently?

If you would like to try, here is my link: https://app.lucanto.eu/r/erichstark

u/erichstark — 1 month ago